Skip to content

Commit 54f9e22

Browse files
authored
fix multiple users without keypackage ux bug (#453)
* fix multiple users without keypackage ux bug * update * evaluate and fix creation logic
1 parent b5d1231 commit 54f9e22

File tree

2 files changed

+74
-101
lines changed

2 files changed

+74
-101
lines changed

lib/ui/contact_list/group_chat_details_sheet.dart

Lines changed: 50 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
44
import 'package:flutter_svg/svg.dart';
55
import 'package:gap/gap.dart';
66
import 'package:go_router/go_router.dart';
7+
import 'package:logging/logging.dart';
78
import 'package:whitenoise/config/providers/active_account_provider.dart';
89
import 'package:whitenoise/config/providers/group_provider.dart';
910
import 'package:whitenoise/domain/models/contact_model.dart';
@@ -57,13 +58,11 @@ class _GroupChatDetailsSheetState extends ConsumerState<GroupChatDetailsSheet> w
5758
bool _hasGroupImage = false;
5859
bool _isGroupNameValid = false;
5960
bool _isCreatingGroup = false;
60-
bool _hasContactsWithKeyPackage = true;
6161

6262
@override
6363
void initState() {
6464
super.initState();
6565
_groupNameController.addListener(_onGroupNameChanged);
66-
_checkContactsKeyPackages();
6766
}
6867

6968
void _onGroupNameChanged() {
@@ -75,36 +74,12 @@ class _GroupChatDetailsSheetState extends ConsumerState<GroupChatDetailsSheet> w
7574
}
7675
}
7776

78-
Future<void> _checkContactsKeyPackages() async {
79-
try {
80-
final filteredContacts = await _filterContactsByKeyPackage(widget.selectedContacts);
81-
if (!mounted) return;
82-
83-
final contactsWithKeyPackage = filteredContacts['withKeyPackage']!;
84-
85-
if (mounted) {
86-
setState(() {
87-
_hasContactsWithKeyPackage = contactsWithKeyPackage.isNotEmpty;
88-
});
89-
}
90-
} catch (e) {
91-
// If there's an error checking keypackages, assume no contacts have keypackages
92-
if (mounted) {
93-
setState(() {
94-
_hasContactsWithKeyPackage = false;
95-
});
96-
}
97-
}
98-
}
99-
10077
void _createGroupChat() async {
101-
if (!_isGroupNameValid) return;
102-
final groupName = _groupNameController.text.trim();
78+
if (!_isGroupNameValid || !mounted) return;
10379

104-
// Store the ref early to avoid accessing it after disposal
80+
final groupName = _groupNameController.text.trim();
10581
final notifier = ref.read(groupsProvider.notifier);
10682

107-
if (!mounted) return;
10883
setState(() {
10984
_isCreatingGroup = true;
11085
});
@@ -117,13 +92,25 @@ class _GroupChatDetailsSheetState extends ConsumerState<GroupChatDetailsSheet> w
11792
final contactsWithKeyPackage = filteredContacts['withKeyPackage']!;
11893
final contactsWithoutKeyPackage = filteredContacts['withoutKeyPackage']!;
11994

120-
if (contactsWithKeyPackage.isEmpty) {
121-
safeShowErrorToast('No contacts have keypackages available for group creation');
95+
// If less than 2 contacts have keypackages, only show invite sheet (no group creation)
96+
if (contactsWithKeyPackage.length < 2) {
97+
if (contactsWithoutKeyPackage.isNotEmpty && mounted) {
98+
await ShareInviteBottomSheet.show(
99+
context: context,
100+
contacts: contactsWithoutKeyPackage,
101+
);
102+
}
103+
104+
if (mounted) {
105+
context.pop();
106+
}
122107
return;
123108
}
124109

125-
// Create group with contacts that have keypackages - use stored notifier
126-
final groupData = await notifier.createNewGroup(
110+
// Create group with contacts that have keypackages
111+
if (!mounted) return;
112+
113+
final createdGroupData = await notifier.createNewGroup(
127114
groupName: groupName,
128115
groupDescription: '',
129116
memberPublicKeyHexs: contactsWithKeyPackage.map((c) => c.publicKey).toList(),
@@ -132,58 +119,50 @@ class _GroupChatDetailsSheetState extends ConsumerState<GroupChatDetailsSheet> w
132119

133120
if (!mounted) return;
134121

135-
GroupData? successGroupData;
136-
String? errorMessage;
137-
138-
if (groupData != null) {
139-
successGroupData = groupData;
122+
if (createdGroupData != null) {
140123
// Show share invite bottom sheet for members without keypackages
141124
if (contactsWithoutKeyPackage.isNotEmpty && mounted) {
142-
await ShareInviteBottomSheet.show(
143-
context: context,
144-
contacts: contactsWithoutKeyPackage,
145-
);
125+
try {
126+
await ShareInviteBottomSheet.show(
127+
context: context,
128+
contacts: contactsWithoutKeyPackage,
129+
);
130+
} catch (e) {
131+
Logger(
132+
'GroupChatDetailsSheet',
133+
).severe('Error showing share invite bottom sheet: $e');
134+
}
146135
}
147-
} else {
148-
errorMessage = 'Failed to create group chat. Please try again.';
149-
}
150-
151-
// Complete all local operations first
152-
if (mounted) {
153-
setState(() {
154-
_isCreatingGroup = false;
155-
});
156-
}
157136

158-
// Show error if needed
159-
if (errorMessage != null) {
160-
safeShowErrorToast(errorMessage);
161-
}
137+
// Navigate to the created group
138+
if (mounted) {
139+
context.pop();
162140

163-
if (successGroupData != null && mounted) {
164-
// Navigate to home first, then to the group chat
165-
WidgetsBinding.instance.addPostFrameCallback((_) {
166-
if (mounted) {
167-
context.pop();
168-
context.go(Routes.home);
169-
WidgetsBinding.instance.addPostFrameCallback((_) {
141+
WidgetsBinding.instance.addPostFrameCallback((_) async {
142+
if (mounted) {
143+
context.go(Routes.home);
144+
// Small delay to ensure navigation completes
145+
await Future.delayed(const Duration(milliseconds: 150));
170146
if (mounted) {
171-
Routes.goToChat(context, groupData!.mlsGroupId);
147+
Routes.goToChat(context, createdGroupData.mlsGroupId);
172148
}
173-
});
174-
}
175-
});
149+
}
150+
});
151+
}
152+
} else {
153+
safeShowErrorToast('Failed to create group chat. Please try again.');
176154
}
177-
178-
return;
179155
} catch (e) {
156+
if (mounted) {
157+
safeShowErrorToast('Error creating group: ${e.toString()}');
158+
}
159+
} finally {
160+
// Always reset loading state
180161
if (mounted) {
181162
setState(() {
182163
_isCreatingGroup = false;
183164
});
184165
}
185-
safeShowErrorToast('Error creating group: ${e.toString()}');
186-
return;
187166
}
188167
}
189168

@@ -319,10 +298,7 @@ class _GroupChatDetailsSheetState extends ConsumerState<GroupChatDetailsSheet> w
319298
),
320299
),
321300
WnFilledButton(
322-
onPressed:
323-
_isCreatingGroup || !_isGroupNameValid || !_hasContactsWithKeyPackage
324-
? null
325-
: () => _createGroupChat(),
301+
onPressed: _isCreatingGroup || !_isGroupNameValid ? null : _createGroupChat,
326302
loading: _isCreatingGroup,
327303
title: _isCreatingGroup ? 'Creating Group...' : 'Create Group',
328304
),

lib/ui/contact_list/share_invite_bottom_sheet.dart

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -41,29 +41,26 @@ class ShareInviteBottomSheet extends ConsumerStatefulWidget {
4141
class _ShareInviteBottomSheetState extends ConsumerState<ShareInviteBottomSheet> {
4242
@override
4343
Widget build(BuildContext context) {
44+
if (widget.contacts.isEmpty) {
45+
return const SizedBox.shrink();
46+
}
4447
final isSingleContact = widget.contacts.length == 1;
45-
final contact = isSingleContact ? widget.contacts.first : null;
46-
if (contact == null) return const SizedBox.shrink();
48+
final singleContact = widget.contacts.first;
4749

4850
return Column(
4951
mainAxisSize: MainAxisSize.min,
5052
children: [
5153
if (isSingleContact) ...[
52-
// Single contact view
53-
Column(
54-
children: [
55-
Gap(12.h),
56-
UserProfile(
57-
imageUrl: contact.imagePath ?? '',
58-
name: contact.displayName,
59-
nip05: contact.nip05 ?? '',
60-
pubkey: contact.publicKey,
61-
ref: ref,
62-
),
63-
Gap(36.h),
64-
ShareInviteCallout(contact: contact),
65-
],
54+
Gap(12.h),
55+
UserProfile(
56+
imageUrl: singleContact.imagePath ?? '',
57+
name: singleContact.displayName,
58+
nip05: singleContact.nip05 ?? '',
59+
pubkey: singleContact.publicKey,
60+
ref: ref,
6661
),
62+
Gap(36.h),
63+
ShareInviteCallout(contact: singleContact),
6764
] else ...[
6865
// Multiple contacts view
6966
Padding(
@@ -80,19 +77,19 @@ class _ShareInviteBottomSheetState extends ConsumerState<ShareInviteBottomSheet>
8077
],
8178
),
8279
),
83-
// Contacts list
84-
Expanded(
85-
child: ListView.builder(
86-
padding: EdgeInsets.symmetric(horizontal: 24.w),
87-
itemCount: widget.contacts.length,
88-
itemBuilder: (context, index) {
89-
final contact = widget.contacts[index];
90-
return ContactListTile(contact: contact);
91-
},
92-
),
80+
81+
ListView.builder(
82+
padding: EdgeInsets.symmetric(horizontal: 24.w),
83+
shrinkWrap: true,
84+
primary: false,
85+
itemCount: widget.contacts.length,
86+
itemBuilder: (context, index) {
87+
final contact = widget.contacts[index];
88+
return ContactListTile(contact: contact);
89+
},
9390
),
9491
],
95-
Gap(10.h),
92+
Gap(14.h),
9693
const ShareInviteButton(),
9794
],
9895
);

0 commit comments

Comments
 (0)