flutter_desktop: password menu

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou
2022-09-06 21:20:53 -07:00
parent a50482af5c
commit 70c4726766
8 changed files with 501 additions and 418 deletions

View File

@@ -19,18 +19,18 @@ import 'package:tray_manager/tray_manager.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:window_manager/window_manager.dart';
class _PopupMenuTheme {
class _MenubarTheme {
static const Color commonColor = MyTheme.accent;
// kMinInteractiveDimension
static const double height = 25.0;
static const double dividerHeight = 3.0;
static const double dividerHeight = 12.0;
}
class DesktopHomePage extends StatefulWidget {
const DesktopHomePage({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _DesktopHomePageState();
State<DesktopHomePage> createState() => _DesktopHomePageState();
}
const borderColor = Color(0xFF2F65BA);
@@ -93,7 +93,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
buildServerBoard(BuildContext context) {
return Container(
color: MyTheme.color(context).grayBg,
child: const ConnectionPage(),
child: ConnectionPage(),
);
}
@@ -116,7 +116,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
Container(
height: 25,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -142,11 +142,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
child: TextFormField(
controller: model.serverId,
readOnly: true,
decoration: const InputDecoration(
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.only(bottom: 20),
),
style: const TextStyle(
style: TextStyle(
fontSize: 22,
),
),
@@ -161,190 +161,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
);
}
// Future<MenuEntryBase<String>> _genSwitchEntry(
// String label, String key) async {
// final v = await bind.mainGetOption(key: key);
// bool enable;
// if (key == "stop-service") {
// enable = v != "Y";
// } else if (key.startsWith("allow-")) {
// enable = v == "Y";
// } else {
// enable = v != "N";
// }
// return PopupMenuItem(
// child: Row(
// children: [
// Icon(Icons.check,
// color: enable ? null : MyTheme.accent.withAlpha(00)),
// Text(
// label,
// style: genTextStyle(enable),
// ),
// ],
// ),
// value: key,
// );
// }
_popupMenu(BuildContext context, RelativeRect position) async {
TextStyle styleEnabled = const TextStyle(
color: Colors.black,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal);
TextStyle styleDisabled = const TextStyle(
color: Colors.redAccent,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal,
decoration: TextDecoration.lineThrough);
enabledEntry(String label, String key) {
Rx<TextStyle> textStyle = styleEnabled.obs;
return MenuEntrySwitch<String>(
text: translate(label),
textStyle: textStyle,
getter: () async {
final opt = await bind.mainGetOption(key: key);
bool enabled;
if (key == 'stop-service') {
enabled = opt != 'Y';
} else if (key.startsWith("allow-")) {
enabled = opt == 'Y';
} else {
enabled = opt != 'N';
}
textStyle.value = enabled ? styleEnabled : styleDisabled;
return enabled;
},
setter: (bool v) async {
String opt;
if (key == 'stop-service') {
opt = v ? 'Y' : '';
} else if (key.startsWith("allow-")) {
opt = v ? 'Y' : '';
} else {
opt = v ? '' : 'N';
}
await bind.mainSetOption(key: key, value: opt);
if (key == 'allow-darktheme') {
changeTheme(opt);
}
},
dismissOnClicked: false,
);
}
final userName = await gFFI.userModel.getUserName();
final enabledInput = await bind.mainGetOption(key: 'enable-audio');
final defaultInput = await gFFI.getDefaultAudioInput();
final List<MenuEntryBase<String>> menu = <MenuEntryBase<String>>[
enabledEntry('Enable Keyboard/Mouse', 'enable-keyboard'),
enabledEntry('Enable Clipboard', 'enable-clipboard'),
enabledEntry('Enable File Transfer', 'enable-file-transfer'),
enabledEntry('Enable TCP Tunneling', 'enable-tunnel'),
// TODO: audio sub menu?
// genAudioInputPopupMenuItem(enabledInput != "N", defaultInput),
MenuEntryDivider(),
MenuEntryButton(
childBuilder: (TextStyle? style) => Text(
translate('ID/Relay Server'),
style: style,
),
proc: () {
changeServer();
},
dismissOnClicked: true,
),
MenuEntryButton(
childBuilder: (TextStyle? style) => Text(
translate('IP Whitelisting'),
style: style,
),
proc: () {
changeWhiteList();
},
dismissOnClicked: true,
),
MenuEntryButton(
childBuilder: (TextStyle? style) => Text(
translate('Socks5 Proxy'),
style: style,
),
proc: () {
changeSocks5Proxy();
},
dismissOnClicked: true,
),
MenuEntryDivider(),
enabledEntry('Enable Service', 'stop-service'),
enabledEntry('Always connected via relay', 'allow-always-relay'),
// FIXME: is this option correct?
enabledEntry('Start ID/relay service', 'stop-rendezvous-service'),
MenuEntryDivider(),
userName.isEmpty
? MenuEntryButton(
childBuilder: (TextStyle? style) => Text(
translate('Login'),
style: style,
),
proc: () {
login();
},
dismissOnClicked: true,
)
: MenuEntryButton(
childBuilder: (TextStyle? style) => Text(
translate('Logout'),
style: style,
),
proc: () {
logOut();
},
dismissOnClicked: true,
),
MenuEntryButton(
childBuilder: (TextStyle? style) => Text(
translate('Change ID'),
style: style,
),
proc: () {
changeId();
},
dismissOnClicked: true,
),
MenuEntryDivider(),
enabledEntry('Dark Theme', 'allow-darktheme'),
MenuEntryButton(
childBuilder: (TextStyle? style) => Text(
translate('About'),
style: style,
),
proc: () {
about();
},
dismissOnClicked: true,
),
];
await mod_menu.showMenu(
context: context,
position: position,
items: menu
.map((e) => e.build(
context,
const MenuConfig(
commonColor: _PopupMenuTheme.commonColor,
height: _PopupMenuTheme.height,
dividerHeight: _PopupMenuTheme.dividerHeight)))
.expand((i) => i)
.toList());
}
Widget buildPopupMenu(BuildContext context) {
RelativeRect position = const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0);
var position;
RxBool hover = false.obs;
return InkWell(
onTapDown: (detail) {
@@ -353,7 +171,83 @@ class _DesktopHomePageState extends State<DesktopHomePage>
position = RelativeRect.fromLTRB(x, y, x, y);
},
onTap: () async {
await _popupMenu(context, position);
final userName = await gFFI.userModel.getUserName();
final enabledInput = await bind.mainGetOption(key: 'enable-audio');
final defaultInput = await gFFI.getDefaultAudioInput();
var menu = <PopupMenuEntry>[
await genEnablePopupMenuItem(
translate("Enable Keyboard/Mouse"),
'enable-keyboard',
),
await genEnablePopupMenuItem(
translate("Enable Clipboard"),
'enable-clipboard',
),
await genEnablePopupMenuItem(
translate("Enable File Transfer"),
'enable-file-transfer',
),
await genEnablePopupMenuItem(
translate("Enable TCP Tunneling"),
'enable-tunnel',
),
genAudioInputPopupMenuItem(enabledInput != "N", defaultInput),
PopupMenuDivider(),
PopupMenuItem(
child: Text(translate("ID/Relay Server")),
value: 'custom-server',
),
PopupMenuItem(
child: Text(translate("IP Whitelisting")),
value: 'whitelist',
),
PopupMenuItem(
child: Text(translate("Socks5 Proxy")),
value: 'socks5-proxy',
),
PopupMenuDivider(),
await genEnablePopupMenuItem(
translate("Enable Service"),
'stop-service',
),
// TODO: direct server
await genEnablePopupMenuItem(
translate("Always connected via relay"),
'allow-always-relay',
),
await genEnablePopupMenuItem(
translate("Start ID/relay service"),
'stop-rendezvous-service',
),
PopupMenuDivider(),
userName.isEmpty
? PopupMenuItem(
child: Text(translate("Login")),
value: 'login',
)
: PopupMenuItem(
child: Text("${translate("Logout")} $userName"),
value: 'logout',
),
PopupMenuItem(
child: Text(translate("Change ID")),
value: 'change-id',
),
PopupMenuDivider(),
await genEnablePopupMenuItem(
translate("Dark Theme"),
'allow-darktheme',
),
PopupMenuItem(
child: Text(translate("About")),
value: 'about',
),
];
final v =
await showMenu(context: context, position: position, items: menu);
if (v != null) {
onSelectMenu(v);
}
},
child: Obx(
() => CircleAvatar(
@@ -435,18 +329,19 @@ class _DesktopHomePageState extends State<DesktopHomePage>
onTap: () => bind.mainUpdateTemporaryPassword(),
onHover: (value) => refreshHover.value = value,
),
FutureBuilder<Widget>(
future: buildPasswordPopupMenu(context),
builder: (context, snapshot) {
if (snapshot.hasError) {
print("${snapshot.error}");
}
if (snapshot.hasData) {
return snapshot.data!;
} else {
return Offstage();
}
})
const _PasswordPopupMenu(),
// FutureBuilder<Widget>(
// future: buildPasswordPopupMenu(context),
// builder: (context, snapshot) {
// if (snapshot.hasError) {
// print("${snapshot.error}");
// }
// if (snapshot.hasData) {
// return snapshot.data!;
// } else {
// return Offstage();
// }
// })
],
),
],
@@ -479,7 +374,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
),
],
),
onTap: () => gFFI.serverModel.verificationMethod = value,
onTap: () => gFFI.serverModel.setVerificationMethod(value),
);
final temporary_enabled =
gFFI.serverModel.verificationMethod != kUsePermanentPassword;
@@ -516,8 +411,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
onTap: () {
if (gFFI.serverModel.temporaryPasswordLength !=
e) {
gFFI.serverModel.temporaryPasswordLength = e;
bind.mainUpdateTemporaryPassword();
() async {
await gFFI.serverModel
.setTemporaryPasswordLength(e);
await bind.mainUpdateTemporaryPassword();
}();
}
},
))
@@ -1148,3 +1046,120 @@ void setPasswordDialog() async {
);
});
}
class _PasswordPopupMenu extends StatefulWidget {
const _PasswordPopupMenu({Key? key}) : super(key: key);
@override
State<_PasswordPopupMenu> createState() => _PasswordPopupMenuState();
}
class _PasswordPopupMenuState extends State<_PasswordPopupMenu> {
final RxBool _tempEnabled = true.obs;
final RxBool _permEnabled = true.obs;
List<MenuEntryBase<String>> _buildMenus() {
return <MenuEntryBase<String>>[
MenuEntryRadios<String>(
text: translate('Password type'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Use temporary password'),
value: kUseTemporaryPassword),
MenuEntryRadioOption(
text: translate('Use permanent password'),
value: kUsePermanentPassword),
MenuEntryRadioOption(
text: translate('Use both passwords'),
value: kUseBothPasswords),
],
curOptionGetter: () async {
return gFFI.serverModel.verificationMethod;
},
optionSetter: (String oldValue, String newValue) async {
await bind.mainSetOption(
key: "verification-method", value: newValue);
await gFFI.serverModel.updatePasswordModel();
setState(() {
_tempEnabled.value =
gFFI.serverModel.verificationMethod != kUsePermanentPassword;
_permEnabled.value =
gFFI.serverModel.verificationMethod != kUseTemporaryPassword;
});
}),
MenuEntryDivider(),
MenuEntryButton<String>(
enabled: _permEnabled,
childBuilder: (TextStyle? style) => Text(
translate('Set permanent password'),
style: style,
),
proc: () {
setPasswordDialog();
},
dismissOnClicked: true,
),
MenuEntrySubMenu(
enabled: _tempEnabled,
text: translate('Set temporary password length'),
entries: [
MenuEntryRadios<String>(
enabled: _tempEnabled,
text: translate(''),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('6'),
value: '6',
enabled: _tempEnabled,
),
MenuEntryRadioOption(
text: translate('8'),
value: '8',
enabled: _tempEnabled,
),
MenuEntryRadioOption(
text: translate('10'),
value: '10',
enabled: _tempEnabled,
),
],
curOptionGetter: () async {
return gFFI.serverModel.temporaryPasswordLength;
},
optionSetter: (String oldValue, String newValue) async {
if (oldValue != newValue) {
await gFFI.serverModel.setTemporaryPasswordLength(newValue);
await gFFI.serverModel.updatePasswordModel();
}
}),
])
];
}
@override
Widget build(BuildContext context) {
final editHover = false.obs;
return mod_menu.PopupMenuButton(
padding: EdgeInsets.zero,
onHover: (v) => editHover.value = v,
tooltip: translate(''),
position: mod_menu.PopupMenuPosition.overSide,
itemBuilder: (BuildContext context) => _buildMenus()
.map((entry) => entry.build(
context,
const MenuConfig(
commonColor: _MenubarTheme.commonColor,
height: _MenubarTheme.height,
dividerHeight: _MenubarTheme.dividerHeight,
)))
.expand((i) => i)
.toList(),
child: Obx(() => Icon(Icons.edit,
size: 22,
color: editHover.value
? MyTheme.color(context).text
: const Color(0xFFDDDDDD))
.marginOnly(bottom: 2)),
);
}
}

View File

@@ -313,8 +313,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
translate("Use permanent password"),
translate("Use both passwords"),
];
bool tmp_enabled = model.verificationMethod != kUsePermanentPassword;
bool perm_enabled = model.verificationMethod != kUseTemporaryPassword;
bool tmpEnabled = model.verificationMethod != kUsePermanentPassword;
bool permEnabled = model.verificationMethod != kUseTemporaryPassword;
String currentValue = values[keys.indexOf(model.verificationMethod)];
List<Widget> radios = values
.map((value) => _Radio<String>(
@@ -323,16 +323,24 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
groupValue: currentValue,
label: value,
onChanged: ((value) {
model.verificationMethod = keys[values.indexOf(value)];
() async {
await model
.setVerificationMethod(keys[values.indexOf(value)]);
await model.updatePasswordModel();
}();
}),
enabled: !locked,
))
.toList();
var onChanged = tmp_enabled && !locked
var onChanged = tmpEnabled && !locked
? (value) {
if (value != null)
model.temporaryPasswordLength = value.toString();
if (value != null) {
() async {
await model.setTemporaryPasswordLength(value.toString());
await model.updatePasswordModel();
}();
}
}
: null;
List<Widget> lengthRadios = ['6', '8', '10']
@@ -364,10 +372,10 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
...lengthRadios,
],
),
enabled: tmp_enabled && !locked),
enabled: tmpEnabled && !locked),
radios[1],
_SubButton('Set permanent password', setPasswordDialog,
perm_enabled && !locked),
permEnabled && !locked),
radios[2],
]);
})));

View File

@@ -42,6 +42,7 @@ class _RemotePageState extends State<RemotePage>
String _value = '';
final _cursorOverImage = false.obs;
late RxBool _showRemoteCursor;
late RxBool _remoteCursorMoved;
late RxBool _keyboardEnabled;
final FocusNode _mobileFocusNode = FocusNode();
@@ -61,8 +62,10 @@ class _RemotePageState extends State<RemotePage>
CurrentDisplayState.init(id);
KeyboardEnabledState.init(id);
ShowRemoteCursorState.init(id);
RemoteCursorMovedState.init(id);
_showRemoteCursor = ShowRemoteCursorState.find(id);
_keyboardEnabled = KeyboardEnabledState.find(id);
_remoteCursorMoved = RemoteCursorMovedState.find(id);
}
void _removeStates(String id) {
@@ -71,6 +74,7 @@ class _RemotePageState extends State<RemotePage>
CurrentDisplayState.delete(id);
ShowRemoteCursorState.delete(id);
KeyboardEnabledState.delete(id);
RemoteCursorMovedState.delete(id);
}
@override
@@ -396,6 +400,7 @@ class _RemotePageState extends State<RemotePage>
id: widget.id,
cursorOverImage: _cursorOverImage,
keyboardEnabled: _keyboardEnabled,
remoteCursorMoved: _remoteCursorMoved,
listenerBuilder: _buildImageListener,
);
}))
@@ -460,6 +465,7 @@ class ImagePaint extends StatelessWidget {
final String id;
final Rx<bool> cursorOverImage;
final Rx<bool> keyboardEnabled;
final Rx<bool> remoteCursorMoved;
final Widget Function(Widget)? listenerBuilder;
final ScrollController _horizontal = ScrollController();
final ScrollController _vertical = ScrollController();
@@ -469,6 +475,7 @@ class ImagePaint extends StatelessWidget {
required this.id,
required this.cursorOverImage,
required this.keyboardEnabled,
required this.remoteCursorMoved,
this.listenerBuilder})
: super(key: key);
@@ -476,6 +483,7 @@ class ImagePaint extends StatelessWidget {
Widget build(BuildContext context) {
final m = Provider.of<ImageModel>(context);
var c = Provider.of<CanvasModel>(context);
final cursor = Provider.of<CursorModel>(context);
final s = c.scale;
if (c.scrollStyle == ScrollStyle.scrollbar) {
final imageWidget = SizedBox(
@@ -501,12 +509,16 @@ class ImagePaint extends StatelessWidget {
return false;
},
child: Obx(() => MouseRegion(
// cursor: (keyboardEnabled.isTrue && cursorOverImage.isTrue)
// ? SystemMouseCursors.none
// : MouseCursor.defer,
/// cursor: MouseCursor.defer,
cursor: FlutterCustomCursor(
path: "assets/pencil.png", x: 1.0, y: 8.0),
cursor: (cursorOverImage.isTrue && keyboardEnabled.isTrue)
? (remoteCursorMoved.isTrue
? SystemMouseCursors.none
: FlutterCustomMemoryImageCursor(
pixbuf: cursor.rgba!,
hotx: cursor.hotx,
hoty: cursor.hoty,
imageWidth: (cursor.image!.width * s).toInt(),
imageHeight: (cursor.image!.height * s).toInt()))
: MouseCursor.defer,
onHover: (evt) {
pos.value = evt.position;
},

View File

@@ -1031,6 +1031,7 @@ class PopupMenuButton<T> extends StatefulWidget {
Key? key,
required this.itemBuilder,
this.initialValue,
this.onHover,
this.onSelected,
this.onCanceled,
this.tooltip,
@@ -1061,6 +1062,9 @@ class PopupMenuButton<T> extends StatefulWidget {
/// The value of the menu item, if any, that should be highlighted when the menu opens.
final T? initialValue;
/// Called when the user hovers this button.
final ValueChanged<bool>? onHover;
/// Called when the user selects a value from the popup menu created by this button.
///
/// If the popup menu is dismissed without selecting a value, [onCanceled] is
@@ -1273,18 +1277,20 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
assert(debugCheckHasMaterialLocalizations(context));
if (widget.child != null)
if (widget.child != null) {
return Tooltip(
message:
widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
child: InkWell(
onTap: widget.enabled ? showButtonMenu : null,
onHover: widget.onHover,
canRequestFocus: _canRequestFocus,
radius: widget.splashRadius,
enableFeedback: enableFeedback,
child: widget.child,
),
);
}
return IconButton(
icon: widget.icon ?? Icon(Icons.adaptive.more),

View File

@@ -12,7 +12,7 @@ class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> {
key,
this.height = kMinInteractiveDimension,
this.padding,
this.enable = true,
this.enabled,
this.textStyle,
this.onTap,
this.position = mod_menu.PopupMenuPosition.overSide,
@@ -25,7 +25,7 @@ class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> {
final Offset offset;
final TextStyle? textStyle;
final EdgeInsets? padding;
final bool enable;
final RxBool? enabled;
final void Function()? onTap;
final List<mod_menu.PopupMenuEntry<T>> Function(BuildContext) itemBuilder;
final Widget child;
@@ -56,25 +56,27 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
TextStyle style = widget.textStyle ??
popupMenuTheme.textStyle ??
theme.textTheme.subtitle1!;
return mod_menu.PopupMenuButton<T>(
enabled: widget.enable,
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),
padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
child: widget.child,
return Obx(() {
return mod_menu.PopupMenuButton<T>(
enabled: widget.enabled != null ? widget.enabled!.value : true,
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),
padding:
widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
child: widget.child,
),
),
),
);
);
});
}
}
@@ -98,8 +100,12 @@ class MenuConfig {
abstract class MenuEntryBase<T> {
bool dismissOnClicked;
RxBool? enabled;
MenuEntryBase({this.dismissOnClicked = false});
MenuEntryBase({
this.dismissOnClicked = false,
this.enabled,
});
List<mod_menu.PopupMenuEntry<T>> build(BuildContext context, MenuConfig conf);
}
@@ -119,9 +125,14 @@ class MenuEntryRadioOption {
String text;
String value;
bool dismissOnClicked;
RxBool? enabled;
MenuEntryRadioOption(
{required this.text, required this.value, this.dismissOnClicked = false});
MenuEntryRadioOption({
required this.text,
required this.value,
this.dismissOnClicked = false,
this.enabled,
});
}
typedef RadioOptionsGetter = List<MenuEntryRadioOption> Function();
@@ -138,13 +149,14 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
final RadioOptionSetter optionSetter;
final RxString _curOption = "".obs;
MenuEntryRadios(
{required this.text,
required this.optionsGetter,
required this.curOptionGetter,
required this.optionSetter,
dismissOnClicked = false})
: super(dismissOnClicked: dismissOnClicked) {
MenuEntryRadios({
required this.text,
required this.optionsGetter,
required this.curOptionGetter,
required this.optionSetter,
dismissOnClicked = false,
RxBool? enabled,
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled) {
() async {
_curOption.value = await curOptionGetter();
}();
@@ -220,13 +232,17 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
final RadioOptionSetter optionSetter;
final RxString _curOption = "".obs;
MenuEntrySubRadios(
{required this.text,
required this.optionsGetter,
required this.curOptionGetter,
required this.optionSetter,
dismissOnClicked = false})
: super(dismissOnClicked: dismissOnClicked) {
MenuEntrySubRadios({
required this.text,
required this.optionsGetter,
required this.curOptionGetter,
required this.optionSetter,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
dismissOnClicked: dismissOnClicked,
enabled: enabled,
) {
() async {
_curOption.value = await curOptionGetter();
}();
@@ -293,6 +309,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
BuildContext context, MenuConfig conf) {
return [
PopupMenuChildrenItem(
enabled: super.enabled,
padding: EdgeInsets.zero,
height: conf.height,
itemBuilder: (BuildContext context) =>
@@ -327,9 +344,12 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
final String text;
final Rx<TextStyle>? textStyle;
MenuEntrySwitchBase(
{required this.text, required dismissOnClicked, this.textStyle})
: super(dismissOnClicked: dismissOnClicked);
MenuEntrySwitchBase({
required this.text,
required dismissOnClicked,
this.textStyle,
RxBool? enabled,
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled);
RxBool get curOption;
Future<void> setOption(bool option);
@@ -395,16 +415,19 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
final SwitchSetter setter;
final RxBool _curOption = false.obs;
MenuEntrySwitch(
{required String text,
required this.getter,
required this.setter,
Rx<TextStyle>? textStyle,
dismissOnClicked = false})
: super(
text: text,
textStyle: textStyle,
dismissOnClicked: dismissOnClicked) {
MenuEntrySwitch({
required String text,
required this.getter,
required this.setter,
Rx<TextStyle>? textStyle,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
text: text,
textStyle: textStyle,
dismissOnClicked: dismissOnClicked,
enabled: enabled,
) {
() async {
_curOption.value = await getter();
}();
@@ -429,13 +452,14 @@ class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
final Switch2Getter getter;
final SwitchSetter setter;
MenuEntrySwitch2(
{required String text,
required this.getter,
required this.setter,
Rx<TextStyle>? textStyle,
dismissOnClicked = false})
: super(
MenuEntrySwitch2({
required String text,
required this.getter,
required this.setter,
Rx<TextStyle>? textStyle,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
text: text,
textStyle: textStyle,
dismissOnClicked: dismissOnClicked);
@@ -452,13 +476,18 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
final String text;
final List<MenuEntryBase<T>> entries;
MenuEntrySubMenu({required this.text, required this.entries});
MenuEntrySubMenu({
required this.text,
required this.entries,
RxBool? enabled,
}) : super(enabled: enabled);
@override
List<mod_menu.PopupMenuEntry<T>> build(
BuildContext context, MenuConfig conf) {
return [
PopupMenuChildrenItem(
enabled: super.enabled,
height: conf.height,
padding: EdgeInsets.zero,
position: mod_menu.PopupMenuPosition.overSide,
@@ -468,20 +497,24 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
.toList(),
child: Row(children: [
const SizedBox(width: MenuConfig.midPadding),
Text(
text,
style: TextStyle(
color: MyTheme.color(context).text,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
),
Obx(() => Text(
text,
style: TextStyle(
color: (super.enabled != null ? super.enabled!.value : true)
? Colors.black
: Colors.grey,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
)),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Icon(
Icons.keyboard_arrow_right,
color: conf.commonColor,
),
child: Obx(() => Icon(
Icons.keyboard_arrow_right,
color: (super.enabled != null ? super.enabled!.value : true)
? conf.commonColor
: Colors.grey,
)),
))
]),
)
@@ -493,36 +526,57 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
final Widget Function(TextStyle? style) childBuilder;
Function() proc;
MenuEntryButton(
{required this.childBuilder,
required this.proc,
dismissOnClicked = false})
: super(dismissOnClicked: dismissOnClicked);
MenuEntryButton({
required this.childBuilder,
required this.proc,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
dismissOnClicked: dismissOnClicked,
enabled: enabled,
);
Widget _buildChild(BuildContext context, MenuConfig conf) {
return Obx(() {
bool enabled = true;
if (super.enabled != null) {
enabled = super.enabled!.value;
}
const enabledStyle = TextStyle(
color: Colors.black,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal);
const disabledStyle = TextStyle(
color: Colors.grey,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal);
return TextButton(
onPressed: enabled
? () {
if (super.dismissOnClicked && Navigator.canPop(context)) {
Navigator.pop(context);
}
proc();
}
: null,
child: Container(
alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height),
child: childBuilder(enabled ? enabledStyle : disabledStyle),
),
);
});
}
@override
List<mod_menu.PopupMenuEntry<T>> build(
BuildContext context, MenuConfig conf) {
return [
mod_menu.PopupMenuItem(
enabled: super.enabled != null ? super.enabled!.value : true,
padding: EdgeInsets.zero,
height: conf.height,
child: TextButton(
child: Container(
alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height),
child: childBuilder(
TextStyle(
color: MyTheme.color(context).text,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
)),
onPressed: () {
if (super.dismissOnClicked && Navigator.canPop(context)) {
Navigator.pop(context);
}
proc();
},
),
child: _buildChild(context, conf),
)
];
}