Skip to content

Instantly share code, notes, and snippets.

@kumamotone
Last active March 1, 2024 03:01
Show Gist options
  • Save kumamotone/84195a839113d60152000e3c0e936fc4 to your computer and use it in GitHub Desktop.
Save kumamotone/84195a839113d60152000e3c0e936fc4 to your computer and use it in GitHub Desktop.
showModalPickerSheet
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dialog Demo',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: const ModalPickerSheetCatalogScreen(),
);
}
}
class ModalPickerSheetCatalogScreen extends StatelessWidget {
const ModalPickerSheetCatalogScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Modal Picker Sheet Demo'),
),
body: ListView(
children: <Widget>[
_buildPickerButton(
context,
'オプション1つ',
[
const PickerOption(
key: 'destructive',
label: '退出する',
isDestructiveAction: true,
),
],
),
_buildPickerButton(
context,
'カメラ/ライブラリ',
[
const PickerOption(
key: 'Camera',
label: 'カメラで撮影する',
),
const PickerOption(
key: 'Library',
label: 'ライブラリから選択する',
),
],
),
_buildPickerButton(context, '編集/削除', [
const PickerOption(
key: 'edit',
label: '編集する',
),
const PickerOption(
key: 'delete',
label: '削除する',
isDestructiveAction: true,
),
]),
_buildPickerButton(
context,
'シェアメニュー',
[
const PickerOption(
key: 'X',
label: 'X(Twitter) でシェア',
),
const PickerOption(
key: 'Facebook',
label: 'Facebook でシェア',
),
const PickerOption(
key: 'Other',
label: 'その他の方法でシェア',
),
const PickerOption(
key: 'Copy',
label: 'コピー',
),
],
),
_buildPickerButton(
context,
'ヘッダーあり',
[
const PickerOption(
key: 'destructive',
label: '変更を破棄する',
isDestructiveAction: true,
),
],
title: '保存していない変更があります',
subTitle: '変更内容を破棄してもよろしいですか?',
),
_buildPickerButton(
context,
'全部入り',
[
const PickerOption(
key: 'Default',
label: 'デフォルトのアクション',
caption: '注釈1',
isDefaultAction: true,
),
const PickerOption(
key: 'Normal',
label: '普通のアクション',
caption: '注釈2',
),
const PickerOption(
key: 'Destructive',
label: '破棄のアクション',
caption: '注釈3',
isDestructiveAction: true,
),
],
title: 'タイトル',
subTitle: 'サブタイトル',
),
],
),
);
}
Widget _buildPickerButton(
BuildContext context,
String buttonText,
List<PickerOption<dynamic>> options, {
String? title,
String? subTitle,
}) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
child: ElevatedButton(
onPressed: () async {
final result = await showModalPickerSheet(
context: context,
options: options,
headerTitle: title,
headerMessage: subTitle,
cancelLabel: 'キャンセル',
);
if (result != null && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('選択: $result'),
duration: const Duration(milliseconds: 500),
),
);
}
},
child: Text(buttonText),
),
);
}
}
Future<T?> showModalPickerSheet<T>({
required BuildContext context,
required List<PickerOption<T>> options,
String? headerTitle,
String? headerMessage,
String? cancelLabel,
}) {
final navigator = Navigator.of(
context,
rootNavigator: true,
);
return showModalBottomSheet<T>(
context: context,
backgroundColor: Colors.transparent,
builder: (context) => _ModalPickerSheet<T>(
title: headerTitle,
message: headerMessage,
options: options,
onOptionPressed: navigator.pop,
cancelLabel: cancelLabel,
onCancelPressed: cancelLabel != null ? navigator.pop : null,
),
);
}
class _ModalPickerSheet<T> extends StatelessWidget {
const _ModalPickerSheet({
required this.options,
required this.onOptionPressed,
this.title,
this.message,
this.cancelLabel,
this.onCancelPressed,
});
final String? title;
final String? message;
final List<PickerOption<T>> options;
final ValueSetter<T?> onOptionPressed;
final String? cancelLabel;
final VoidCallback? onCancelPressed;
List<Widget> buildHeader({
required BuildContext context,
String? title,
String? subTitle,
}) {
if (title == null && subTitle == null) {
return [const SizedBox.shrink()];
} else {
return [
ListTile(
title: title != null ? Text(title) : null,
subtitle: subTitle != null
? Text(
subTitle,
style: Theme.of(context).textTheme.bodySmall,
)
: null,
),
const Divider(height: 1),
];
}
}
List<Widget> buildOptionTiles({
required BuildContext context,
required List<PickerOption<T>> options,
required ValueSetter<T?> onOptionPressed,
}) {
return options.map<Widget>((option) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: Text(
option.label,
style: TextStyle(
color: option.isDestructiveAction
? Theme.of(context).colorScheme.error
: null,
fontWeight: option.isDefaultAction ? FontWeight.bold : null,
),
),
subtitle: option.caption != null
? Text(
option.caption!,
style: Theme.of(context).textTheme.bodySmall,
)
: null,
onTap: () => onOptionPressed(option.key),
),
const Divider(height: 1),
],
);
}).toList();
}
List<Widget> buildCancelTile({
String? cancelLabel,
VoidCallback? onCancelPressed,
}) {
if (cancelLabel == null) {
return [const SizedBox.shrink()];
} else {
return [
const Divider(height: 1),
ListTile(
title: Text(cancelLabel),
onTap: onCancelPressed,
),
];
}
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(8),
child: SingleChildScrollView(
child: Material(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
color: Theme.of(context).bottomSheetTheme.backgroundColor ??
Theme.of(context).canvasColor,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
...buildHeader(
context: context,
title: title,
subTitle: message,
),
...buildOptionTiles(
context: context,
options: options,
onOptionPressed: onOptionPressed,
),
...buildCancelTile(
cancelLabel: cancelLabel,
onCancelPressed: onCancelPressed,
),
],
),
),
),
),
);
}
}
class PickerOption<T> {
const PickerOption({
required this.label,
this.caption,
this.key,
this.isDefaultAction = false,
this.isDestructiveAction = false,
});
final String label;
final String? caption;
final T? key;
final bool isDefaultAction;
final bool isDestructiveAction;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment