mirrored_repositories/rustdesk/flutter/lib/desktop
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>>();
}

class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
    extends State<W> {
  RxBool enabled = true.obs;

  @override
  void initState() {
    super.initState();
    if (widget.enabled != null) {
      enabled.value = widget.enabled!.value;
    }
  }

  @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.subtitle1!;
    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,
        );

  RxBool get curOption;
  Future<void> setOption(bool? option);

  @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: (v) {
                                  if (super.dismissOnClicked &&
                                      Navigator.canPop(context)) {
                                    Navigator.pop(context);
                                    if (super.dismissCallback != null) {
                                      super.dismissCallback!();
                                    }
                                  }
                                  setOption(v);
                                },
                              );
                            } else {
                              return Checkbox(
                                value: curOption.value,
                                onChanged: (v) {
                                  if (super.dismissOnClicked &&
                                      Navigator.canPop(context)) {
                                    Navigator.pop(context);
                                    if (super.dismissCallback != null) {
                                      super.dismissCallback!();
                                    }
                                  }
                                  setOption(v);
                                },
                              );
                            }
                          })),
                    ))
                  ])),
              onPressed: () {
                if (super.dismissOnClicked && Navigator.canPop(context)) {
                  Navigator.pop(context);
                  if (super.dismissCallback != null) {
                    super.dismissCallback!();
                  }
                }
                setOption(!curOption.value);
              },
            )),
      )
    ];
  }
}

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;
}
