Skip to content

Commit

Permalink
Improve ios choice picker with small number of choices
Browse files Browse the repository at this point in the history
  • Loading branch information
veloce committed Apr 16, 2024
1 parent d283e3b commit ac570f9
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 39 deletions.
88 changes: 57 additions & 31 deletions lib/src/widgets/adaptive_choice_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,39 +43,65 @@ Future<void> showChoicePicker<T>(
},
);
case TargetPlatform.iOS:
return showCupertinoModalPopup<void>(
context: context,
builder: (context) {
return NotificationListener(
onNotification: (ScrollEndNotification notification) {
if (onSelectedItemChanged != null) {
final index =
(notification.metrics as FixedExtentMetrics).itemIndex;
onSelectedItemChanged(choices[index]);
}
return false;
},
child: SizedBox(
height: 250,
child: CupertinoPicker(
backgroundColor: Theme.of(context).canvasColor,
useMagnifier: true,
magnification: 1.1,
itemExtent: 40,
scrollController: FixedExtentScrollController(
initialItem: choices.indexWhere((t) => t == selectedItem),
if (choices.length <= 6) {
return showCupertinoModalPopup<void>(
context: context,
builder: (context) {
return CupertinoActionSheet(
actions: choices.map((value) {
return CupertinoActionSheetAction(
onPressed: () {
if (onSelectedItemChanged != null) {
onSelectedItemChanged(value);
}
Navigator.of(context).pop();
},
child: labelBuilder(value),
);
}).toList(),
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () => Navigator.of(context).pop(),
child: Text(context.l10n.cancel),
),
);
},
);
} else {
return showCupertinoModalPopup<void>(
context: context,
builder: (context) {
return NotificationListener(
onNotification: (ScrollEndNotification notification) {
if (onSelectedItemChanged != null) {
final index =
(notification.metrics as FixedExtentMetrics).itemIndex;
onSelectedItemChanged(choices[index]);
}
return false;
},
child: SizedBox(
height: 250,
child: CupertinoPicker(
backgroundColor: Theme.of(context).canvasColor,
useMagnifier: true,
magnification: 1.1,
itemExtent: 40,
scrollController: FixedExtentScrollController(
initialItem: choices.indexWhere((t) => t == selectedItem),
),
children: choices.map((value) {
return Center(
child: labelBuilder(value),
);
}).toList(),
onSelectedItemChanged: (_) {},
),
children: choices.map((value) {
return Center(
child: labelBuilder(value),
);
}).toList(),
onSelectedItemChanged: (_) {},
),
),
);
},
);
);
},
);
}
default:
throw Exception('Unexpected platform $Theme.of(context).platform');
}
Expand Down
62 changes: 54 additions & 8 deletions test/widgets/adaptive_choice_picker_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import 'package:lichess_mobile/src/widgets/adaptive_choice_picker.dart';

import '../test_utils.dart';

enum TestEnum { one, two, three }
enum TestEnumLarge { one, two, three, four, five, six, seven, eight, nine, ten }

enum TestEnumSmall { one, two, three }

void main() {
testWidgets(
'showChoicePicker call onSelectedItemChanged',
'showChoicePicker call onSelectedItemChanged (large choices)',
(WidgetTester tester) async {
final List<TestEnum> selectedItems = <TestEnum>[];
final List<TestEnumLarge> selectedItems = <TestEnumLarge>[];

await tester.pumpWidget(
MaterialApp(
Expand All @@ -26,8 +28,8 @@ void main() {
onPressed: () {
showChoicePicker(
context,
choices: TestEnum.values,
selectedItem: TestEnum.one,
choices: TestEnumLarge.values,
selectedItem: TestEnumLarge.one,
labelBuilder: (choice) => Text(choice.name),
onSelectedItemChanged: (choice) {
selectedItems.add(choice);
Expand All @@ -45,22 +47,66 @@ void main() {
await tester.tap(find.text('Show picker'));
await tester.pumpAndSettle();

// with large choices (>= 6), on iOS the picker scrolls
if (debugDefaultTargetPlatformOverride == TargetPlatform.iOS) {
// scroll 2 items (2 * 40 height)
await tester.drag(
find.text('one'),
const Offset(0.0, -80.0),
warnIfMissed: false,
); // has an IgnorePointer
expect(selectedItems, <TestEnum>[]);
expect(selectedItems, <TestEnumLarge>[]);
await tester.pumpAndSettle(); // await for scroll ends
// only third item is selected as the scroll ends
expect(selectedItems, [TestEnum.three]);
expect(selectedItems, [TestEnumLarge.three]);
} else {
await tester.tap(find.text('three'));
expect(selectedItems, [TestEnum.three]);
expect(selectedItems, [TestEnumLarge.three]);
}
},
variant: kPlatformVariant,
);

testWidgets(
'showChoicePicker call onSelectedItemChanged (small choices)',
(WidgetTester tester) async {
final List<TestEnumSmall> selectedItems = <TestEnumSmall>[];

await tester.pumpWidget(
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
home: Scaffold(
body: Builder(
builder: (context) {
return Center(
child: ElevatedButton(
child: const Text('Show picker'),
onPressed: () {
showChoicePicker(
context,
choices: TestEnumSmall.values,
selectedItem: TestEnumSmall.one,
labelBuilder: (choice) => Text(choice.name),
onSelectedItemChanged: (choice) {
selectedItems.add(choice);
},
);
},
),
);
},
),
),
),
);

await tester.tap(find.text('Show picker'));
await tester.pumpAndSettle();

// With small choices, on iOS the picker is an action sheet
await tester.tap(find.text('three'));
expect(selectedItems, [TestEnumSmall.three]);
},
variant: kPlatformVariant,
);
}

0 comments on commit ac570f9

Please sign in to comment.