fix(mobile): match section options to existing choice sheets

Reuse the shared sheet header and checked list-card rows for sort, filter and duration. Preserve the single-sheet draft flow and disable the shared close control while saving.

Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
Taylor Ho
2026-10-19 15:27:31 -07:00
parent 00ea385538
commit 038b0148d7
3 changed files with 80 additions and 116 deletions

View File

@@ -25,6 +25,7 @@ import '../../shared/community/paired_community_landing.dart';
import '../../shared/community/community_membership_provider.dart';
import '../../shared/widgets/app_list_card_item.dart';
import '../../shared/widgets/app_list.dart';
import '../../shared/widgets/buzz_sheet_header.dart';
import '../../shared/widgets/app_list_card.dart';
import '../../shared/relay/relay.dart';
import '../../shared/theme/theme.dart';

View File

@@ -132,57 +132,36 @@ class _SectionViewSheet extends HookConsumerWidget {
Map<T, String> values,
T selected,
ValueChanged<T>? change,
) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
) => AppListCard(
label: label,
dividerIndent: Grid.xs,
children: [
Text(label, style: context.textTheme.titleSmall),
const SizedBox(height: Grid.xxs),
SegmentedButton<T>(
segments: [
for (final entry in values.entries)
ButtonSegment(value: entry.key, label: Text(entry.value)),
],
selected: {selected},
showSelectedIcon: false,
direction: MediaQuery.textScalerOf(context).scale(14) > 20
? Axis.vertical
: Axis.horizontal,
expandedInsets: MediaQuery.textScalerOf(context).scale(14) > 20
? null
: EdgeInsets.zero,
style: ButtonStyle(
foregroundColor: WidgetStateProperty.resolveWith(
(states) => states.contains(WidgetState.disabled)
? context.colors.onSurface.withValues(alpha: 0.5)
: states.contains(WidgetState.selected)
? context.colors.onSurface
: context.colors.onSurfaceVariant,
),
backgroundColor: WidgetStateProperty.resolveWith(
(states) => states.contains(WidgetState.selected)
? context.colors.surface
: context.colors.onSurface.withValues(alpha: 0.06),
),
side: WidgetStatePropertyAll(
BorderSide(color: context.colors.outline.withValues(alpha: 0.25)),
),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(Radii.sm),
),
),
padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(horizontal: Grid.xxs, vertical: Grid.twelve),
for (final entry in values.entries)
Semantics(
checked: entry.key == selected,
inMutuallyExclusiveGroup: true,
enabled: change != null,
child: AppListRow(
key: ValueKey('section-choice-$label-${entry.value}'),
title: entry.value,
trailing: entry.key == selected
? Icon(
BuzzIcons.check,
size: 18,
color: context.colors.primary,
)
: null,
titleColor: change == null
? context.colors.onSurfaceVariant
: null,
onTap: change == null
? null
: () {
dirty.value = true;
change(entry.key);
},
),
),
onSelectionChanged: change == null
? null
: (selection) {
dirty.value = true;
change(selection.single);
},
),
const SizedBox(height: Grid.xs),
],
);
return PopScope(
@@ -193,33 +172,20 @@ class _SectionViewSheet extends HookConsumerWidget {
maxHeight: MediaQuery.sizeOf(context).height * 0.85,
),
child: Padding(
padding: const EdgeInsets.fromLTRB(
Grid.gutter,
Grid.xxs,
Grid.gutter,
Grid.twelve,
),
padding: const EdgeInsets.only(bottom: Grid.twelve),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: Text(
title,
style: context.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
),
IconButton(
tooltip: 'Close',
onPressed: saving.value ? null : () => close(),
icon: const Icon(Icons.close),
),
],
BuzzSheetHeader(
title: title,
trailing: saving.value
? const IconButton(
tooltip: 'Saving…',
onPressed: null,
icon: Icon(BuzzIcons.x),
)
: null,
),
const SizedBox(height: Grid.xxs),
Flexible(
child: SingleChildScrollView(
child: Column(
@@ -297,34 +263,37 @@ class _SectionViewSheet extends HookConsumerWidget {
),
),
const SizedBox(height: Grid.xxs),
Align(
alignment: Alignment.centerRight,
child: Wrap(
alignment: WrapAlignment.end,
spacing: Grid.xxs,
runSpacing: Grid.xxs,
children: [
TextButton(
onPressed: saving.value ? null : () => close(),
child: const Text('Cancel'),
),
FilledButton(
style: FilledButton.styleFrom(
backgroundColor: context.colors.onSurface,
foregroundColor: context.colors.surface,
Padding(
padding: const EdgeInsets.symmetric(horizontal: Grid.gutter),
child: Align(
alignment: Alignment.centerRight,
child: Wrap(
alignment: WrapAlignment.end,
spacing: Grid.xxs,
runSpacing: Grid.xxs,
children: [
TextButton(
onPressed: saving.value ? null : () => close(),
child: const Text('Cancel'),
),
onPressed: saving.value || manager?.busy == true
? null
: apply,
child: Text(
saving.value
? 'Saving…'
: failed.value
? 'Retry'
: 'Apply',
FilledButton(
style: FilledButton.styleFrom(
backgroundColor: context.colors.onSurface,
foregroundColor: context.colors.surface,
),
onPressed: saving.value || manager?.busy == true
? null
: apply,
child: Text(
saving.value
? 'Saving…'
: failed.value
? 'Retry'
: 'Apply',
),
),
),
],
],
),
),
),
],

View File

@@ -1,4 +1,4 @@
import 'package:buzz/features/channels/channel_sort/channel_sort_storage.dart';
import 'package:buzz/shared/widgets/app_list.dart';
import 'package:nostr/nostr.dart' as nostr;
import 'package:buzz/features/channels/channel_filter/channel_filter_provider.dart';
import 'package:buzz/features/channels/channel_filter/channel_filter_manager.dart';
@@ -226,13 +226,10 @@ void main() {
await tester.tap(find.byTooltip('Channels options'));
await tester.pumpAndSettle();
expect(find.text('general'), findsNothing);
final all = tester.widget<SegmentedButton<ChannelFilterMode>>(
find.ancestor(
of: find.text('All'),
matching: find.byType(SegmentedButton<ChannelFilterMode>),
),
final all = tester.widget<AppListRow>(
find.ancestor(of: find.text('All'), matching: find.byType(AppListRow)),
);
expect(all.onSelectionChanged, isNull);
expect(all.onTap, isNull);
await tester.tap(find.text('Cancel'));
await tester.pumpAndSettle();
await tester.tap(find.text('Show hidden channels · 1'));
@@ -272,13 +269,13 @@ void main() {
expect(manager.store.windowFor('channels'), 7 * 86400);
await tester.tap(find.byTooltip('Channels options'));
await tester.pumpAndSettle();
final days = tester.widget<SegmentedButton<int>>(
final days = tester.widget<AppListRow>(
find.ancestor(
of: find.text('7 days'),
matching: find.byType(SegmentedButton<int>),
matching: find.byType(AppListRow),
),
);
expect(days.selected, {7});
expect(days.trailing, isA<Icon>());
await tester.tap(find.text('All'));
await tester.tap(find.text('Cancel'));
await tester.pumpAndSettle();
@@ -323,13 +320,13 @@ void main() {
),
findsOneWidget,
);
final selected = tester.widget<SegmentedButton<int>>(
final selected = tester.widget<AppListRow>(
find.ancestor(
of: find.text('7 days'),
matching: find.byType(SegmentedButton<int>),
matching: find.byType(AppListRow),
),
);
expect(selected.selected, {7});
expect(selected.trailing, isA<Icon>());
await tester.tap(find.text('Cancel'));
await tester.pumpAndSettle();
expect(find.byType(AlertDialog), findsNothing);
@@ -475,13 +472,10 @@ void main() {
expect(find.text('A–Z'), findsOneWidget);
expect(find.text('Show'), findsOneWidget);
expect(find.byType(AlertDialog), findsNothing);
final alpha = tester.widget<SegmentedButton<ChannelSortMode>>(
find.ancestor(
of: find.text('A–Z'),
matching: find.byType(SegmentedButton<ChannelSortMode>),
),
final alpha = tester.widget<AppListRow>(
find.ancestor(of: find.text('A–Z'), matching: find.byType(AppListRow)),
);
expect(alpha.selected, {ChannelSortMode.alpha});
expect(alpha.trailing, isA<Icon>());
await tester.tap(find.text('Cancel'));
await tester.pumpAndSettle();