refactor: Projektstruktur in app/ und backend/ aufgeteilt

This commit is contained in:
2026-03-24 15:06:44 +01:00
parent 5f5d5995c5
commit 2673ef658d
449 changed files with 28551 additions and 167 deletions

View File

@@ -0,0 +1,211 @@
import 'package:votianlt_app/services/developer.dart' as developer;
enum ChatDirection { incoming, outgoing }
enum ChatMessageType { general, jobRelated }
enum ChatContentType { text, image }
ChatDirection chatDirectionFromString(
String? value, {
ChatDirection fallback = ChatDirection.incoming,
}) {
switch (value?.toUpperCase()) {
case 'CLIENT':
case 'OUTGOING':
return ChatDirection.outgoing;
case 'SERVER':
case 'INCOMING':
return ChatDirection.incoming;
default:
return fallback;
}
}
String chatDirectionToString(ChatDirection direction) {
return direction == ChatDirection.outgoing ? 'CLIENT' : 'SERVER';
}
ChatMessageType chatMessageTypeFromString(
String? value, {
ChatMessageType fallback = ChatMessageType.general,
}) {
switch (value?.toUpperCase()) {
case 'JOB_RELATED':
return ChatMessageType.jobRelated;
case 'GENERAL':
return ChatMessageType.general;
default:
return fallback;
}
}
String chatMessageTypeToString(ChatMessageType type) {
return type == ChatMessageType.jobRelated ? 'JOB_RELATED' : 'GENERAL';
}
ChatContentType chatContentTypeFromString(
String? value, {
ChatContentType fallback = ChatContentType.text,
}) {
switch (value?.toUpperCase()) {
case 'IMAGE':
return ChatContentType.image;
case 'TEXT':
return ChatContentType.text;
default:
return fallback;
}
}
String chatContentTypeToString(ChatContentType type) {
return type == ChatContentType.image ? 'IMAGE' : 'TEXT';
}
class ChatMessage {
final String id;
final String content;
final DateTime createdAt;
final ChatDirection direction;
final ChatMessageType messageType;
final ChatContentType contentType;
final String? jobId;
final String? jobNumber;
final bool read;
final bool pendingSync;
const ChatMessage({
required this.id,
required this.content,
required this.createdAt,
required this.direction,
required this.messageType,
this.contentType = ChatContentType.text,
this.jobId,
this.jobNumber,
this.read = false,
this.pendingSync = false,
});
factory ChatMessage.fromJson(Map<String, dynamic> json) {
final rawId = (json['messageId'] ?? json['id'] ?? '').toString();
final rawContent = (json['content'] ?? json['text'] ?? '').toString();
final rawContentType = json['contentType']?.toString();
DateTime createdAt;
final createdAtRaw = json['createdAt'] ?? json['timestamp'];
if (createdAtRaw is DateTime) {
createdAt = createdAtRaw;
} else {
createdAt =
DateTime.tryParse(createdAtRaw?.toString() ?? '') ?? DateTime.now();
}
var direction = chatDirectionFromString(
json['origin']?.toString() ?? json['direction']?.toString(),
fallback:
json['isOwn'] == true
? ChatDirection.outgoing
: ChatDirection.incoming,
);
var messageType = chatMessageTypeFromString(
json['messageType']?.toString(),
fallback:
((json['jobId']?.toString().isNotEmpty ?? false) ||
(json['jobNumber']?.toString().isNotEmpty ?? false))
? ChatMessageType.jobRelated
: ChatMessageType.general,
);
final jobIdRaw = json['jobId']?.toString();
final jobNumberRaw = json['jobNumber']?.toString();
final jobId = jobIdRaw?.trim();
final jobNumber = jobNumberRaw?.trim();
if (rawId.isEmpty || rawContent.isEmpty) {
developer.log(
'Invalid ChatMessage payload (one or more required fields missing): $json',
);
}
return ChatMessage(
id: rawId,
content: rawContent,
createdAt: createdAt,
direction: direction,
messageType: messageType,
contentType: chatContentTypeFromString(rawContentType),
jobId: jobId?.isEmpty ?? true ? null : jobId,
jobNumber: jobNumber?.isEmpty ?? true ? null : jobNumber,
read: json['read'] == true,
pendingSync: json['pendingSync'] == true,
);
}
Map<String, dynamic> toJson() {
final map = <String, dynamic>{
'messageId': id,
'content': content,
'origin': chatDirectionToString(direction),
'messageType': chatMessageTypeToString(messageType),
'contentType': chatContentTypeToString(contentType),
'createdAt': createdAt.toIso8601String(),
'read': read,
};
if (jobId != null && jobId!.isNotEmpty) {
map['jobId'] = jobId;
}
if (jobNumber != null && jobNumber!.isNotEmpty) {
map['jobNumber'] = jobNumber;
}
if (pendingSync) {
map['pendingSync'] = true;
}
return map;
}
bool get isOwn => direction == ChatDirection.outgoing;
ChatMessage copyWith({
String? id,
String? content,
DateTime? createdAt,
ChatDirection? direction,
ChatMessageType? messageType,
ChatContentType? contentType,
String? jobId,
String? jobNumber,
bool? read,
bool? pendingSync,
}) {
return ChatMessage(
id: id ?? this.id,
content: content ?? this.content,
createdAt: createdAt ?? this.createdAt,
direction: direction ?? this.direction,
messageType: messageType ?? this.messageType,
contentType: contentType ?? this.contentType,
jobId: jobId ?? this.jobId,
jobNumber: jobNumber ?? this.jobNumber,
read: read ?? this.read,
pendingSync: pendingSync ?? this.pendingSync,
);
}
@override
String toString() {
return 'ChatMessage(id: $id, direction: $direction, messageType: $messageType, contentType: $contentType, createdAt: $createdAt)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ChatMessage && other.id == id;
}
@override
int get hashCode => id.hashCode;
}