feat: Implement conversation mute and favorite features with UI integration and state management.

This commit is contained in:
pradeepkumar
2026-03-19 17:03:44 +05:30
parent 32ec839016
commit 858c912df9
4 changed files with 121 additions and 3 deletions

View File

@@ -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) { final messagingRepositoryProvider = Provider<MessagingRepository>((ref) {

View File

@@ -90,6 +90,10 @@ class Conversation {
final int userUnreadCount; final int userUnreadCount;
final int agentUnreadCount; final int agentUnreadCount;
final int unreadCount; final int unreadCount;
final bool userMuted;
final bool agentMuted;
final bool userFavorited;
final bool agentFavorited;
final String createdAt; final String createdAt;
final String updatedAt; final String updatedAt;
final OtherParty otherParty; final OtherParty otherParty;
@@ -104,6 +108,10 @@ class Conversation {
this.userUnreadCount = 0, this.userUnreadCount = 0,
this.agentUnreadCount = 0, this.agentUnreadCount = 0,
this.unreadCount = 0, this.unreadCount = 0,
this.userMuted = false,
this.agentMuted = false,
this.userFavorited = false,
this.agentFavorited = false,
required this.createdAt, required this.createdAt,
required this.updatedAt, required this.updatedAt,
required this.otherParty, required this.otherParty,
@@ -120,6 +128,10 @@ class Conversation {
userUnreadCount: json['userUnreadCount'] as int? ?? 0, userUnreadCount: json['userUnreadCount'] as int? ?? 0,
agentUnreadCount: json['agentUnreadCount'] as int? ?? 0, agentUnreadCount: json['agentUnreadCount'] as int? ?? 0,
unreadCount: json['unreadCount'] 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, createdAt: json['createdAt'] as String,
updatedAt: json['updatedAt'] as String, updatedAt: json['updatedAt'] as String,
otherParty: otherParty:
@@ -134,6 +146,10 @@ class Conversation {
int? unreadCount, int? unreadCount,
String? lastMessageAt, String? lastMessageAt,
String? lastMessageText, String? lastMessageText,
bool? userMuted,
bool? agentMuted,
bool? userFavorited,
bool? agentFavorited,
OtherParty? otherParty, OtherParty? otherParty,
List<ChatMessage>? messages, List<ChatMessage>? messages,
}) { }) {
@@ -146,6 +162,10 @@ class Conversation {
userUnreadCount: userUnreadCount, userUnreadCount: userUnreadCount,
agentUnreadCount: agentUnreadCount, agentUnreadCount: agentUnreadCount,
unreadCount: unreadCount ?? this.unreadCount, unreadCount: unreadCount ?? this.unreadCount,
userMuted: userMuted ?? this.userMuted,
agentMuted: agentMuted ?? this.agentMuted,
userFavorited: userFavorited ?? this.userFavorited,
agentFavorited: agentFavorited ?? this.agentFavorited,
createdAt: createdAt, createdAt: createdAt,
updatedAt: updatedAt, updatedAt: updatedAt,
otherParty: otherParty ?? this.otherParty, otherParty: otherParty ?? this.otherParty,

View File

@@ -569,6 +569,55 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
state = state.copyWith(error: 'Failed to delete conversation'); 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 = final messagingProvider =

View File

@@ -45,6 +45,17 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
notifier.setActiveConversation(widget.conversationId); notifier.setActiveConversation(widget.conversationId);
notifier.loadMessages(widget.conversationId); notifier.loadMessages(widget.conversationId);
notifier.markAsRead(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 // Suppress push notifications for this conversation while viewing
PushNotificationService().activeConversationId = widget.conversationId; PushNotificationService().activeConversationId = widget.conversationId;
// Ensure socket is connected and join room for real-time updates // Ensure socket is connected and join room for real-time updates
@@ -159,10 +170,12 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
); );
break; break;
case 'mute': case 'mute':
setState(() => _isMuted = !_isMuted); final newMuted = !_isMuted;
setState(() => _isMuted = newMuted);
ref.read(messagingProvider.notifier).toggleMute(widget.conversationId, newMuted);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(_isMuted ? 'Notifications muted' : 'Notifications unmuted'), content: Text(newMuted ? 'Notifications muted' : 'Notifications unmuted'),
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
), ),
); );
@@ -540,7 +553,11 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
), ),
// Star icon // Star icon
GestureDetector( GestureDetector(
onTap: () => setState(() => _isStarred = !_isStarred), onTap: () {
final newStarred = !_isStarred;
setState(() => _isStarred = newStarred);
ref.read(messagingProvider.notifier).toggleFavorite(widget.conversationId, newStarred);
},
child: Padding( child: Padding(
padding: const EdgeInsets.only(right: 4), padding: const EdgeInsets.only(right: 4),
child: Icon( child: Icon(