groups_provider.dart
1 import 'dart:convert'; 2 import 'dart:typed_data'; 3 4 import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 6 import '../services/dead_drop_service.dart'; 7 import 'messages_provider.dart' show bytesToHex, hexToBytes, messagesProvider; 8 import 'service_provider.dart'; 9 10 /// Tracks group IDs that the user intentionally removed (decline/leave) 11 /// so we don't show a "you were removed" toast for user-initiated actions. 12 final userRemovedGroupIdsProvider = StateProvider<Set<String>>((ref) => {}); 13 14 /// Provider for the list of all groups. 15 final groupsProvider = 16 AsyncNotifierProvider<GroupsNotifier, List<GroupInfo>>(GroupsNotifier.new); 17 18 class GroupsNotifier extends AsyncNotifier<List<GroupInfo>> { 19 @override 20 Future<List<GroupInfo>> build() async { 21 final service = ref.watch(deadDropServiceProvider); 22 if (!service.isInitialized) return []; 23 return service.listGroups(); 24 } 25 26 /// Refresh the groups list. 27 Future<void> refresh() async { 28 final service = ref.read(deadDropServiceProvider); 29 if (!service.isInitialized) return; 30 state = AsyncValue.data(service.listGroups()); 31 } 32 33 /// Create a new group. 34 Future<Uint8List> createGroup(String name, List<Uint8List> memberIds) async { 35 final service = ref.read(deadDropServiceProvider); 36 final groupId = await service.createGroup(name, memberIds); 37 await refresh(); 38 return groupId; 39 } 40 41 /// Leave a group. 42 Future<void> leaveGroup(Uint8List groupId) async { 43 final service = ref.read(deadDropServiceProvider); 44 await service.leaveGroup(groupId); 45 await refresh(); 46 } 47 48 /// Rename a group. 49 Future<void> renameGroup(Uint8List groupId, String newName) async { 50 final service = ref.read(deadDropServiceProvider); 51 await service.renameGroup(groupId, newName); 52 await refresh(); 53 } 54 } 55 56 /// Provider for messages in a specific group. 57 /// Uses hex string as key to avoid Uint8List equality issues. 58 final groupMessagesProvider = AsyncNotifierProvider.family< 59 GroupMessagesNotifier, List<MessageInfo>, String>( 60 GroupMessagesNotifier.new, 61 ); 62 63 class GroupMessagesNotifier 64 extends FamilyAsyncNotifier<List<MessageInfo>, String> { 65 @override 66 Future<List<MessageInfo>> build(String groupIdHex) async { 67 final service = ref.watch(deadDropServiceProvider); 68 if (!service.isInitialized) return []; 69 return _fetchMessages(service, groupIdHex); 70 } 71 72 List<MessageInfo> _fetchMessages( 73 DeadDropService service, 74 String groupIdHex, 75 ) { 76 final groupId = hexToBytes(groupIdHex); 77 return service.getGroupConversation(groupId); 78 } 79 80 Future<void> _refreshState() async { 81 final service = ref.read(deadDropServiceProvider); 82 if (!service.isInitialized) return; 83 state = AsyncValue.data(_fetchMessages(service, arg)); 84 } 85 86 /// Send a text message to the group. 87 Future<void> sendText(String text) async { 88 final service = ref.read(deadDropServiceProvider); 89 final groupId = hexToBytes(arg); 90 91 await service.sendGroupMessage( 92 groupId, 93 utf8.encode(text), 94 ContentType.text, 95 ); 96 97 await _refreshState(); 98 _deliverToMembers(groupId); 99 } 100 101 /// Send a document/file to the group. 102 Future<void> sendDocument(Uint8List content, String filename) async { 103 final service = ref.read(deadDropServiceProvider); 104 final groupId = hexToBytes(arg); 105 106 await service.sendGroupDocument(groupId, content, filename); 107 108 await _refreshState(); 109 _deliverToMembers(groupId); 110 } 111 112 /// Trigger transport delivery for all group members. 113 void _deliverToMembers(Uint8List groupId) { 114 final service = ref.read(deadDropServiceProvider); 115 final members = service.getGroupMembers(groupId); 116 for (final m in members) { 117 final hex = bytesToHex(m.contactId); 118 ref.read(messagesProvider(hex).notifier).retryDelivery(); 119 } 120 } 121 122 /// Refresh the conversation. 123 Future<void> refresh() async { 124 await _refreshState(); 125 } 126 } 127 128 /// Provider for group members. 129 final groupMembersProvider = 130 Provider.family<List<GroupMemberInfoDto>, String>((ref, groupIdHex) { 131 final service = ref.watch(deadDropServiceProvider); 132 if (!service.isInitialized) return []; 133 final groupId = hexToBytes(groupIdHex); 134 return service.getGroupMembers(groupId); 135 });