* refact: init values from initState to Constractor Signed-off-by: dignow <linlong1265@gmail.com> * fix: move RxBool init into Constructor Signed-off-by: dignow <linlong1265@gmail.com> * peer sort option Signed-off-by: dignow <linlong1265@gmail.com> * Remove empty initState() Signed-off-by: dignow <linlong1265@gmail.com> --------- Signed-off-by: dignow <linlong1265@gmail.com>
		
			
				
	
	
		
			756 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			756 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'dart:core';
 | |
| 
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:get/get.dart';
 | |
| 
 | |
| import '../../common.dart';
 | |
| import './material_mod_popup_menu.dart' as mod_menu;
 | |
| 
 | |
| // https://stackoverflow.com/questions/68318314/flutter-popup-menu-inside-popup-menu
 | |
| class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> {
 | |
|   const PopupMenuChildrenItem({
 | |
|     key,
 | |
|     this.height = kMinInteractiveDimension,
 | |
|     this.padding,
 | |
|     this.enabled,
 | |
|     this.textStyle,
 | |
|     this.onTap,
 | |
|     this.position = mod_menu.PopupMenuPosition.overSide,
 | |
|     this.offset = Offset.zero,
 | |
|     required this.itemBuilder,
 | |
|     required this.child,
 | |
|   }) : super(key: key);
 | |
| 
 | |
|   final mod_menu.PopupMenuPosition position;
 | |
|   final Offset offset;
 | |
|   final TextStyle? textStyle;
 | |
|   final EdgeInsets? padding;
 | |
|   final RxBool? enabled;
 | |
|   final void Function()? onTap;
 | |
|   final List<mod_menu.PopupMenuEntry<T>> Function(BuildContext) itemBuilder;
 | |
|   final Widget child;
 | |
| 
 | |
|   @override
 | |
|   final double height;
 | |
| 
 | |
|   @override
 | |
|   bool represents(T? value) => false;
 | |
| 
 | |
|   @override
 | |
|   MyPopupMenuItemState<T, PopupMenuChildrenItem<T>> createState() =>
 | |
|       MyPopupMenuItemState<T, PopupMenuChildrenItem<T>>(enabled?.value);
 | |
| }
 | |
| 
 | |
| class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
 | |
|     extends State<W> {
 | |
|   RxBool enabled = true.obs;
 | |
| 
 | |
|   MyPopupMenuItemState(bool? e) {
 | |
|     if (e != null) {
 | |
|       enabled.value = e;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   @protected
 | |
|   void handleTap(T value) {
 | |
|     widget.onTap?.call();
 | |
|     Navigator.pop<T>(context, value);
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     final ThemeData theme = Theme.of(context);
 | |
|     final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
 | |
|     TextStyle style = widget.textStyle ??
 | |
|         popupMenuTheme.textStyle ??
 | |
|         theme.textTheme.titleMedium!;
 | |
|     return Obx(() => mod_menu.PopupMenuButton<T>(
 | |
|           enabled: enabled.value,
 | |
|           position: widget.position,
 | |
|           offset: widget.offset,
 | |
|           onSelected: handleTap,
 | |
|           itemBuilder: widget.itemBuilder,
 | |
|           padding: EdgeInsets.zero,
 | |
|           child: AnimatedDefaultTextStyle(
 | |
|             style: style,
 | |
|             duration: kThemeChangeDuration,
 | |
|             child: Container(
 | |
|               alignment: AlignmentDirectional.centerStart,
 | |
|               constraints: BoxConstraints(
 | |
|                   minHeight: widget.height, maxHeight: widget.height),
 | |
|               padding:
 | |
|                   widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
 | |
|               child: widget.child,
 | |
|             ),
 | |
|           ),
 | |
|         ));
 | |
|   }
 | |
| }
 | |
| 
 | |
| class MenuConfig {
 | |
|   // adapt to the screen height
 | |
|   static const fontSize = 14.0;
 | |
|   static const midPadding = 10.0;
 | |
|   static const iconScale = 0.8;
 | |
|   static const iconWidth = 12.0;
 | |
|   static const iconHeight = 12.0;
 | |
| 
 | |
|   final double height;
 | |
|   final double dividerHeight;
 | |
|   final double? boxWidth;
 | |
|   final Color commonColor;
 | |
| 
 | |
|   const MenuConfig(
 | |
|       {required this.commonColor,
 | |
|       this.height = kMinInteractiveDimension,
 | |
|       this.dividerHeight = 16.0,
 | |
|       this.boxWidth});
 | |
| }
 | |
| 
 | |
| typedef DismissCallback = Function();
 | |
| 
 | |
| abstract class MenuEntryBase<T> {
 | |
|   bool dismissOnClicked;
 | |
|   DismissCallback? dismissCallback;
 | |
|   RxBool? enabled;
 | |
| 
 | |
|   MenuEntryBase({
 | |
|     this.dismissOnClicked = false,
 | |
|     this.enabled,
 | |
|     this.dismissCallback,
 | |
|   });
 | |
|   List<mod_menu.PopupMenuEntry<T>> build(BuildContext context, MenuConfig conf);
 | |
| 
 | |
|   enabledStyle(BuildContext context) => TextStyle(
 | |
|       color: Theme.of(context).textTheme.titleLarge?.color,
 | |
|       fontSize: MenuConfig.fontSize,
 | |
|       fontWeight: FontWeight.normal);
 | |
|   disabledStyle() => TextStyle(
 | |
|       color: Colors.grey,
 | |
|       fontSize: MenuConfig.fontSize,
 | |
|       fontWeight: FontWeight.normal);
 | |
| }
 | |
| 
 | |
| class MenuEntryDivider<T> extends MenuEntryBase<T> {
 | |
|   @override
 | |
|   List<mod_menu.PopupMenuEntry<T>> build(
 | |
|       BuildContext context, MenuConfig conf) {
 | |
|     return [
 | |
|       mod_menu.PopupMenuDivider(
 | |
|         height: conf.dividerHeight,
 | |
|       )
 | |
|     ];
 | |
|   }
 | |
| }
 | |
| 
 | |
| class MenuEntryRadioOption {
 | |
|   String text;
 | |
|   String value;
 | |
|   bool dismissOnClicked;
 | |
|   RxBool? enabled;
 | |
|   DismissCallback? dismissCallback;
 | |
| 
 | |
|   MenuEntryRadioOption({
 | |
|     required this.text,
 | |
|     required this.value,
 | |
|     this.dismissOnClicked = false,
 | |
|     this.enabled,
 | |
|     this.dismissCallback,
 | |
|   });
 | |
| }
 | |
| 
 | |
| typedef RadioOptionsGetter = List<MenuEntryRadioOption> Function();
 | |
| typedef RadioCurOptionGetter = Future<String> Function();
 | |
| typedef RadioOptionSetter = Future<void> Function(
 | |
|     String oldValue, String newValue);
 | |
| 
 | |
| class MenuEntryRadioUtils<T> {}
 | |
| 
 | |
| class MenuEntryRadios<T> extends MenuEntryBase<T> {
 | |
|   final String text;
 | |
|   final RadioOptionsGetter optionsGetter;
 | |
|   final RadioCurOptionGetter curOptionGetter;
 | |
|   final RadioOptionSetter optionSetter;
 | |
|   final RxString _curOption = "".obs;
 | |
|   final EdgeInsets? padding;
 | |
| 
 | |
|   MenuEntryRadios({
 | |
|     required this.text,
 | |
|     required this.optionsGetter,
 | |
|     required this.curOptionGetter,
 | |
|     required this.optionSetter,
 | |
|     this.padding,
 | |
|     dismissOnClicked = false,
 | |
|     dismissCallback,
 | |
|     RxBool? enabled,
 | |
|   }) : super(
 | |
|           dismissOnClicked: dismissOnClicked,
 | |
|           enabled: enabled,
 | |
|           dismissCallback: dismissCallback,
 | |
|         ) {
 | |
|     () async {
 | |
|       _curOption.value = await curOptionGetter();
 | |
|     }();
 | |
|   }
 | |
| 
 | |
|   List<MenuEntryRadioOption> get options => optionsGetter();
 | |
|   RxString get curOption => _curOption;
 | |
|   setOption(String option) async {
 | |
|     await optionSetter(_curOption.value, option);
 | |
|     if (_curOption.value != option) {
 | |
|       final opt = await curOptionGetter();
 | |
|       if (_curOption.value != opt) {
 | |
|         _curOption.value = opt;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mod_menu.PopupMenuEntry<T> _buildMenuItem(
 | |
|       BuildContext context, MenuConfig conf, MenuEntryRadioOption opt) {
 | |
|     Widget getTextChild() {
 | |
|       final enabledTextChild = Text(
 | |
|         opt.text,
 | |
|         style: enabledStyle(context),
 | |
|       );
 | |
|       final disabledTextChild = Text(
 | |
|         opt.text,
 | |
|         style: disabledStyle(),
 | |
|       );
 | |
|       if (opt.enabled == null) {
 | |
|         return enabledTextChild;
 | |
|       } else {
 | |
|         return Obx(
 | |
|             () => opt.enabled!.isTrue ? enabledTextChild : disabledTextChild);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     final child = Container(
 | |
|       padding: padding,
 | |
|       alignment: AlignmentDirectional.centerStart,
 | |
|       constraints:
 | |
|           BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
 | |
|       child: Row(
 | |
|         children: [
 | |
|           getTextChild(),
 | |
|           Expanded(
 | |
|               child: Align(
 | |
|                   alignment: Alignment.centerRight,
 | |
|                   child: Transform.scale(
 | |
|                     scale: MenuConfig.iconScale,
 | |
|                     child: Obx(() => opt.value == curOption.value
 | |
|                         ? IconButton(
 | |
|                             padding:
 | |
|                                 const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.0),
 | |
|                             hoverColor: Colors.transparent,
 | |
|                             focusColor: Colors.transparent,
 | |
|                             onPressed: () {},
 | |
|                             icon: Icon(
 | |
|                               Icons.check,
 | |
|                               color: (opt.enabled ?? true.obs).isTrue
 | |
|                                   ? conf.commonColor
 | |
|                                   : Colors.grey,
 | |
|                             ))
 | |
|                         : const SizedBox.shrink()),
 | |
|                   ))),
 | |
|         ],
 | |
|       ),
 | |
|     );
 | |
|     onPressed() {
 | |
|       if (opt.dismissOnClicked && Navigator.canPop(context)) {
 | |
|         Navigator.pop(context);
 | |
|         if (opt.dismissCallback != null) {
 | |
|           opt.dismissCallback!();
 | |
|         }
 | |
|       }
 | |
|       setOption(opt.value);
 | |
|     }
 | |
| 
 | |
|     return mod_menu.PopupMenuItem(
 | |
|       padding: EdgeInsets.zero,
 | |
|       height: conf.height,
 | |
|       child: Container(
 | |
|         width: conf.boxWidth,
 | |
|         child: opt.enabled == null
 | |
|             ? TextButton(
 | |
|                 child: child,
 | |
|                 onPressed: onPressed,
 | |
|               )
 | |
|             : Obx(() => TextButton(
 | |
|                   child: child,
 | |
|                   onPressed: opt.enabled!.isTrue ? onPressed : null,
 | |
|                 )),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   List<mod_menu.PopupMenuEntry<T>> build(
 | |
|       BuildContext context, MenuConfig conf) {
 | |
|     return options.map((opt) => _buildMenuItem(context, conf, opt)).toList();
 | |
|   }
 | |
| }
 | |
| 
 | |
| class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
 | |
|   final String text;
 | |
|   final RadioOptionsGetter optionsGetter;
 | |
|   final RadioCurOptionGetter curOptionGetter;
 | |
|   final RadioOptionSetter optionSetter;
 | |
|   final RxString _curOption = "".obs;
 | |
|   final EdgeInsets? padding;
 | |
| 
 | |
|   MenuEntrySubRadios({
 | |
|     required this.text,
 | |
|     required this.optionsGetter,
 | |
|     required this.curOptionGetter,
 | |
|     required this.optionSetter,
 | |
|     this.padding,
 | |
|     dismissOnClicked = false,
 | |
|     RxBool? enabled,
 | |
|   }) : super(
 | |
|           dismissOnClicked: dismissOnClicked,
 | |
|           enabled: enabled,
 | |
|         ) {
 | |
|     () async {
 | |
|       _curOption.value = await curOptionGetter();
 | |
|     }();
 | |
|   }
 | |
| 
 | |
|   List<MenuEntryRadioOption> get options => optionsGetter();
 | |
|   RxString get curOption => _curOption;
 | |
|   setOption(String option) async {
 | |
|     await optionSetter(_curOption.value, option);
 | |
|     if (_curOption.value != option) {
 | |
|       final opt = await curOptionGetter();
 | |
|       if (_curOption.value != opt) {
 | |
|         _curOption.value = opt;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mod_menu.PopupMenuEntry<T> _buildSecondMenu(
 | |
|       BuildContext context, MenuConfig conf, MenuEntryRadioOption opt) {
 | |
|     return mod_menu.PopupMenuItem(
 | |
|       padding: EdgeInsets.zero,
 | |
|       height: conf.height,
 | |
|       child: Container(
 | |
|           width: conf.boxWidth,
 | |
|           child: TextButton(
 | |
|             child: Container(
 | |
|               padding: padding,
 | |
|               alignment: AlignmentDirectional.centerStart,
 | |
|               constraints: BoxConstraints(
 | |
|                   minHeight: conf.height, maxHeight: conf.height),
 | |
|               child: Row(
 | |
|                 children: [
 | |
|                   Text(
 | |
|                     opt.text,
 | |
|                     style: TextStyle(
 | |
|                         color: Theme.of(context).textTheme.titleLarge?.color,
 | |
|                         fontSize: MenuConfig.fontSize,
 | |
|                         fontWeight: FontWeight.normal),
 | |
|                   ),
 | |
|                   Expanded(
 | |
|                       child: Align(
 | |
|                     alignment: Alignment.centerRight,
 | |
|                     child: Transform.scale(
 | |
|                         scale: MenuConfig.iconScale,
 | |
|                         child: Obx(() => opt.value == curOption.value
 | |
|                             ? IconButton(
 | |
|                                 padding: EdgeInsets.zero,
 | |
|                                 hoverColor: Colors.transparent,
 | |
|                                 focusColor: Colors.transparent,
 | |
|                                 onPressed: () {},
 | |
|                                 icon: Icon(
 | |
|                                   Icons.check,
 | |
|                                   color: conf.commonColor,
 | |
|                                 ))
 | |
|                             : const SizedBox.shrink())),
 | |
|                   )),
 | |
|                 ],
 | |
|               ),
 | |
|             ),
 | |
|             onPressed: () {
 | |
|               if (opt.dismissOnClicked && Navigator.canPop(context)) {
 | |
|                 Navigator.pop(context);
 | |
|                 if (opt.dismissCallback != null) {
 | |
|                   opt.dismissCallback!();
 | |
|                 }
 | |
|               }
 | |
|               setOption(opt.value);
 | |
|             },
 | |
|           )),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   List<mod_menu.PopupMenuEntry<T>> build(
 | |
|       BuildContext context, MenuConfig conf) {
 | |
|     return [
 | |
|       PopupMenuChildrenItem(
 | |
|         enabled: super.enabled,
 | |
|         padding: padding,
 | |
|         height: conf.height,
 | |
|         itemBuilder: (BuildContext context) =>
 | |
|             options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(),
 | |
|         child: Row(children: [
 | |
|           const SizedBox(width: MenuConfig.midPadding),
 | |
|           Text(
 | |
|             text,
 | |
|             style: TextStyle(
 | |
|                 color: Theme.of(context).textTheme.titleLarge?.color,
 | |
|                 fontSize: MenuConfig.fontSize,
 | |
|                 fontWeight: FontWeight.normal),
 | |
|           ),
 | |
|           Expanded(
 | |
|               child: Align(
 | |
|             alignment: Alignment.centerRight,
 | |
|             child: Icon(
 | |
|               Icons.keyboard_arrow_right,
 | |
|               color: conf.commonColor,
 | |
|             ),
 | |
|           ))
 | |
|         ]),
 | |
|       )
 | |
|     ];
 | |
|   }
 | |
| }
 | |
| 
 | |
| enum SwitchType {
 | |
|   sswitch,
 | |
|   scheckbox,
 | |
| }
 | |
| 
 | |
| typedef SwitchGetter = Future<bool> Function();
 | |
| typedef SwitchSetter = Future<void> Function(bool);
 | |
| 
 | |
| abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
 | |
|   final SwitchType switchType;
 | |
|   final String text;
 | |
|   final EdgeInsets? padding;
 | |
|   Rx<TextStyle>? textStyle;
 | |
| 
 | |
|   MenuEntrySwitchBase({
 | |
|     required this.switchType,
 | |
|     required this.text,
 | |
|     required dismissOnClicked,
 | |
|     this.textStyle,
 | |
|     this.padding,
 | |
|     RxBool? enabled,
 | |
|     dismissCallback,
 | |
|   }) : super(
 | |
|           dismissOnClicked: dismissOnClicked,
 | |
|           enabled: enabled,
 | |
|           dismissCallback: dismissCallback,
 | |
|         );
 | |
| 
 | |
|   bool get isEnabled => enabled?.value ?? true;
 | |
| 
 | |
|   RxBool get curOption;
 | |
|   Future<void> setOption(bool? option);
 | |
| 
 | |
|   tryPop(BuildContext context) {
 | |
|     if (dismissOnClicked && Navigator.canPop(context)) {
 | |
|       Navigator.pop(context);
 | |
|       super.dismissCallback?.call();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   List<mod_menu.PopupMenuEntry<T>> build(
 | |
|       BuildContext context, MenuConfig conf) {
 | |
|     textStyle ??= TextStyle(
 | |
|             color: Theme.of(context).textTheme.titleLarge?.color,
 | |
|             fontSize: MenuConfig.fontSize,
 | |
|             fontWeight: FontWeight.normal)
 | |
|         .obs;
 | |
|     return [
 | |
|       mod_menu.PopupMenuItem(
 | |
|         padding: EdgeInsets.zero,
 | |
|         height: conf.height,
 | |
|         child: Container(
 | |
|             width: conf.boxWidth,
 | |
|             child: TextButton(
 | |
|               child: Container(
 | |
|                   padding: padding,
 | |
|                   alignment: AlignmentDirectional.centerStart,
 | |
|                   height: conf.height,
 | |
|                   child: Row(children: [
 | |
|                     Obx(() => Text(
 | |
|                           text,
 | |
|                           style: textStyle!.value,
 | |
|                         )),
 | |
|                     Expanded(
 | |
|                         child: Align(
 | |
|                       alignment: Alignment.centerRight,
 | |
|                       child: Transform.scale(
 | |
|                           scale: MenuConfig.iconScale,
 | |
|                           child: Obx(() {
 | |
|                             if (switchType == SwitchType.sswitch) {
 | |
|                               return Switch(
 | |
|                                 value: curOption.value,
 | |
|                                 onChanged: isEnabled
 | |
|                                     ? (v) {
 | |
|                                         tryPop(context);
 | |
|                                         setOption(v);
 | |
|                                       }
 | |
|                                     : null,
 | |
|                               );
 | |
|                             } else {
 | |
|                               return Checkbox(
 | |
|                                 value: curOption.value,
 | |
|                                 onChanged: isEnabled
 | |
|                                     ? (v) {
 | |
|                                         tryPop(context);
 | |
|                                         setOption(v);
 | |
|                                       }
 | |
|                                     : null,
 | |
|                               );
 | |
|                             }
 | |
|                           })),
 | |
|                     ))
 | |
|                   ])),
 | |
|               onPressed: isEnabled
 | |
|                   ? () {
 | |
|                       tryPop(context);
 | |
|                       setOption(!curOption.value);
 | |
|                     }
 | |
|                   : null,
 | |
|             )),
 | |
|       )
 | |
|     ];
 | |
|   }
 | |
| }
 | |
| 
 | |
| class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
 | |
|   final SwitchGetter getter;
 | |
|   final SwitchSetter setter;
 | |
|   final RxBool _curOption = false.obs;
 | |
| 
 | |
|   MenuEntrySwitch({
 | |
|     required SwitchType switchType,
 | |
|     required String text,
 | |
|     required this.getter,
 | |
|     required this.setter,
 | |
|     Rx<TextStyle>? textStyle,
 | |
|     EdgeInsets? padding,
 | |
|     dismissOnClicked = false,
 | |
|     RxBool? enabled,
 | |
|     dismissCallback,
 | |
|   }) : super(
 | |
|           switchType: switchType,
 | |
|           text: text,
 | |
|           textStyle: textStyle,
 | |
|           padding: padding,
 | |
|           dismissOnClicked: dismissOnClicked,
 | |
|           enabled: enabled,
 | |
|           dismissCallback: dismissCallback,
 | |
|         ) {
 | |
|     () async {
 | |
|       _curOption.value = await getter();
 | |
|     }();
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   RxBool get curOption => _curOption;
 | |
|   @override
 | |
|   setOption(bool? option) async {
 | |
|     if (option != null) {
 | |
|       await setter(option);
 | |
|       final opt = await getter();
 | |
|       if (_curOption.value != opt) {
 | |
|         _curOption.value = opt;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Compatible with MenuEntrySwitch, it uses value instead of getter
 | |
| class MenuEntrySwitchSync<T> extends MenuEntrySwitchBase<T> {
 | |
|   final SwitchSetter setter;
 | |
|   final RxBool _curOption = false.obs;
 | |
| 
 | |
|   MenuEntrySwitchSync({
 | |
|     required SwitchType switchType,
 | |
|     required String text,
 | |
|     required bool currentValue,
 | |
|     required this.setter,
 | |
|     Rx<TextStyle>? textStyle,
 | |
|     EdgeInsets? padding,
 | |
|     dismissOnClicked = false,
 | |
|     RxBool? enabled,
 | |
|     dismissCallback,
 | |
|   }) : super(
 | |
|           switchType: switchType,
 | |
|           text: text,
 | |
|           textStyle: textStyle,
 | |
|           padding: padding,
 | |
|           dismissOnClicked: dismissOnClicked,
 | |
|           enabled: enabled,
 | |
|           dismissCallback: dismissCallback,
 | |
|         ) {
 | |
|     _curOption.value = currentValue;
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   RxBool get curOption => _curOption;
 | |
|   @override
 | |
|   setOption(bool? option) async {
 | |
|     if (option != null) {
 | |
|       await setter(option);
 | |
|       // Notice: no ensure with getter, best used on menus that are destroyed on click
 | |
|       if (_curOption.value != option) {
 | |
|         _curOption.value = option;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| typedef Switch2Getter = RxBool Function();
 | |
| typedef Switch2Setter = Future<void> Function(bool);
 | |
| 
 | |
| class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
 | |
|   final Switch2Getter getter;
 | |
|   final SwitchSetter setter;
 | |
| 
 | |
|   MenuEntrySwitch2({
 | |
|     required SwitchType switchType,
 | |
|     required String text,
 | |
|     required this.getter,
 | |
|     required this.setter,
 | |
|     Rx<TextStyle>? textStyle,
 | |
|     EdgeInsets? padding,
 | |
|     dismissOnClicked = false,
 | |
|     RxBool? enabled,
 | |
|     dismissCallback,
 | |
|   }) : super(
 | |
|           switchType: switchType,
 | |
|           text: text,
 | |
|           textStyle: textStyle,
 | |
|           padding: padding,
 | |
|           dismissOnClicked: dismissOnClicked,
 | |
|           dismissCallback: dismissCallback,
 | |
|         );
 | |
| 
 | |
|   @override
 | |
|   RxBool get curOption => getter();
 | |
|   @override
 | |
|   setOption(bool? option) async {
 | |
|     if (option != null) {
 | |
|       await setter(option);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
 | |
|   final String text;
 | |
|   final List<MenuEntryBase<T>> entries;
 | |
|   final EdgeInsets? padding;
 | |
| 
 | |
|   MenuEntrySubMenu({
 | |
|     required this.text,
 | |
|     required this.entries,
 | |
|     this.padding,
 | |
|     RxBool? enabled,
 | |
|   }) : super(enabled: enabled);
 | |
| 
 | |
|   @override
 | |
|   List<mod_menu.PopupMenuEntry<T>> build(
 | |
|       BuildContext context, MenuConfig conf) {
 | |
|     super.enabled ??= true.obs;
 | |
|     return [
 | |
|       PopupMenuChildrenItem(
 | |
|         enabled: super.enabled,
 | |
|         height: conf.height,
 | |
|         padding: padding,
 | |
|         position: mod_menu.PopupMenuPosition.overSide,
 | |
|         itemBuilder: (BuildContext context) => entries
 | |
|             .map((entry) => entry.build(context, conf))
 | |
|             .expand((i) => i)
 | |
|             .toList(),
 | |
|         child: Row(children: [
 | |
|           const SizedBox(width: MenuConfig.midPadding),
 | |
|           Obx(() => Text(
 | |
|                 text,
 | |
|                 style: super.enabled!.value
 | |
|                     ? enabledStyle(context)
 | |
|                     : disabledStyle(),
 | |
|               )),
 | |
|           Expanded(
 | |
|               child: Align(
 | |
|             alignment: Alignment.centerRight,
 | |
|             child: Obx(() => Icon(
 | |
|                   Icons.keyboard_arrow_right,
 | |
|                   color: super.enabled!.value ? conf.commonColor : Colors.grey,
 | |
|                 )),
 | |
|           ))
 | |
|         ]),
 | |
|       )
 | |
|     ];
 | |
|   }
 | |
| }
 | |
| 
 | |
| class MenuEntryButton<T> extends MenuEntryBase<T> {
 | |
|   final Widget Function(TextStyle? style) childBuilder;
 | |
|   Function() proc;
 | |
|   final EdgeInsets? padding;
 | |
| 
 | |
|   MenuEntryButton({
 | |
|     required this.childBuilder,
 | |
|     required this.proc,
 | |
|     this.padding,
 | |
|     dismissOnClicked = false,
 | |
|     RxBool? enabled,
 | |
|     dismissCallback,
 | |
|   }) : super(
 | |
|           dismissOnClicked: dismissOnClicked,
 | |
|           enabled: enabled,
 | |
|           dismissCallback: dismissCallback,
 | |
|         );
 | |
| 
 | |
|   Widget _buildChild(BuildContext context, MenuConfig conf) {
 | |
|     super.enabled ??= true.obs;
 | |
|     return Obx(() => Container(
 | |
|         width: conf.boxWidth,
 | |
|         child: TextButton(
 | |
|           onPressed: super.enabled!.value
 | |
|               ? () {
 | |
|                   if (super.dismissOnClicked && Navigator.canPop(context)) {
 | |
|                     Navigator.pop(context);
 | |
|                     if (super.dismissCallback != null) {
 | |
|                       super.dismissCallback!();
 | |
|                     }
 | |
|                   }
 | |
|                   proc();
 | |
|                 }
 | |
|               : null,
 | |
|           child: Container(
 | |
|             padding: padding,
 | |
|             alignment: AlignmentDirectional.centerStart,
 | |
|             constraints:
 | |
|                 BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
 | |
|             child: childBuilder(
 | |
|                 super.enabled!.value ? enabledStyle(context) : disabledStyle()),
 | |
|           ),
 | |
|         )));
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   List<mod_menu.PopupMenuEntry<T>> build(
 | |
|       BuildContext context, MenuConfig conf) {
 | |
|     return [
 | |
|       mod_menu.PopupMenuItem(
 | |
|         padding: EdgeInsets.zero,
 | |
|         height: conf.height,
 | |
|         child: _buildChild(context, conf),
 | |
|       )
 | |
|     ];
 | |
|   }
 | |
| }
 | |
| 
 | |
| class CustomPopupMenuTheme {
 | |
|   static const Color commonColor = MyTheme.accent;
 | |
|   // kMinInteractiveDimension
 | |
|   static const double height = 20.0;
 | |
|   static const double dividerHeight = 3.0;
 | |
| }
 |