341 lines
9.2 KiB
Dart
341 lines
9.2 KiB
Dart
enum MessageType {
|
|
text,
|
|
file,
|
|
image,
|
|
system;
|
|
|
|
static MessageType fromString(String? value) {
|
|
switch (value?.toUpperCase()) {
|
|
case 'FILE':
|
|
return MessageType.file;
|
|
case 'IMAGE':
|
|
return MessageType.image;
|
|
case 'SYSTEM':
|
|
return MessageType.system;
|
|
default:
|
|
return MessageType.text;
|
|
}
|
|
}
|
|
|
|
String toApiString() => name.toUpperCase();
|
|
}
|
|
|
|
enum MessageStatus {
|
|
sent,
|
|
delivered,
|
|
read;
|
|
|
|
static MessageStatus fromString(String? value) {
|
|
switch (value?.toUpperCase()) {
|
|
case 'DELIVERED':
|
|
return MessageStatus.delivered;
|
|
case 'READ':
|
|
return MessageStatus.read;
|
|
default:
|
|
return MessageStatus.sent;
|
|
}
|
|
}
|
|
}
|
|
|
|
class OtherParty {
|
|
final String id;
|
|
final String? userId;
|
|
final String name;
|
|
final String? avatar;
|
|
final String? headline;
|
|
final bool isOnline;
|
|
final String? lastSeenAt;
|
|
|
|
const OtherParty({
|
|
required this.id,
|
|
this.userId,
|
|
required this.name,
|
|
this.avatar,
|
|
this.headline,
|
|
this.isOnline = false,
|
|
this.lastSeenAt,
|
|
});
|
|
|
|
factory OtherParty.fromJson(Map<String, dynamic> json) {
|
|
return OtherParty(
|
|
id: json['id'] as String,
|
|
userId: json['userId'] as String?,
|
|
name: json['name'] as String? ?? '',
|
|
avatar: json['avatar'] as String?,
|
|
headline: json['headline'] as String?,
|
|
isOnline: json['isOnline'] as bool? ?? false,
|
|
lastSeenAt: json['lastSeenAt'] as String?,
|
|
);
|
|
}
|
|
|
|
OtherParty copyWith({bool? isOnline, String? lastSeenAt}) {
|
|
return OtherParty(
|
|
id: id,
|
|
userId: userId,
|
|
name: name,
|
|
avatar: avatar,
|
|
headline: headline,
|
|
isOnline: isOnline ?? this.isOnline,
|
|
lastSeenAt: lastSeenAt ?? this.lastSeenAt,
|
|
);
|
|
}
|
|
}
|
|
|
|
class Conversation {
|
|
final String id;
|
|
final String userId;
|
|
final String agentProfileId;
|
|
final String? lastMessageAt;
|
|
final String? lastMessageText;
|
|
final int userUnreadCount;
|
|
final int agentUnreadCount;
|
|
final int unreadCount;
|
|
final bool userMuted;
|
|
final bool agentMuted;
|
|
final bool userFavorited;
|
|
final bool agentFavorited;
|
|
final String createdAt;
|
|
final String updatedAt;
|
|
final OtherParty otherParty;
|
|
final List<ChatMessage>? messages;
|
|
|
|
const Conversation({
|
|
required this.id,
|
|
required this.userId,
|
|
required this.agentProfileId,
|
|
this.lastMessageAt,
|
|
this.lastMessageText,
|
|
this.userUnreadCount = 0,
|
|
this.agentUnreadCount = 0,
|
|
this.unreadCount = 0,
|
|
this.userMuted = false,
|
|
this.agentMuted = false,
|
|
this.userFavorited = false,
|
|
this.agentFavorited = false,
|
|
required this.createdAt,
|
|
required this.updatedAt,
|
|
required this.otherParty,
|
|
this.messages,
|
|
});
|
|
|
|
factory Conversation.fromJson(Map<String, dynamic> json) {
|
|
return Conversation(
|
|
id: json['id'] as String,
|
|
userId: json['userId'] as String,
|
|
agentProfileId: json['agentProfileId'] as String,
|
|
lastMessageAt: json['lastMessageAt'] as String?,
|
|
lastMessageText: json['lastMessageText'] as String?,
|
|
userUnreadCount: json['userUnreadCount'] as int? ?? 0,
|
|
agentUnreadCount: json['agentUnreadCount'] as int? ?? 0,
|
|
unreadCount: json['unreadCount'] as int? ?? 0,
|
|
userMuted: json['userMuted'] as bool? ?? false,
|
|
agentMuted: json['agentMuted'] as bool? ?? false,
|
|
userFavorited: json['userFavorited'] as bool? ?? false,
|
|
agentFavorited: json['agentFavorited'] as bool? ?? false,
|
|
createdAt: json['createdAt'] as String,
|
|
updatedAt: json['updatedAt'] as String,
|
|
otherParty:
|
|
OtherParty.fromJson(json['otherParty'] as Map<String, dynamic>),
|
|
messages: (json['messages'] as List<dynamic>?)
|
|
?.map((e) => ChatMessage.fromJson(e as Map<String, dynamic>))
|
|
.toList(),
|
|
);
|
|
}
|
|
|
|
Conversation copyWith({
|
|
int? unreadCount,
|
|
String? lastMessageAt,
|
|
String? lastMessageText,
|
|
bool? userMuted,
|
|
bool? agentMuted,
|
|
bool? userFavorited,
|
|
bool? agentFavorited,
|
|
OtherParty? otherParty,
|
|
List<ChatMessage>? messages,
|
|
}) {
|
|
return Conversation(
|
|
id: id,
|
|
userId: userId,
|
|
agentProfileId: agentProfileId,
|
|
lastMessageAt: lastMessageAt ?? this.lastMessageAt,
|
|
lastMessageText: lastMessageText ?? this.lastMessageText,
|
|
userUnreadCount: userUnreadCount,
|
|
agentUnreadCount: agentUnreadCount,
|
|
unreadCount: unreadCount ?? this.unreadCount,
|
|
userMuted: userMuted ?? this.userMuted,
|
|
agentMuted: agentMuted ?? this.agentMuted,
|
|
userFavorited: userFavorited ?? this.userFavorited,
|
|
agentFavorited: agentFavorited ?? this.agentFavorited,
|
|
createdAt: createdAt,
|
|
updatedAt: updatedAt,
|
|
otherParty: otherParty ?? this.otherParty,
|
|
messages: messages ?? this.messages,
|
|
);
|
|
}
|
|
}
|
|
|
|
class SenderProfile {
|
|
final String? firstName;
|
|
final String? lastName;
|
|
final String? avatar;
|
|
|
|
const SenderProfile({this.firstName, this.lastName, this.avatar});
|
|
|
|
factory SenderProfile.fromJson(Map<String, dynamic> json) {
|
|
return SenderProfile(
|
|
firstName: json['firstName'] as String?,
|
|
lastName: json['lastName'] as String?,
|
|
avatar: json['avatar'] as String?,
|
|
);
|
|
}
|
|
}
|
|
|
|
class MessageSender {
|
|
final String id;
|
|
final String role;
|
|
final SenderProfile? userProfile;
|
|
final SenderProfile? agentProfile;
|
|
|
|
const MessageSender({
|
|
required this.id,
|
|
required this.role,
|
|
this.userProfile,
|
|
this.agentProfile,
|
|
});
|
|
|
|
String get displayName {
|
|
final profile = userProfile ?? agentProfile;
|
|
if (profile == null) return 'Unknown';
|
|
return '${profile.firstName ?? ''} ${profile.lastName ?? ''}'.trim();
|
|
}
|
|
|
|
String? get avatar => userProfile?.avatar ?? agentProfile?.avatar;
|
|
|
|
factory MessageSender.fromJson(Map<String, dynamic> json) {
|
|
return MessageSender(
|
|
id: json['id'] as String,
|
|
role: json['role'] as String? ?? '',
|
|
userProfile: json['userProfile'] != null
|
|
? SenderProfile.fromJson(
|
|
_safeMap(json['userProfile']))
|
|
: null,
|
|
agentProfile: json['agentProfile'] != null
|
|
? SenderProfile.fromJson(
|
|
_safeMap(json['agentProfile']))
|
|
: null,
|
|
);
|
|
}
|
|
|
|
/// socket.io in Dart can deliver nested maps as Map<dynamic, dynamic>
|
|
static Map<String, dynamic> _safeMap(dynamic val) {
|
|
if (val is Map<String, dynamic>) return val;
|
|
if (val is Map) return Map<String, dynamic>.from(val);
|
|
return <String, dynamic>{};
|
|
}
|
|
}
|
|
|
|
class ChatMessage {
|
|
final String id;
|
|
final String conversationId;
|
|
final String senderId;
|
|
final String content;
|
|
final MessageType messageType;
|
|
final String? fileUrl;
|
|
final String? fileName;
|
|
final int? fileSize;
|
|
final String? mimeType;
|
|
final MessageStatus status;
|
|
final String? deliveredAt;
|
|
final String? readAt;
|
|
final String createdAt;
|
|
final String updatedAt;
|
|
final MessageSender sender;
|
|
|
|
const ChatMessage({
|
|
required this.id,
|
|
required this.conversationId,
|
|
required this.senderId,
|
|
required this.content,
|
|
this.messageType = MessageType.text,
|
|
this.fileUrl,
|
|
this.fileName,
|
|
this.fileSize,
|
|
this.mimeType,
|
|
this.status = MessageStatus.sent,
|
|
this.deliveredAt,
|
|
this.readAt,
|
|
required this.createdAt,
|
|
required this.updatedAt,
|
|
required this.sender,
|
|
});
|
|
|
|
bool isMine(String currentUserId) => senderId == currentUserId;
|
|
|
|
ChatMessage copyWith({MessageStatus? status, String? readAt, String? deliveredAt}) {
|
|
return ChatMessage(
|
|
id: id,
|
|
conversationId: conversationId,
|
|
senderId: senderId,
|
|
content: content,
|
|
messageType: messageType,
|
|
fileUrl: fileUrl,
|
|
fileName: fileName,
|
|
fileSize: fileSize,
|
|
mimeType: mimeType,
|
|
status: status ?? this.status,
|
|
deliveredAt: deliveredAt ?? this.deliveredAt,
|
|
readAt: readAt ?? this.readAt,
|
|
createdAt: createdAt,
|
|
updatedAt: updatedAt,
|
|
sender: sender,
|
|
);
|
|
}
|
|
|
|
factory ChatMessage.fromJson(Map<String, dynamic> json) {
|
|
return ChatMessage(
|
|
id: json['id'] as String,
|
|
conversationId: json['conversationId'] as String,
|
|
senderId: json['senderId'] as String,
|
|
content: json['content'] as String? ?? '',
|
|
messageType: MessageType.fromString(json['messageType'] as String?),
|
|
fileUrl: json['fileUrl'] as String?,
|
|
fileName: json['fileName'] as String?,
|
|
fileSize: json['fileSize'] as int?,
|
|
mimeType: json['mimeType'] as String?,
|
|
status: MessageStatus.fromString(json['status'] as String?),
|
|
deliveredAt: json['deliveredAt'] as String?,
|
|
readAt: json['readAt'] as String?,
|
|
createdAt: json['createdAt'] as String,
|
|
updatedAt: json['updatedAt'] as String,
|
|
sender: json['sender'] != null
|
|
? MessageSender.fromJson(MessageSender._safeMap(json['sender']))
|
|
: MessageSender(id: json['senderId'] as String, role: ''),
|
|
);
|
|
}
|
|
}
|
|
|
|
class PaginationInfo {
|
|
final int page;
|
|
final int limit;
|
|
final int total;
|
|
final int pages;
|
|
|
|
const PaginationInfo({
|
|
required this.page,
|
|
required this.limit,
|
|
required this.total,
|
|
required this.pages,
|
|
});
|
|
|
|
bool get hasMore => page < pages;
|
|
|
|
factory PaginationInfo.fromJson(Map<String, dynamic> json) {
|
|
return PaginationInfo(
|
|
page: json['page'] as int? ?? 1,
|
|
limit: json['limit'] as int? ?? 50,
|
|
total: json['total'] as int? ?? 0,
|
|
pages: json['pages'] as int? ?? 1,
|
|
);
|
|
}
|
|
}
|