Instantly share code, notes, and snippets.
Last active
August 9, 2024 07:57
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save stevenosse/107390424ede8c60f75e4e316ee15ecc to your computer and use it in GitHub Desktop.
AdaptativeMenuButton (Uses Popup menu button on Android & CupertinoActionSheet on iOS)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/cupertino.dart'; | |
class AdaptivePopupMenu extends StatelessWidget { | |
final List<AdaptiveMenuItem> menuItems; | |
final Widget child; | |
final String? iosCancelButtonLabel; | |
final BorderRadius? androidButtonRadius; | |
final double spacing; | |
final EdgeInsets padding; | |
const AdaptivePopupMenu({ | |
super.key, | |
required this.menuItems, | |
required this.child, | |
this.iosCancelButtonLabel, | |
this.androidButtonRadius, | |
this.spacing = 10.0, | |
this.padding = const EdgeInsets.all(16.0), | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return defaultTargetPlatform == TargetPlatform.iOS | |
? _CupertinoPopupMenu( | |
menuItems: menuItems, | |
iosCancelButtonLabel: iosCancelButtonLabel ?? 'Cancel', | |
padding: padding, | |
spacing: spacing, | |
child: child, | |
) | |
: _MaterialPopupMenu( | |
menuItems: menuItems, | |
padding: padding, | |
spacing: spacing, | |
androidButtonRadius: androidButtonRadius, | |
child: child, | |
); | |
} | |
} | |
class _MaterialPopupMenu extends StatelessWidget { | |
final List<AdaptiveMenuItem> menuItems; | |
final Widget child; | |
final BorderRadius? androidButtonRadius; | |
final double? spacing; | |
final EdgeInsets padding; | |
const _MaterialPopupMenu({ | |
required this.menuItems, | |
required this.child, | |
required this.padding, | |
this.androidButtonRadius, | |
this.spacing, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return PopupMenuButton<String>( | |
shape: RoundedRectangleBorder(borderRadius: androidButtonRadius ?? BorderRadius.circular(12.0)), | |
itemBuilder: (BuildContext context) => menuItems | |
.map((item) => PopupMenuItem<String>( | |
value: item.value, | |
child: _MaterialMenuItem( | |
item: item, | |
spacing: spacing, | |
), | |
)) | |
.toList(), | |
onSelected: (String value) { | |
final selectedItem = menuItems.firstWhere((item) => item.value == value); | |
selectedItem.onTap(); | |
}, | |
child: child, | |
); | |
} | |
} | |
class _MaterialMenuItem extends StatelessWidget { | |
final AdaptiveMenuItem item; | |
final double? spacing; | |
const _MaterialMenuItem({ | |
required this.item, | |
required this.spacing, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return Row( | |
children: [ | |
if (item.icon != null) ...[ | |
IconTheme( | |
data: IconThemeData(color: item.color ?? Theme.of(context).colorScheme.onSurface), | |
child: item.icon!, | |
), | |
SizedBox(width: spacing ?? 10.0), | |
], | |
Text(item.label, style: TextStyle(color: item.color ?? Theme.of(context).colorScheme.onSurface)), | |
], | |
); | |
} | |
} | |
class _CupertinoPopupMenu extends StatelessWidget { | |
final List<AdaptiveMenuItem> menuItems; | |
final Widget child; | |
final String iosCancelButtonLabel; | |
final EdgeInsets padding; | |
final double spacing; | |
const _CupertinoPopupMenu({ | |
required this.menuItems, | |
required this.child, | |
required this.iosCancelButtonLabel, | |
required this.padding, | |
required this.spacing, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return GestureDetector( | |
onTap: () => showCupertinoModalPopup( | |
context: context, | |
builder: (BuildContext context) => _CupertinoActionSheet( | |
menuItems: menuItems, | |
iosCancelButtonLabel: iosCancelButtonLabel, | |
padding: padding, | |
spacing: spacing, | |
), | |
), | |
child: child, | |
); | |
} | |
} | |
class _CupertinoActionSheet extends StatelessWidget { | |
final List<AdaptiveMenuItem> menuItems; | |
final double spacing; | |
final EdgeInsets? padding; | |
final String iosCancelButtonLabel; | |
const _CupertinoActionSheet({ | |
required this.menuItems, | |
required this.iosCancelButtonLabel, | |
this.spacing = 10.0, | |
this.padding, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return CupertinoActionSheet( | |
actions: menuItems | |
.map( | |
(item) => _CupertinoActionSheetItem( | |
item: item, | |
spacing: spacing, | |
padding: padding, | |
), | |
) | |
.toList(), | |
cancelButton: _CupertinoCancelButton(iosCancelButtonLabel: iosCancelButtonLabel), | |
); | |
} | |
} | |
class _CupertinoActionSheetItem extends StatelessWidget { | |
final AdaptiveMenuItem item; | |
final double spacing; | |
final EdgeInsets? padding; | |
const _CupertinoActionSheetItem({ | |
required this.item, | |
required this.spacing, | |
this.padding, | |
}); | |
@override | |
Widget build(BuildContext context) { | |
return CupertinoActionSheetAction( | |
child: Padding( | |
padding: padding ?? const EdgeInsets.symmetric(horizontal: 16.0), | |
child: Row( | |
children: [ | |
Expanded( | |
child: Text( | |
item.label, | |
textAlign: TextAlign.start, | |
style: TextStyle(color: item.color ?? Theme.of(context).colorScheme.onSurface), | |
), | |
), | |
if (item.icon != null) ...[ | |
SizedBox(width: spacing), | |
IconTheme( | |
data: IconThemeData(color: item.color ?? Theme.of(context).colorScheme.onSurface), | |
child: item.icon!, | |
), | |
], | |
], | |
), | |
), | |
onPressed: () { | |
Navigator.pop(context); | |
item.onTap(); | |
}, | |
); | |
} | |
} | |
class _CupertinoCancelButton extends StatelessWidget { | |
const _CupertinoCancelButton({required this.iosCancelButtonLabel}); | |
final String iosCancelButtonLabel; | |
@override | |
Widget build(BuildContext context) { | |
return CupertinoActionSheetAction( | |
child: Text( | |
iosCancelButtonLabel, | |
style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Theme.of(context).colorScheme.onSurface), | |
), | |
onPressed: () => Navigator.pop(context), | |
); | |
} | |
} | |
class AdaptiveMenuItem { | |
final Widget? icon; | |
final String label; | |
final String value; | |
final Color? color; | |
final VoidCallback onTap; | |
AdaptiveMenuItem({ | |
this.icon, | |
required this.label, | |
required this.value, | |
this.color, | |
required this.onTap, | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment