fix(mobile): apply approved channel accessibility guards

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 16:35:44 +01:00
parent 19ede787df
commit d34728722c
5 changed files with 88 additions and 50 deletions

View File

@@ -158,37 +158,44 @@ class AddChannelMembersSheet extends HookConsumerWidget {
final selected = selectedPubkeys.contains(
user.pubkey.toLowerCase(),
);
return ListTile(
return Semantics(
key: ValueKey(
'add-channel-member-${user.pubkey}',
),
contentPadding: EdgeInsets.zero,
leading: AvatarImage(
imageUrl: user.avatarUrl,
radius: 20,
backgroundColor:
context.colors.primaryContainer,
fallback: Text(user.initial),
button: true,
selected: selected,
label: selected
? '${user.label}, selected'
: user.label,
child: ListTile(
contentPadding: EdgeInsets.zero,
leading: AvatarImage(
imageUrl: user.avatarUrl,
radius: 20,
backgroundColor:
context.colors.primaryContainer,
fallback: Text(user.initial),
),
title: Text(
user.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
user.secondaryLabel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: Icon(
selected
? LucideIcons.circleCheck
: LucideIcons.plus,
color: selected
? context.colors.primary
: context.colors.onSurfaceVariant,
),
onTap: () => toggleUser(user),
),
title: Text(
user.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
user.secondaryLabel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: Icon(
selected
? LucideIcons.circleCheck
: LucideIcons.plus,
color: selected
? context.colors.primary
: context.colors.onSurfaceVariant,
),
onTap: () => toggleUser(user),
);
},
),

View File

@@ -220,7 +220,11 @@ class ChannelActionsSheet extends ConsumerWidget {
maxWidth: 640,
maxHeight: MediaQuery.sizeOf(context).height * 0.9,
),
builder: (_) => ManageChannelSheet(channel: channel),
builder: (_) => ManageChannelSheet(
channel: channel,
canEditDetails:
canManageLifecycle && !channel.isArchived,
),
);
if (shouldClose == true && context.mounted) {
Navigator.of(context).pop(true);

View File

@@ -197,6 +197,7 @@ class ChannelDetailsPage extends HookConsumerWidget {
),
builder: (_) => ManageChannelSheet(
channel: resolvedChannel,
canEditDetails: canEdit,
onChannelUpdated: (updated) => displayedChannel.value = updated,
),
);

View File

@@ -11,10 +11,12 @@ class ManageChannelSheet extends HookConsumerWidget {
const ManageChannelSheet({
super.key,
required this.channel,
this.canEditDetails = true,
this.onChannelUpdated,
});
final Channel channel;
final bool canEditDetails;
final ValueChanged<Channel>? onChannelUpdated;
@override
@@ -48,6 +50,7 @@ class ManageChannelSheet extends HookConsumerWidget {
final nameDirty = canonicalName != channel.name.trim();
final descriptionDirty = description != channel.description.trim();
final canSaveDetails =
canEditDetails &&
canonicalName.isNotEmpty &&
(nameDirty || descriptionDirty) &&
!isSavingDetails.value;
@@ -113,7 +116,7 @@ class ManageChannelSheet extends HookConsumerWidget {
fieldKey: const ValueKey('manage-channel-name'),
outlineKey: const ValueKey('manage-channel-name-outline'),
controller: nameController,
enabled: !isSavingDetails.value,
enabled: canEditDetails && !isSavingDetails.value,
hintText: 'Channel name',
textInputAction: TextInputAction.next,
),
@@ -122,7 +125,7 @@ class ManageChannelSheet extends HookConsumerWidget {
fieldKey: const ValueKey('manage-channel-description'),
outlineKey: const ValueKey('manage-channel-description-outline'),
controller: descriptionController,
enabled: !isSavingDetails.value,
enabled: canEditDetails && !isSavingDetails.value,
hintText: 'Description',
minLines: 2,
maxLines: 4,
@@ -284,28 +287,32 @@ class _ManageChannelTextField extends StatelessWidget {
),
borderRadius: BorderRadius.circular(Radii.card),
),
child: TextField(
key: fieldKey,
controller: controller,
enabled: enabled,
minLines: minLines,
maxLines: maxLines,
style: context.textTheme.bodyLarge,
decoration: InputDecoration(
hintText: hintText,
hintStyle: context.textTheme.bodyLarge?.copyWith(
color: context.colors.onSurfaceVariant,
),
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
isDense: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: Grid.xs,
vertical: Grid.twelve,
child: Semantics(
label: hintText,
textField: true,
child: TextField(
key: fieldKey,
controller: controller,
enabled: enabled,
minLines: minLines,
maxLines: maxLines,
style: context.textTheme.bodyLarge,
decoration: InputDecoration(
hintText: hintText,
hintStyle: context.textTheme.bodyLarge?.copyWith(
color: context.colors.onSurfaceVariant,
),
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
isDense: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: Grid.xs,
vertical: Grid.twelve,
),
),
textInputAction: textInputAction,
),
textInputAction: textInputAction,
),
);
}

View File

@@ -1107,8 +1107,27 @@ void main() {
);
await tester.pumpAndSettle();
await tester.pump();
final unselectedSemantics = tester.getSemantics(
find.byKey(const ValueKey('add-channel-member-alice')),
);
expect(unselectedSemantics.flagsCollection.isButton, isTrue);
expect(
unselectedSemantics.flagsCollection.isSelected.toString(),
'Tristate.isFalse',
);
await tester.tap(find.byKey(const ValueKey('add-channel-member-alice')));
await tester.pump();
final selectedSemantics = tester.getSemantics(
find.byKey(const ValueKey('add-channel-member-alice')),
);
expect(selectedSemantics.flagsCollection.isButton, isTrue);
expect(
selectedSemantics.flagsCollection.isSelected.toString(),
'Tristate.isTrue',
);
expect(selectedSemantics.label, 'Alice, selected');
await tester.tap(
find.byKey(const ValueKey('add-channel-members-submit')),
);