fix(mobile): preserve failed member selections

Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz
This commit is contained in:
Fast Fizz
2026-08-17 17:29:54 +01:00
parent 6810faf909
commit e13e9cfd99
2 changed files with 99 additions and 1 deletions

View File

@@ -69,6 +69,7 @@ class AddChannelMembersSheet extends HookConsumerWidget {
Future<void> addSelectedMembers() async {
if (selectedUsers.value.isEmpty || isSubmitting.value) return;
final submittedUsers = List<DirectoryUser>.of(selectedUsers.value);
isSubmitting.value = true;
submitError.value = null;
try {
@@ -76,10 +77,17 @@ class AddChannelMembersSheet extends HookConsumerWidget {
.read(channelActionsProvider)
.addMembers(
channelId: channelId,
pubkeys: selectedUsers.value.map((user) => user.pubkey).toList(),
pubkeys: submittedUsers.map((user) => user.pubkey).toList(),
);
if (context.mounted) Navigator.of(context).pop(true);
} on AddMembersException catch (error) {
final failedPubkeys = error.failures.keys
.map((pubkey) => pubkey.toLowerCase())
.toSet();
selectedUsers.value = [
for (final user in submittedUsers)
if (failedPubkeys.contains(user.pubkey.toLowerCase())) user,
];
submitError.value = error.message;
} catch (error) {
submitError.value = error.toString();

View File

@@ -1140,6 +1140,96 @@ void main() {
);
});
testWidgets(
'keeps only rejected members selected after a partial failure',
(tester) async {
var attempts = 0;
final submittedPubkeys = <List<String>>[];
await tester.pumpWidget(
_buildTestable(
messages: const [],
members: [
ChannelMember(
pubkey: 'self',
role: 'owner',
joinedAt: DateTime(2025),
),
],
directoryUsers: const [
DirectoryUser(pubkey: 'alice', displayName: 'Alice'),
DirectoryUser(pubkey: 'bob', displayName: 'Bob'),
],
createChannelActions: (ref) => _FakeChannelActions(
ref,
onAddMembers: (_, pubkeys) async {
submittedPubkeys.add(pubkeys);
attempts += 1;
if (attempts == 1) {
throw const AddMembersException({'bob': 'rejected'});
}
},
),
),
);
await tester.pumpAndSettle();
await tester.tap(
find.byKey(const ValueKey('channel-header-settings-trigger')),
);
await tester.pumpAndSettle();
await tester.tap(
find.byKey(const ValueKey('channel-details-add-members-row')),
);
await tester.pumpAndSettle();
await tester.tap(
find.byKey(const ValueKey('add-channel-member-alice')),
);
await tester.pump();
await tester.tap(find.byKey(const ValueKey('add-channel-member-bob')));
await tester.pump();
expect(
find.byKey(const ValueKey('add-channel-member-selected-alice')),
findsOneWidget,
);
expect(
find.byKey(const ValueKey('add-channel-member-selected-bob')),
findsOneWidget,
);
await tester.tap(
find.byKey(const ValueKey('add-channel-members-submit')),
);
await tester.pumpAndSettle();
expect(attempts, 1);
expect(
find.byKey(const ValueKey('add-channel-member-selected-alice')),
findsNothing,
);
expect(
find.byKey(const ValueKey('add-channel-member-selected-bob')),
findsOneWidget,
);
// The successful add must not remain selected for a retry.
expect(
tester
.widgetList<InputChip>(find.byType(InputChip))
.map((chip) => chip.key)
.toList(),
[const ValueKey('add-channel-member-selected-bob')],
);
await tester.tap(
find.byKey(const ValueKey('add-channel-members-submit')),
);
await tester.pumpAndSettle();
expect(attempts, 2);
expect(submittedPubkeys, [
['alice', 'bob'],
['bob'],
]);
},
);
testWidgets('hides composer for archived channels', (tester) async {
final archivedChannel = _testChannel.copyWith(
archivedAt: DateTime.utc(2025, 1, 2),