feat: Implement conversation mute and favorite features with UI integration and state management.
This commit is contained in:
@@ -186,6 +186,38 @@ class MessagingRepository {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle mute status for a conversation
|
||||
Future<void> toggleMute(String conversationId, bool muted) async {
|
||||
try {
|
||||
await _dio.patch(
|
||||
'${ApiConstants.conversations}/$conversationId/mute',
|
||||
data: {'muted': muted},
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
if (e.error is ApiException) throw e.error as ApiException;
|
||||
throw ApiException(
|
||||
message: e.message ?? 'Failed to toggle mute',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle favorite status for a conversation
|
||||
Future<void> toggleFavorite(String conversationId, bool favorited) async {
|
||||
try {
|
||||
await _dio.patch(
|
||||
'${ApiConstants.conversations}/$conversationId/favorite',
|
||||
data: {'favorited': favorited},
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
if (e.error is ApiException) throw e.error as ApiException;
|
||||
throw ApiException(
|
||||
message: e.message ?? 'Failed to toggle favorite',
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final messagingRepositoryProvider = Provider<MessagingRepository>((ref) {
|
||||
|
||||
@@ -90,6 +90,10 @@ class Conversation {
|
||||
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;
|
||||
@@ -104,6 +108,10 @@ class Conversation {
|
||||
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,
|
||||
@@ -120,6 +128,10 @@ class Conversation {
|
||||
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:
|
||||
@@ -134,6 +146,10 @@ class Conversation {
|
||||
int? unreadCount,
|
||||
String? lastMessageAt,
|
||||
String? lastMessageText,
|
||||
bool? userMuted,
|
||||
bool? agentMuted,
|
||||
bool? userFavorited,
|
||||
bool? agentFavorited,
|
||||
OtherParty? otherParty,
|
||||
List<ChatMessage>? messages,
|
||||
}) {
|
||||
@@ -146,6 +162,10 @@ class Conversation {
|
||||
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,
|
||||
|
||||
@@ -569,6 +569,55 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
|
||||
state = state.copyWith(error: 'Failed to delete conversation');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleMute(String conversationId, bool muted) async {
|
||||
try {
|
||||
await _repository.toggleMute(conversationId, muted);
|
||||
final currentUserId = _ref.read(currentUserIdProvider);
|
||||
final updatedConversations = state.conversations.map((c) {
|
||||
if (c.id == conversationId) {
|
||||
final isUser = c.userId == currentUserId;
|
||||
return isUser
|
||||
? c.copyWith(userMuted: muted)
|
||||
: c.copyWith(agentMuted: muted);
|
||||
}
|
||||
return c;
|
||||
}).toList();
|
||||
state = state.copyWith(conversations: updatedConversations);
|
||||
} catch (_) {
|
||||
state = state.copyWith(error: 'Failed to toggle mute');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleFavorite(String conversationId, bool favorited) async {
|
||||
try {
|
||||
await _repository.toggleFavorite(conversationId, favorited);
|
||||
final currentUserId = _ref.read(currentUserIdProvider);
|
||||
var updatedConversations = state.conversations.map((c) {
|
||||
if (c.id == conversationId) {
|
||||
final isUser = c.userId == currentUserId;
|
||||
return isUser
|
||||
? c.copyWith(userFavorited: favorited)
|
||||
: c.copyWith(agentFavorited: favorited);
|
||||
}
|
||||
return c;
|
||||
}).toList();
|
||||
// Sort: favorites first, then by lastMessageAt
|
||||
updatedConversations.sort((a, b) {
|
||||
final aFav = a.userId == currentUserId ? a.userFavorited : a.agentFavorited;
|
||||
final bFav = b.userId == currentUserId ? b.userFavorited : b.agentFavorited;
|
||||
if (aFav != bFav) {
|
||||
return aFav ? -1 : 1;
|
||||
}
|
||||
final aTime = a.lastMessageAt ?? '';
|
||||
final bTime = b.lastMessageAt ?? '';
|
||||
return bTime.compareTo(aTime);
|
||||
});
|
||||
state = state.copyWith(conversations: updatedConversations);
|
||||
} catch (_) {
|
||||
state = state.copyWith(error: 'Failed to toggle favorite');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final messagingProvider =
|
||||
|
||||
@@ -45,6 +45,17 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
|
||||
notifier.setActiveConversation(widget.conversationId);
|
||||
notifier.loadMessages(widget.conversationId);
|
||||
notifier.markAsRead(widget.conversationId);
|
||||
// Initialize mute/star state from conversation data
|
||||
final conversations = ref.read(messagingProvider).conversations;
|
||||
final conv = conversations.where((c) => c.id == widget.conversationId).firstOrNull;
|
||||
if (conv != null) {
|
||||
final currentUserId = ref.read(currentUserIdProvider);
|
||||
final isUser = conv.userId == currentUserId;
|
||||
setState(() {
|
||||
_isMuted = isUser ? conv.userMuted : conv.agentMuted;
|
||||
_isStarred = isUser ? conv.userFavorited : conv.agentFavorited;
|
||||
});
|
||||
}
|
||||
// Suppress push notifications for this conversation while viewing
|
||||
PushNotificationService().activeConversationId = widget.conversationId;
|
||||
// Ensure socket is connected and join room for real-time updates
|
||||
@@ -159,10 +170,12 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
|
||||
);
|
||||
break;
|
||||
case 'mute':
|
||||
setState(() => _isMuted = !_isMuted);
|
||||
final newMuted = !_isMuted;
|
||||
setState(() => _isMuted = newMuted);
|
||||
ref.read(messagingProvider.notifier).toggleMute(widget.conversationId, newMuted);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(_isMuted ? 'Notifications muted' : 'Notifications unmuted'),
|
||||
content: Text(newMuted ? 'Notifications muted' : 'Notifications unmuted'),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
@@ -540,7 +553,11 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
|
||||
),
|
||||
// Star icon
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _isStarred = !_isStarred),
|
||||
onTap: () {
|
||||
final newStarred = !_isStarred;
|
||||
setState(() => _isStarred = newStarred);
|
||||
ref.read(messagingProvider.notifier).toggleFavorite(widget.conversationId, newStarred);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: Icon(
|
||||
|
||||
Reference in New Issue
Block a user