212 lines
5.7 KiB
Dart
212 lines
5.7 KiB
Dart
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;
|
|
}
|