/ app / lib / providers / groups_provider.dart
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  });