From a553334157c6af108f1fd672aa2d102aa84bf87c Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 3 Sep 2022 18:19:50 +0800 Subject: [PATCH] dialog focus && deal with Enter/Esc key Signed-off-by: 21pages --- flutter/lib/common.dart | 148 ++++++--- .../lib/desktop/pages/connection_page.dart | 207 ++++++------ .../lib/desktop/pages/desktop_home_page.dart | 314 +++++++++--------- .../desktop/pages/desktop_setting_page.dart | 300 ++++++++--------- .../lib/desktop/pages/desktop_tab_page.dart | 46 +-- .../lib/desktop/pages/file_manager_page.dart | 84 ++--- .../lib/desktop/pages/port_forward_page.dart | 69 ++-- .../lib/desktop/widgets/peercard_widget.dart | 140 ++++---- .../lib/desktop/widgets/remote_menubar.dart | 81 ++--- .../lib/desktop/widgets/tabbar_widget.dart | 34 +- flutter/lib/models/file_model.dart | 194 ++++++----- flutter/lib/models/server_model.dart | 201 +++++------ 12 files changed, 959 insertions(+), 859 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 75328c840..4a0a0dc82 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -7,6 +7,7 @@ import 'package:back_button_interceptor/back_button_interceptor.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/models/peer_model.dart'; import 'package:get/get.dart'; @@ -340,34 +341,41 @@ class OverlayDialogManager { {bool clickMaskDismiss = false, bool showCancel = true, VoidCallback? onCancel}) { - show((setState, close) => CustomAlertDialog( + show((setState, close) { + cancel() { + dismissAll(); + if (onCancel != null) { + onCancel(); + } + } + + return CustomAlertDialog( content: Container( - constraints: BoxConstraints(maxWidth: 240), + constraints: const BoxConstraints(maxWidth: 240), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 30), - Center(child: CircularProgressIndicator()), - SizedBox(height: 20), + const SizedBox(height: 30), + const Center(child: CircularProgressIndicator()), + const SizedBox(height: 20), Center( child: Text(translate(text), - style: TextStyle(fontSize: 15))), - SizedBox(height: 20), + style: const TextStyle(fontSize: 15))), + const SizedBox(height: 20), Offstage( offstage: !showCancel, child: Center( child: TextButton( style: flatButtonStyle, - onPressed: () { - dismissAll(); - if (onCancel != null) { - onCancel(); - } - }, + onPressed: cancel, child: Text(translate('Cancel'), - style: TextStyle(color: MyTheme.accent))))) - ])))); + style: + const TextStyle(color: MyTheme.accent))))) + ])), + onCancel: showCancel ? cancel : null, + ); + }); } } @@ -377,18 +385,18 @@ void showToast(String text, {Duration timeout = const Duration(seconds: 2)}) { final entry = OverlayEntry(builder: (_) { return IgnorePointer( child: Align( - alignment: Alignment(0.0, 0.8), + alignment: const Alignment(0.0, 0.8), child: Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.6), - borderRadius: BorderRadius.all( + borderRadius: const BorderRadius.all( Radius.circular(20), ), ), - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), child: Text( text, - style: TextStyle( + style: const TextStyle( decoration: TextDecoration.none, fontWeight: FontWeight.w300, fontSize: 18, @@ -403,23 +411,54 @@ void showToast(String text, {Duration timeout = const Duration(seconds: 2)}) { } class CustomAlertDialog extends StatelessWidget { - CustomAlertDialog( - {this.title, required this.content, this.actions, this.contentPadding}); + const CustomAlertDialog( + {Key? key, + this.title, + required this.content, + this.actions, + this.contentPadding, + this.onSubmit, + this.onCancel}) + : super(key: key); final Widget? title; final Widget content; final List? actions; final double? contentPadding; + final Function()? onSubmit; + final Function()? onCancel; @override Widget build(BuildContext context) { - return AlertDialog( - scrollable: true, - title: title, - contentPadding: - EdgeInsets.symmetric(horizontal: contentPadding ?? 25, vertical: 10), - content: content, - actions: actions, + FocusNode focusNode = FocusNode(); + // request focus if there is no focused FocusNode in the dialog + Future.delayed(Duration.zero, () { + if (!focusNode.hasFocus) focusNode.requestFocus(); + }); + return Focus( + focusNode: focusNode, + autofocus: true, + onKey: (node, key) { + if (key.logicalKey == LogicalKeyboardKey.escape) { + if (key is RawKeyDownEvent) { + onCancel?.call(); + } + return KeyEventResult.handled; // avoid TextField exception on escape + } else if (onSubmit != null && + key.logicalKey == LogicalKeyboardKey.enter) { + if (key is RawKeyDownEvent) onSubmit?.call(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: AlertDialog( + scrollable: true, + title: title, + contentPadding: EdgeInsets.symmetric( + horizontal: contentPadding ?? 25, vertical: 10), + content: content, + actions: actions, + ), ); } } @@ -429,26 +468,28 @@ void msgBox( {bool? hasCancel}) { dialogManager.dismissAll(); List buttons = []; + bool hasOk = false; + submit() { + dialogManager.dismissAll(); + // https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263 + if (!type.contains("custom")) { + closeConnection(); + } + } + + cancel() { + dialogManager.dismissAll(); + } + if (type != "connecting" && type != "success" && !type.contains("nook")) { - buttons.insert( - 0, - msgBoxButton(translate('OK'), () { - dialogManager.dismissAll(); - // https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263 - if (!type.contains("custom")) { - closeConnection(); - } - })); + hasOk = true; + buttons.insert(0, msgBoxButton(translate('OK'), submit)); } hasCancel ??= !type.contains("error") && !type.contains("nocancel") && type != "restarting"; if (hasCancel) { - buttons.insert( - 0, - msgBoxButton(translate('Cancel'), () { - dialogManager.dismissAll(); - })); + buttons.insert(0, msgBoxButton(translate('Cancel'), cancel)); } // TODO: test this button if (type.contains("hasclose")) { @@ -459,9 +500,12 @@ void msgBox( })); } dialogManager.show((setState, close) => CustomAlertDialog( - title: _msgBoxTitle(title), - content: Text(translate(text), style: TextStyle(fontSize: 15)), - actions: buttons)); + title: _msgBoxTitle(title), + content: Text(translate(text), style: const TextStyle(fontSize: 15)), + actions: buttons, + onSubmit: hasOk ? submit : null, + onCancel: hasCancel == true ? cancel : null, + )); } Widget msgBoxButton(String text, void Function() onPressed) { @@ -479,15 +523,19 @@ Widget msgBoxButton(String text, void Function() onPressed) { Text(translate(text), style: TextStyle(color: MyTheme.accent)))); } -Widget _msgBoxTitle(String title) => Text(translate(title), style: TextStyle(fontSize: 21)); +Widget _msgBoxTitle(String title) => + Text(translate(title), style: TextStyle(fontSize: 21)); void msgBoxCommon(OverlayDialogManager dialogManager, String title, - Widget content, List buttons) { + Widget content, List buttons, + {bool hasCancel = true}) { dialogManager.dismissAll(); dialogManager.show((setState, close) => CustomAlertDialog( - title: _msgBoxTitle(title), - content: content, - actions: buttons)); + title: _msgBoxTitle(title), + content: content, + actions: buttons, + onCancel: hasCancel ? close : null, + )); } Color str2color(String str, [alpha = 0xFF]) { diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index fe363c4c9..e4f6527ca 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -167,7 +167,7 @@ class _ConnectionPageState extends State { }); var w = Container( width: 320 + 20 * 2, - padding: EdgeInsets.fromLTRB(20, 24, 20, 22), + padding: const EdgeInsets.fromLTRB(20, 24, 20, 22), decoration: BoxDecoration( color: MyTheme.color(context).bg, borderRadius: const BorderRadius.all(Radius.circular(13)), @@ -179,7 +179,7 @@ class _ConnectionPageState extends State { children: [ Text( translate('Control Remote Desktop'), - style: TextStyle(fontSize: 19, height: 1), + style: const TextStyle(fontSize: 19, height: 1), ), ], ).marginOnly(bottom: 15), @@ -192,11 +192,13 @@ class _ConnectionPageState extends State { enableSuggestions: false, keyboardType: TextInputType.visiblePassword, focusNode: focusNode, - style: TextStyle( + style: const TextStyle( fontFamily: 'WorkSans', fontSize: 22, height: 1, ), + maxLines: 1, + cursorColor: MyTheme.color(context).text!, decoration: InputDecoration( hintText: inputFocused.value ? null @@ -206,14 +208,18 @@ class _ConnectionPageState extends State { border: OutlineInputBorder( borderRadius: BorderRadius.zero, borderSide: BorderSide( - color: MyTheme.color(context).placeholder!)), - focusedBorder: OutlineInputBorder( + color: MyTheme.color(context).border!)), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.zero, + borderSide: BorderSide( + color: MyTheme.color(context).border!)), + focusedBorder: const OutlineInputBorder( borderRadius: BorderRadius.zero, borderSide: BorderSide(color: MyTheme.button, width: 3), ), isDense: true, - contentPadding: EdgeInsets.symmetric( + contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 12)), controller: _idController, inputFormatters: [IDTextInputFormatter()], @@ -266,7 +272,7 @@ class _ConnectionPageState extends State { ).marginSymmetric(horizontal: 12), ), )), - SizedBox( + const SizedBox( width: 17, ), Obx( @@ -311,7 +317,8 @@ class _ConnectionPageState extends State { ), ); return Center( - child: Container(constraints: BoxConstraints(maxWidth: 600), child: w)); + child: Container( + constraints: const BoxConstraints(maxWidth: 600), child: w)); } @override @@ -661,71 +668,69 @@ class _ConnectionPageState extends State { var field = ""; var msg = ""; var isInProgress = false; + TextEditingController controller = TextEditingController(text: field); + gFFI.dialogManager.show((setState, close) { + submit() async { + setState(() { + msg = ""; + isInProgress = true; + }); + field = controller.text.trim(); + if (field.isEmpty) { + // pass + } else { + final ids = field.trim().split(RegExp(r"[\s,;\n]+")); + field = ids.join(','); + for (final newId in ids) { + if (gFFI.abModel.idContainBy(newId)) { + continue; + } + gFFI.abModel.addId(newId); + } + await gFFI.abModel.updateAb(); + this.setState(() {}); + // final currentPeers + } + close(); + } + return CustomAlertDialog( title: Text(translate("Add ID")), content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(translate("whitelist_sep")), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ Expanded( child: TextField( - onChanged: (s) { - field = s; - }, - maxLines: null, - decoration: InputDecoration( - border: OutlineInputBorder(), - errorText: msg.isEmpty ? null : translate(msg), - ), - controller: TextEditingController(text: field), - ), + maxLines: null, + decoration: InputDecoration( + border: const OutlineInputBorder(), + errorText: msg.isEmpty ? null : translate(msg), + ), + controller: controller, + focusNode: FocusNode()..requestFocus()), ), ], ), - SizedBox( + const SizedBox( height: 4.0, ), - Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()) ], ), actions: [ - TextButton( - onPressed: () { - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - setState(() { - msg = ""; - isInProgress = true; - }); - field = field.trim(); - if (field.isEmpty) { - // pass - } else { - final ids = field.trim().split(RegExp(r"[\s,;\n]+")); - field = ids.join(','); - for (final newId in ids) { - if (gFFI.abModel.idContainBy(newId)) { - continue; - } - gFFI.abModel.addId(newId); - } - await gFFI.abModel.updateAb(); - this.setState(() {}); - // final currentPeers - } - close(); - }, - child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], + onSubmit: submit, + onCancel: close, ); }); } @@ -734,67 +739,65 @@ class _ConnectionPageState extends State { var field = ""; var msg = ""; var isInProgress = false; + TextEditingController controller = TextEditingController(text: field); gFFI.dialogManager.show((setState, close) { + submit() async { + setState(() { + msg = ""; + isInProgress = true; + }); + field = controller.text.trim(); + if (field.isEmpty) { + // pass + } else { + final tags = field.trim().split(RegExp(r"[\s,;\n]+")); + field = tags.join(','); + for (final tag in tags) { + gFFI.abModel.addTag(tag); + } + await gFFI.abModel.updateAb(); + // final currentPeers + } + close(); + } + return CustomAlertDialog( title: Text(translate("Add Tag")), content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(translate("whitelist_sep")), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ Expanded( child: TextField( - onChanged: (s) { - field = s; - }, maxLines: null, decoration: InputDecoration( - border: OutlineInputBorder(), + border: const OutlineInputBorder(), errorText: msg.isEmpty ? null : translate(msg), ), - controller: TextEditingController(text: field), + controller: controller, + focusNode: FocusNode()..requestFocus(), ), ), ], ), - SizedBox( + const SizedBox( height: 4.0, ), - Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()) ], ), actions: [ - TextButton( - onPressed: () { - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - setState(() { - msg = ""; - isInProgress = true; - }); - field = field.trim(); - if (field.isEmpty) { - // pass - } else { - final tags = field.trim().split(RegExp(r"[\s,;\n]+")); - field = tags.join(','); - for (final tag in tags) { - gFFI.abModel.addTag(tag); - } - await gFFI.abModel.updateAb(); - // final currentPeers - } - close(); - }, - child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], + onSubmit: submit, + onCancel: close, ); }); } @@ -806,13 +809,23 @@ class _ConnectionPageState extends State { var selectedTag = gFFI.abModel.getPeerTags(id).obs; gFFI.dialogManager.show((setState, close) { + submit() async { + setState(() { + isInProgress = true; + }); + gFFI.abModel.changeTagForPeer(id, selectedTag); + await gFFI.abModel.updateAb(); + close(); + } + return CustomAlertDialog( title: Text(translate("Edit Tag")), content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + padding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Wrap( children: tags .map((e) => buildTag(e, selectedTag, onTap: () { @@ -825,26 +838,16 @@ class _ConnectionPageState extends State { .toList(growable: false), ), ), - Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()) ], ), actions: [ - TextButton( - onPressed: () { - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - setState(() { - isInProgress = true; - }); - gFFI.abModel.changeTagForPeer(id, selectedTag); - await gFFI.abModel.updateAb(); - close(); - }, - child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], + onSubmit: submit, + onCancel: close, ); }); } diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 5a082a8fd..0ccb86d1f 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -55,7 +55,7 @@ class _DesktopHomePageState extends State return Row( children: [ buildServerInfo(context), - VerticalDivider( + const VerticalDivider( width: 1, thickness: 1, ), @@ -93,7 +93,7 @@ class _DesktopHomePageState extends State buildIDBoard(BuildContext context) { final model = gFFI.serverModel; return Container( - margin: EdgeInsets.only(left: 20, right: 16), + margin: const EdgeInsets.only(left: 20, right: 16), height: 52, child: Row( crossAxisAlignment: CrossAxisAlignment.baseline, @@ -101,7 +101,7 @@ class _DesktopHomePageState extends State children: [ Container( width: 2, - decoration: BoxDecoration(color: MyTheme.accent), + decoration: const BoxDecoration(color: MyTheme.accent), ), Expanded( child: Padding( @@ -109,7 +109,7 @@ class _DesktopHomePageState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( + SizedBox( height: 25, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -135,11 +135,11 @@ class _DesktopHomePageState extends State child: TextFormField( controller: model.serverId, readOnly: true, - decoration: InputDecoration( + decoration: const InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.only(bottom: 18), ), - style: TextStyle( + style: const TextStyle( fontSize: 22, ), ), @@ -642,76 +642,76 @@ class _DesktopHomePageState extends State var newId = ""; var msg = ""; var isInProgress = false; + TextEditingController controller = TextEditingController(); gFFI.dialogManager.show((setState, close) { + submit() async { + newId = controller.text.trim(); + setState(() { + msg = ""; + isInProgress = true; + bind.mainChangeId(newId: newId); + }); + + var status = await bind.mainGetAsyncStatus(); + while (status == " ") { + await Future.delayed(const Duration(milliseconds: 100)); + status = await bind.mainGetAsyncStatus(); + } + if (status.isEmpty) { + // ok + close(); + return; + } + setState(() { + isInProgress = false; + msg = translate(status); + }); + } + return CustomAlertDialog( title: Text(translate("Change ID")), content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(translate("id_change_tip")), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ - Text("ID:").marginOnly(bottom: 16.0), - SizedBox( + const Text("ID:").marginOnly(bottom: 16.0), + const SizedBox( width: 24.0, ), Expanded( child: TextField( - onChanged: (s) { - newId = s; - }, decoration: InputDecoration( - border: OutlineInputBorder(), + border: const OutlineInputBorder(), errorText: msg.isEmpty ? null : translate(msg)), inputFormatters: [ LengthLimitingTextInputFormatter(16), // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true) ], maxLength: 16, + controller: controller, + focusNode: FocusNode()..requestFocus(), ), ), ], ), - SizedBox( + const SizedBox( height: 4.0, ), - Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()) ], ), actions: [ - TextButton( - onPressed: () { - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - setState(() { - msg = ""; - isInProgress = true; - bind.mainChangeId(newId: newId); - }); - - var status = await bind.mainGetAsyncStatus(); - while (status == " ") { - await Future.delayed(Duration(milliseconds: 100)); - status = await bind.mainGetAsyncStatus(); - } - if (status.isEmpty) { - // ok - close(); - return; - } - setState(() { - isInProgress = false; - msg = translate(status); - }); - }, - child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], + onSubmit: submit, + onCancel: close, ); }); } @@ -720,16 +720,16 @@ class _DesktopHomePageState extends State final appName = await bind.mainGetAppName(); final license = await bind.mainGetLicense(); final version = await bind.mainGetVersion(); - final linkStyle = TextStyle(decoration: TextDecoration.underline); + const linkStyle = TextStyle(decoration: TextDecoration.underline); gFFI.dialogManager.show((setState, close) { return CustomAlertDialog( title: Text("About $appName"), content: ConstrainedBox( - constraints: BoxConstraints(minWidth: 500), + constraints: const BoxConstraints(minWidth: 500), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( + const SizedBox( height: 8.0, ), Text("Version: $version").marginSymmetric(vertical: 4.0), @@ -737,7 +737,7 @@ class _DesktopHomePageState extends State onTap: () { launchUrlString("https://rustdesk.com/privacy"); }, - child: Text( + child: const Text( "Privacy Statement", style: linkStyle, ).marginSymmetric(vertical: 4.0)), @@ -745,13 +745,14 @@ class _DesktopHomePageState extends State onTap: () { launchUrlString("https://rustdesk.com"); }, - child: Text( + child: const Text( "Website", style: linkStyle, ).marginSymmetric(vertical: 4.0)), Container( - decoration: BoxDecoration(color: Color(0xFF2c8cff)), - padding: EdgeInsets.symmetric(vertical: 24, horizontal: 8), + decoration: const BoxDecoration(color: Color(0xFF2c8cff)), + padding: + const EdgeInsets.symmetric(vertical: 24, horizontal: 8), child: Row( children: [ Expanded( @@ -760,9 +761,9 @@ class _DesktopHomePageState extends State children: [ Text( "Copyright © 2022 Purslane Ltd.\n$license", - style: TextStyle(color: Colors.white), + style: const TextStyle(color: Colors.white), ), - Text( + const Text( "Made with heart in this chaotic world!", style: TextStyle( fontWeight: FontWeight.w800, @@ -778,12 +779,10 @@ class _DesktopHomePageState extends State ), ), actions: [ - TextButton( - onPressed: () async { - close(); - }, - child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate("OK"))), ], + onSubmit: close, + onCancel: close, ); }); } @@ -815,118 +814,124 @@ Future loginDialog() async { var isInProgress = false; var completer = Completer(); gFFI.dialogManager.show((setState, close) { + submit() async { + setState(() { + userNameMsg = ""; + passMsg = ""; + isInProgress = true; + }); + cancel() { + setState(() { + isInProgress = false; + }); + } + + userName = userContontroller.text; + pass = pwdController.text; + if (userName.isEmpty) { + userNameMsg = translate("Username missed"); + cancel(); + return; + } + if (pass.isEmpty) { + passMsg = translate("Password missed"); + cancel(); + return; + } + try { + final resp = await gFFI.userModel.login(userName, pass); + if (resp.containsKey('error')) { + passMsg = resp['error']; + cancel(); + return; + } + // {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w, + // token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}} + debugPrint("$resp"); + completer.complete(true); + } catch (err) { + // ignore: avoid_print + print(err.toString()); + cancel(); + return; + } + close(); + } + + cancel() { + completer.complete(false); + close(); + } + return CustomAlertDialog( title: Text(translate("Login")), content: ConstrainedBox( - constraints: BoxConstraints(minWidth: 500), + constraints: const BoxConstraints(minWidth: 500), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text( "${translate('Username')}:", textAlign: TextAlign.start, ).marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( decoration: InputDecoration( - border: OutlineInputBorder(), + border: const OutlineInputBorder(), errorText: userNameMsg.isNotEmpty ? userNameMsg : null), controller: userContontroller, + focusNode: FocusNode()..requestFocus(), ), ), ], ), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text("${translate('Password')}:") .marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( obscureText: true, decoration: InputDecoration( - border: OutlineInputBorder(), + border: const OutlineInputBorder(), errorText: passMsg.isNotEmpty ? passMsg : null), controller: pwdController, ), ), ], ), - SizedBox( + const SizedBox( height: 4.0, ), - Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()) ], ), ), actions: [ - TextButton( - onPressed: () { - completer.complete(false); - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - setState(() { - userNameMsg = ""; - passMsg = ""; - isInProgress = true; - }); - final cancel = () { - setState(() { - isInProgress = false; - }); - }; - userName = userContontroller.text; - pass = pwdController.text; - if (userName.isEmpty) { - userNameMsg = translate("Username missed"); - cancel(); - return; - } - if (pass.isEmpty) { - passMsg = translate("Password missed"); - cancel(); - return; - } - try { - final resp = await gFFI.userModel.login(userName, pass); - if (resp.containsKey('error')) { - passMsg = resp['error']; - cancel(); - return; - } - // {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w, - // token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}} - debugPrint("$resp"); - completer.complete(true); - } catch (err) { - print(err.toString()); - cancel(); - return; - } - close(); - }, - child: Text(translate("OK"))), + TextButton(onPressed: cancel, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], + onSubmit: submit, + onCancel: cancel, ); }); return completer.future; @@ -940,55 +945,78 @@ void setPasswordDialog() async { var errMsg1 = ""; gFFI.dialogManager.show((setState, close) { + submit() { + setState(() { + errMsg0 = ""; + errMsg1 = ""; + }); + final pass = p0.text.trim(); + if (pass.length < 6) { + setState(() { + errMsg0 = translate("Too short, at least 6 characters."); + }); + return; + } + if (p1.text.trim() != pass) { + setState(() { + errMsg1 = translate("The confirmation is not identical."); + }); + return; + } + bind.mainSetPermanentPassword(password: pass); + close(); + } + return CustomAlertDialog( title: Text(translate("Set Password")), content: ConstrainedBox( - constraints: BoxConstraints(minWidth: 500), + constraints: const BoxConstraints(minWidth: 500), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text( "${translate('Password')}:", textAlign: TextAlign.start, ).marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( obscureText: true, decoration: InputDecoration( - border: OutlineInputBorder(), + border: const OutlineInputBorder(), errorText: errMsg0.isNotEmpty ? errMsg0 : null), controller: p0, + focusNode: FocusNode()..requestFocus(), ), ), ], ), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text("${translate('Confirmation')}:") .marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( obscureText: true, decoration: InputDecoration( - border: OutlineInputBorder(), + border: const OutlineInputBorder(), errorText: errMsg1.isNotEmpty ? errMsg1 : null), controller: p1, ), @@ -999,35 +1027,11 @@ void setPasswordDialog() async { ), ), actions: [ - TextButton( - onPressed: () { - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () { - setState(() { - errMsg0 = ""; - errMsg1 = ""; - }); - final pass = p0.text.trim(); - if (pass.length < 6) { - setState(() { - errMsg0 = translate("Too short, at least 6 characters."); - }); - return; - } - if (p1.text.trim() != pass) { - setState(() { - errMsg1 = translate("The confirmation is not identical."); - }); - return; - } - bind.mainSetPermanentPassword(password: pass); - close(); - }, - child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], + onSubmit: submit, + onCancel: close, ); }); } diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 120f8bc7a..867c8a54c 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1038,52 +1038,117 @@ void changeServer() async { var keyController = TextEditingController(text: key); var isInProgress = false; + gFFI.dialogManager.show((setState, close) { + submit() async { + setState(() { + [idServerMsg, relayServerMsg, apiServerMsg].forEach((element) { + element = ""; + }); + isInProgress = true; + }); + cancel() { + setState(() { + isInProgress = false; + }); + } + + idServer = idController.text.trim(); + relayServer = relayController.text.trim(); + apiServer = apiController.text.trim().toLowerCase(); + key = keyController.text.trim(); + + if (idServer.isNotEmpty) { + idServerMsg = + translate(await bind.mainTestIfValidServer(server: idServer)); + if (idServerMsg.isEmpty) { + oldOptions['custom-rendezvous-server'] = idServer; + } else { + cancel(); + return; + } + } else { + oldOptions['custom-rendezvous-server'] = ""; + } + + if (relayServer.isNotEmpty) { + relayServerMsg = + translate(await bind.mainTestIfValidServer(server: relayServer)); + if (relayServerMsg.isEmpty) { + oldOptions['relay-server'] = relayServer; + } else { + cancel(); + return; + } + } else { + oldOptions['relay-server'] = ""; + } + + if (apiServer.isNotEmpty) { + if (apiServer.startsWith('http://') || + apiServer.startsWith("https://")) { + oldOptions['api-server'] = apiServer; + return; + } else { + apiServerMsg = translate("invalid_http"); + cancel(); + return; + } + } else { + oldOptions['api-server'] = ""; + } + // ok + oldOptions['key'] = key; + await bind.mainSetOptions(json: jsonEncode(oldOptions)); + close(); + } + return CustomAlertDialog( title: Text(translate("ID/Relay Server")), content: ConstrainedBox( - constraints: BoxConstraints(minWidth: 500), + constraints: const BoxConstraints(minWidth: 500), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text("${translate('ID Server')}:") .marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( decoration: InputDecoration( - border: OutlineInputBorder(), + border: const OutlineInputBorder(), errorText: idServerMsg.isNotEmpty ? idServerMsg : null), controller: idController, + focusNode: FocusNode()..requestFocus(), ), ), ], ), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text("${translate('Relay Server')}:") .marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( decoration: InputDecoration( - border: OutlineInputBorder(), + border: const OutlineInputBorder(), errorText: relayServerMsg.isNotEmpty ? relayServerMsg : null), controller: relayController, @@ -1091,22 +1156,22 @@ void changeServer() async { ), ], ), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text("${translate('API Server')}:") .marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( decoration: InputDecoration( - border: OutlineInputBorder(), + border: const OutlineInputBorder(), errorText: apiServerMsg.isNotEmpty ? apiServerMsg : null), controller: apiController, @@ -1114,21 +1179,21 @@ void changeServer() async { ), ], ), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text("${translate('Key')}:").marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( - decoration: InputDecoration( + decoration: const InputDecoration( border: OutlineInputBorder(), ), controller: keyController, @@ -1136,83 +1201,20 @@ void changeServer() async { ), ], ), - SizedBox( + const SizedBox( height: 4.0, ), - Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()) ], ), ), actions: [ - TextButton( - onPressed: () { - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - setState(() { - [idServerMsg, relayServerMsg, apiServerMsg].forEach((element) { - element = ""; - }); - isInProgress = true; - }); - final cancel = () { - setState(() { - isInProgress = false; - }); - }; - idServer = idController.text.trim(); - relayServer = relayController.text.trim(); - apiServer = apiController.text.trim().toLowerCase(); - key = keyController.text.trim(); - - if (idServer.isNotEmpty) { - idServerMsg = translate( - await bind.mainTestIfValidServer(server: idServer)); - if (idServerMsg.isEmpty) { - oldOptions['custom-rendezvous-server'] = idServer; - } else { - cancel(); - return; - } - } else { - oldOptions['custom-rendezvous-server'] = ""; - } - - if (relayServer.isNotEmpty) { - relayServerMsg = translate( - await bind.mainTestIfValidServer(server: relayServer)); - if (relayServerMsg.isEmpty) { - oldOptions['relay-server'] = relayServer; - } else { - cancel(); - return; - } - } else { - oldOptions['relay-server'] = ""; - } - - if (apiServer.isNotEmpty) { - if (apiServer.startsWith('http://') || - apiServer.startsWith("https://")) { - oldOptions['api-server'] = apiServer; - return; - } else { - apiServerMsg = translate("invalid_http"); - cancel(); - return; - } - } else { - oldOptions['api-server'] = ""; - } - // ok - oldOptions['key'] = key; - await bind.mainSetOptions(json: jsonEncode(oldOptions)); - close(); - }, - child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], + onSubmit: submit, + onCancel: close, ); }); } @@ -1231,27 +1233,28 @@ void changeWhiteList() async { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(translate("whitelist_sep")), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ Expanded( child: TextField( - maxLines: null, - decoration: InputDecoration( - border: OutlineInputBorder(), - errorText: msg.isEmpty ? null : translate(msg), - ), - controller: controller, - ), + maxLines: null, + decoration: InputDecoration( + border: const OutlineInputBorder(), + errorText: msg.isEmpty ? null : translate(msg), + ), + controller: controller, + focusNode: FocusNode()..requestFocus()), ), ], ), - SizedBox( + const SizedBox( height: 4.0, ), - Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()) ], ), actions: [ @@ -1277,7 +1280,7 @@ void changeWhiteList() async { final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$"); for (final ip in ips) { if (!ipMatch.hasMatch(ip)) { - msg = translate("Invalid IP") + " $ip"; + msg = "${translate("Invalid IP")} $ip"; setState(() { isInProgress = false; }); @@ -1292,6 +1295,7 @@ void changeWhiteList() async { }, child: Text(translate("OK"))), ], + onCancel: close, ); }); } @@ -1314,50 +1318,80 @@ void changeSocks5Proxy() async { var isInProgress = false; gFFI.dialogManager.show((setState, close) { + submit() async { + setState(() { + proxyMsg = ""; + isInProgress = true; + }); + cancel() { + setState(() { + isInProgress = false; + }); + } + + proxy = proxyController.text.trim(); + username = userController.text.trim(); + password = pwdController.text.trim(); + + if (proxy.isNotEmpty) { + proxyMsg = translate(await bind.mainTestIfValidServer(server: proxy)); + if (proxyMsg.isEmpty) { + // ignore + } else { + cancel(); + return; + } + } + await bind.mainSetSocks( + proxy: proxy, username: username, password: password); + close(); + } + return CustomAlertDialog( title: Text(translate("Socks5 Proxy")), content: ConstrainedBox( - constraints: BoxConstraints(minWidth: 500), + constraints: const BoxConstraints(minWidth: 500), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text("${translate('Hostname')}:") .marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( decoration: InputDecoration( - border: OutlineInputBorder(), + border: const OutlineInputBorder(), errorText: proxyMsg.isNotEmpty ? proxyMsg : null), controller: proxyController, + focusNode: FocusNode()..requestFocus(), ), ), ], ), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text("${translate('Username')}:") .marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( - decoration: InputDecoration( + decoration: const InputDecoration( border: OutlineInputBorder(), ), controller: userController, @@ -1365,21 +1399,21 @@ void changeSocks5Proxy() async { ), ], ), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text("${translate('Password')}:") .marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( - decoration: InputDecoration( + decoration: const InputDecoration( border: OutlineInputBorder(), ), controller: pwdController, @@ -1387,50 +1421,20 @@ void changeSocks5Proxy() async { ), ], ), - SizedBox( + const SizedBox( height: 8.0, ), - Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()) ], ), ), actions: [ - TextButton( - onPressed: () { - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - setState(() { - proxyMsg = ""; - isInProgress = true; - }); - final cancel = () { - setState(() { - isInProgress = false; - }); - }; - proxy = proxyController.text.trim(); - username = userController.text.trim(); - password = pwdController.text.trim(); - - if (proxy.isNotEmpty) { - proxyMsg = - translate(await bind.mainTestIfValidServer(server: proxy)); - if (proxyMsg.isEmpty) { - // ignore - } else { - cancel(); - return; - } - } - await bind.mainSetSocks( - proxy: proxy, username: username, password: password); - close(); - }, - child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], + onSubmit: submit, + onCancel: close, ); }); } diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart index 874a71dcf..0546f0503 100644 --- a/flutter/lib/desktop/pages/desktop_tab_page.dart +++ b/flutter/lib/desktop/pages/desktop_tab_page.dart @@ -37,25 +37,33 @@ class _DesktopTabPageState extends State { RxBool fullscreen = false.obs; Get.put(fullscreen, tag: 'fullscreen'); return Obx(() => DragToResizeArea( - resizeEdgeSize: fullscreen.value ? 1.0 : 8.0, - child: Container( - decoration: BoxDecoration( - border: Border.all(color: MyTheme.color(context).border!)), - child: Scaffold( - backgroundColor: MyTheme.color(context).bg, - body: DesktopTab( - controller: tabController, - theme: dark ? TarBarTheme.dark() : TarBarTheme.light(), - tail: ActionIcon( - message: 'Settings', - icon: IconFont.menu, - theme: dark ? TarBarTheme.dark() : TarBarTheme.light(), - onTap: onAddSetting, - is_close: false, - ), - )), - ), - )); + resizeEdgeSize: fullscreen.value ? 1.0 : 8.0, + child: Container( + decoration: BoxDecoration( + border: Border.all(color: MyTheme.color(context).border!)), + child: Overlay(initialEntries: [ + OverlayEntry(builder: (context) { + gFFI.dialogManager.setOverlayState(Overlay.of(context)); + return Scaffold( + backgroundColor: MyTheme.color(context).bg, + body: DesktopTab( + controller: tabController, + theme: dark + ? const TarBarTheme.dark() + : const TarBarTheme.light(), + tail: ActionIcon( + message: 'Settings', + icon: IconFont.menu, + theme: dark + ? const TarBarTheme.dark() + : const TarBarTheme.light(), + onTap: onAddSetting, + is_close: false, + ), + )); + }) + ]), + ))); } void onAddSetting() { diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 4a2f11553..b13f40a5f 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -642,47 +642,51 @@ class _FileManagerPageState extends State IconButton( onPressed: () { final name = TextEditingController(); - _ffi.dialogManager - .show((setState, close) => CustomAlertDialog( - title: Text(translate("Create Folder")), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextFormField( - decoration: InputDecoration( - labelText: translate( - "Please enter the folder name"), - ), - controller: name, - ), - ], - ), - actions: [ - TextButton( - style: flatButtonStyle, - onPressed: () => close(false), - child: Text(translate("Cancel"))), - ElevatedButton( - style: flatButtonStyle, - onPressed: () { - if (name.value.text.isNotEmpty) { - model.createDir( - PathUtil.join( - model - .getCurrentDir( - isLocal) - .path, - name.value.text, - model.getCurrentIsWindows( - isLocal)), - isLocal: isLocal); - close(); - } - }, - child: Text(translate("OK"))) - ])); + _ffi.dialogManager.show((setState, close) { + submit() { + if (name.value.text.isNotEmpty) { + model.createDir( + PathUtil.join( + model.getCurrentDir(isLocal).path, + name.value.text, + model.getCurrentIsWindows(isLocal)), + isLocal: isLocal); + close(); + } + } + + cancel() => close(false); + return CustomAlertDialog( + title: Text(translate("Create Folder")), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + decoration: InputDecoration( + labelText: translate( + "Please enter the folder name"), + ), + controller: name, + focusNode: FocusNode()..requestFocus(), + ), + ], + ), + actions: [ + TextButton( + style: flatButtonStyle, + onPressed: cancel, + child: Text(translate("Cancel"))), + ElevatedButton( + style: flatButtonStyle, + onPressed: submit, + child: Text(translate("OK"))) + ], + onSubmit: submit, + onCancel: cancel, + ); + }); }, - icon: Icon(Icons.create_new_folder_outlined)), + icon: const Icon(Icons.create_new_folder_outlined)), IconButton( onPressed: () async { final items = isLocal diff --git a/flutter/lib/desktop/pages/port_forward_page.dart b/flutter/lib/desktop/pages/port_forward_page.dart index 6cfd0cdb2..28ee0d70e 100644 --- a/flutter/lib/desktop/pages/port_forward_page.dart +++ b/flutter/lib/desktop/pages/port_forward_page.dart @@ -70,38 +70,45 @@ class _PortForwardPageState extends State @override Widget build(BuildContext context) { super.build(context); - return Scaffold( - backgroundColor: MyTheme.color(context).grayBg, - body: FutureBuilder(future: () async { - if (!isRdp) { - refreshTunnelConfig(); - } - }(), builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - return Container( - decoration: BoxDecoration( - border: Border.all( - width: 20, color: MyTheme.color(context).grayBg!)), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - buildPrompt(context), - Flexible( - child: Container( - decoration: BoxDecoration( - color: MyTheme.color(context).bg, - border: Border.all(width: 1, color: MyTheme.border)), - child: - widget.isRDP ? buildRdp(context) : buildTunnel(context), - ), + return Overlay(initialEntries: [ + OverlayEntry(builder: (context) { + _ffi.dialogManager.setOverlayState(Overlay.of(context)); + return Scaffold( + backgroundColor: MyTheme.color(context).grayBg, + body: FutureBuilder(future: () async { + if (!isRdp) { + refreshTunnelConfig(); + } + }(), builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return Container( + decoration: BoxDecoration( + border: Border.all( + width: 20, color: MyTheme.color(context).grayBg!)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + buildPrompt(context), + Flexible( + child: Container( + decoration: BoxDecoration( + color: MyTheme.color(context).bg, + border: + Border.all(width: 1, color: MyTheme.border)), + child: widget.isRDP + ? buildRdp(context) + : buildTunnel(context), + ), + ), + ], ), - ], - ), - ); - } - return const Offstage(); - }), - ); + ); + } + return const Offstage(); + }), + ); + }) + ]); } buildPrompt(BuildContext context) { diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index 13cf02699..1bff02508 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -563,47 +563,47 @@ abstract class BasePeerCard extends StatelessWidget { } } gFFI.dialogManager.show((setState, close) { + submit() async { + isInProgress.value = true; + name = controller.text; + await bind.mainSetPeerOption(id: id, key: 'alias', value: name); + if (isAddressBook) { + gFFI.abModel.setPeerOption(id, 'alias', name); + await gFFI.abModel.updateAb(); + } + alias.value = await bind.mainGetPeerOption(id: peer.id, key: 'alias'); + close(); + isInProgress.value = false; + } + return CustomAlertDialog( title: Text(translate('Rename')), content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + padding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Form( child: TextFormField( controller: controller, - decoration: InputDecoration(border: OutlineInputBorder()), + focusNode: FocusNode()..requestFocus(), + decoration: + const InputDecoration(border: OutlineInputBorder()), ), ), ), Obx(() => Offstage( offstage: isInProgress.isFalse, - child: LinearProgressIndicator())), + child: const LinearProgressIndicator())), ], ), actions: [ - TextButton( - onPressed: () { - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - isInProgress.value = true; - name = controller.text; - await bind.mainSetPeerOption(id: id, key: 'alias', value: name); - if (isAddressBook) { - gFFI.abModel.setPeerOption(id, 'alias', name); - await gFFI.abModel.updateAb(); - } - alias.value = - await bind.mainGetPeerOption(id: peer.id, key: 'alias'); - close(); - isInProgress.value = false; - }, - child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], + onSubmit: submit, + onCancel: close, ); }); } @@ -750,13 +750,23 @@ class AddressBookPeerCard extends BasePeerCard { var selectedTag = gFFI.abModel.getPeerTags(id).obs; gFFI.dialogManager.show((setState, close) { + submit() async { + setState(() { + isInProgress = true; + }); + gFFI.abModel.changeTagForPeer(id, selectedTag); + await gFFI.abModel.updateAb(); + close(); + } + return CustomAlertDialog( title: Text(translate("Edit Tag")), content: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + padding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Wrap( children: tags .map((e) => _buildTag(e, selectedTag, onTap: () { @@ -769,26 +779,16 @@ class AddressBookPeerCard extends BasePeerCard { .toList(growable: false), ), ), - Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) + Offstage( + offstage: !isInProgress, child: const LinearProgressIndicator()) ], ), actions: [ - TextButton( - onPressed: () { - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - setState(() { - isInProgress = true; - }); - gFFI.abModel.changeTagForPeer(id, selectedTag); - await gFFI.abModel.updateAb(); - close(); - }, - child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], + onSubmit: submit, + onCancel: close, ); }); } @@ -871,25 +871,35 @@ void _rdpDialog(String id) async { RxBool secure = true.obs; gFFI.dialogManager.show((setState, close) { + submit() async { + await bind.mainSetPeerOption( + id: id, key: 'rdp_port', value: portController.text.trim()); + await bind.mainSetPeerOption( + id: id, key: 'rdp_username', value: userController.text); + await bind.mainSetPeerOption( + id: id, key: 'rdp_password', value: passwordContorller.text); + close(); + } + return CustomAlertDialog( - title: Text('RDP ' + translate('Settings')), + title: Text('RDP ${translate('Settings')}'), content: ConstrainedBox( - constraints: BoxConstraints(minWidth: 500), + constraints: const BoxConstraints(minWidth: 500), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text( "${translate('Port')}:", textAlign: TextAlign.start, ).marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( @@ -898,52 +908,54 @@ void _rdpDialog(String id) async { FilteringTextInputFormatter.allow(RegExp( r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')) ], - decoration: InputDecoration( + decoration: const InputDecoration( border: OutlineInputBorder(), hintText: '3389'), controller: portController, + focusNode: FocusNode()..requestFocus(), ), ), ], ), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text( "${translate('Username')}:", textAlign: TextAlign.start, ).marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: TextField( - decoration: InputDecoration(border: OutlineInputBorder()), + decoration: + const InputDecoration(border: OutlineInputBorder()), controller: userController, ), ), ], ), - SizedBox( + const SizedBox( height: 8.0, ), Row( children: [ ConstrainedBox( - constraints: BoxConstraints(minWidth: 100), + constraints: const BoxConstraints(minWidth: 100), child: Text("${translate('Password')}:") .marginOnly(bottom: 16.0)), - SizedBox( + const SizedBox( width: 24.0, ), Expanded( child: Obx(() => TextField( obscureText: secure.value, decoration: InputDecoration( - border: OutlineInputBorder(), + border: const OutlineInputBorder(), suffixIcon: IconButton( onPressed: () => secure.value = !secure.value, icon: Icon(secure.value @@ -958,23 +970,11 @@ void _rdpDialog(String id) async { ), ), actions: [ - TextButton( - onPressed: () { - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - await bind.mainSetPeerOption( - id: id, key: 'rdp_port', value: portController.text.trim()); - await bind.mainSetPeerOption( - id: id, key: 'rdp_username', value: userController.text); - await bind.mainSetPeerOption( - id: id, key: 'rdp_password', value: passwordContorller.text); - close(); - }, - child: Text(translate("OK"))), + TextButton(onPressed: close, child: Text(translate("Cancel"))), + TextButton(onPressed: submit, child: Text(translate("OK"))), ], + onSubmit: submit, + onCancel: close, ); }); } diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index dc3c249f0..c83f61a17 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -596,46 +596,49 @@ void showSetOSPassword( var autoLogin = await bind.sessionGetOption(id: id, arg: "auto-login") != ""; controller.text = password; dialogManager.show((setState, close) { + submit() { + var text = controller.text.trim(); + bind.sessionPeerOption(id: id, name: "os-password", value: text); + bind.sessionPeerOption( + id: id, name: "auto-login", value: autoLogin ? 'Y' : ''); + if (text != "" && login) { + bind.sessionInputOsPassword(id: id, value: text); + } + close(); + } + return CustomAlertDialog( - title: Text(translate('OS Password')), - content: Column(mainAxisSize: MainAxisSize.min, children: [ - PasswordWidget(controller: controller), - CheckboxListTile( - contentPadding: const EdgeInsets.all(0), - dense: true, - controlAffinity: ListTileControlAffinity.leading, - title: Text( - translate('Auto Login'), - ), - value: autoLogin, - onChanged: (v) { - if (v == null) return; - setState(() => autoLogin = v); - }, + title: Text(translate('OS Password')), + content: Column(mainAxisSize: MainAxisSize.min, children: [ + PasswordWidget(controller: controller), + CheckboxListTile( + contentPadding: const EdgeInsets.all(0), + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + translate('Auto Login'), ), - ]), - actions: [ - TextButton( - style: flatButtonStyle, - onPressed: () { - close(); - }, - child: Text(translate('Cancel')), - ), - TextButton( - style: flatButtonStyle, - onPressed: () { - var text = controller.text.trim(); - bind.sessionPeerOption(id: id, name: "os-password", value: text); - bind.sessionPeerOption( - id: id, name: "auto-login", value: autoLogin ? 'Y' : ''); - if (text != "" && login) { - bind.sessionInputOsPassword(id: id, value: text); - } - close(); - }, - child: Text(translate('OK')), - ), - ]); + value: autoLogin, + onChanged: (v) { + if (v == null) return; + setState(() => autoLogin = v); + }, + ), + ]), + actions: [ + TextButton( + style: flatButtonStyle, + onPressed: close, + child: Text(translate('Cancel')), + ), + TextButton( + style: flatButtonStyle, + onPressed: submit, + child: Text(translate('OK')), + ), + ], + onSubmit: submit, + onCancel: close, + ); }); } diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 1a19dd833..3c2b28ab0 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -462,22 +462,24 @@ class WindowActionPanel extends StatelessWidget { } closeConfirmDialog(Function() callback) async { - final res = await gFFI.dialogManager - .show((setState, close) => CustomAlertDialog( - title: Row(children: [ - Icon(Icons.warning_amber_sharp, - color: Colors.redAccent, size: 28), - SizedBox(width: 10), - Text(translate("Warning")), - ]), - content: Text(translate("Disconnect all devices?")), - actions: [ - TextButton( - onPressed: () => close(), child: Text(translate("Cancel"))), - ElevatedButton( - onPressed: () => close(true), child: Text(translate("OK"))), - ], - )); + final res = await gFFI.dialogManager.show((setState, close) { + submit() => close(true); + return CustomAlertDialog( + title: Row(children: [ + const Icon(Icons.warning_amber_sharp, + color: Colors.redAccent, size: 28), + const SizedBox(width: 10), + Text(translate("Warning")), + ]), + content: Text(translate("Disconnect all devices?")), + actions: [ + TextButton(onPressed: close, child: Text(translate("Cancel"))), + ElevatedButton(onPressed: submit, child: Text(translate("OK"))), + ], + onSubmit: submit, + onCancel: close, + ); + }); if (res == true) { callback(); } diff --git a/flutter/lib/models/file_model.dart b/flutter/lib/models/file_model.dart index 74c2cd515..dedca5efa 100644 --- a/flutter/lib/models/file_model.dart +++ b/flutter/lib/models/file_model.dart @@ -559,49 +559,55 @@ class FileModel extends ChangeNotifier { Future showRemoveDialog( String title, String content, bool showCheckbox) async { return await parent.target?.dialogManager.show( - (setState, Function(bool v) close) => CustomAlertDialog( - title: Row( - children: [ - Icon(Icons.warning, color: Colors.red), - SizedBox(width: 20), - Text(title) - ], - ), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text(content), - SizedBox(height: 5), - Text(translate("This is irreversible!"), - style: TextStyle(fontWeight: FontWeight.bold)), - showCheckbox - ? CheckboxListTile( - contentPadding: const EdgeInsets.all(0), - dense: true, - controlAffinity: ListTileControlAffinity.leading, - title: Text( - translate("Do this for all conflicts"), - ), - value: removeCheckboxRemember, - onChanged: (v) { - if (v == null) return; - setState(() => removeCheckboxRemember = v); - }, - ) - : SizedBox.shrink() - ]), - actions: [ - TextButton( - style: flatButtonStyle, - onPressed: () => close(false), - child: Text(translate("Cancel"))), - TextButton( - style: flatButtonStyle, - onPressed: () => close(true), - child: Text(translate("OK"))), - ]), - useAnimation: false); + (setState, Function(bool v) close) { + cancel() => close(false); + submit() => close(true); + return CustomAlertDialog( + title: Row( + children: [ + const Icon(Icons.warning, color: Colors.red), + const SizedBox(width: 20), + Text(title) + ], + ), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(content), + const SizedBox(height: 5), + Text(translate("This is irreversible!"), + style: const TextStyle(fontWeight: FontWeight.bold)), + showCheckbox + ? CheckboxListTile( + contentPadding: const EdgeInsets.all(0), + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + translate("Do this for all conflicts"), + ), + value: removeCheckboxRemember, + onChanged: (v) { + if (v == null) return; + setState(() => removeCheckboxRemember = v); + }, + ) + : const SizedBox.shrink() + ]), + actions: [ + TextButton( + style: flatButtonStyle, + onPressed: cancel, + child: Text(translate("Cancel"))), + TextButton( + style: flatButtonStyle, + onPressed: submit, + child: Text(translate("OK"))), + ], + onSubmit: submit, + onCancel: cancel, + ); + }, useAnimation: false); } bool fileConfirmCheckboxRemember = false; @@ -610,55 +616,59 @@ class FileModel extends ChangeNotifier { String title, String content, bool showCheckbox) async { fileConfirmCheckboxRemember = false; return await parent.target?.dialogManager.show( - (setState, Function(bool? v) close) => CustomAlertDialog( - title: Row( - children: [ - Icon(Icons.warning, color: Colors.red), - SizedBox(width: 20), - Text(title) - ], - ), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - translate( - "This file exists, skip or overwrite this file?"), - style: TextStyle(fontWeight: FontWeight.bold)), - SizedBox(height: 5), - Text(content), - showCheckbox - ? CheckboxListTile( - contentPadding: const EdgeInsets.all(0), - dense: true, - controlAffinity: ListTileControlAffinity.leading, - title: Text( - translate("Do this for all conflicts"), - ), - value: fileConfirmCheckboxRemember, - onChanged: (v) { - if (v == null) return; - setState(() => fileConfirmCheckboxRemember = v); - }, - ) - : SizedBox.shrink() - ]), - actions: [ - TextButton( - style: flatButtonStyle, - onPressed: () => close(false), - child: Text(translate("Cancel"))), - TextButton( - style: flatButtonStyle, - onPressed: () => close(null), - child: Text(translate("Skip"))), - TextButton( - style: flatButtonStyle, - onPressed: () => close(true), - child: Text(translate("OK"))), - ]), - useAnimation: false); + (setState, Function(bool? v) close) { + cancel() => close(false); + submit() => close(true); + return CustomAlertDialog( + title: Row( + children: [ + const Icon(Icons.warning, color: Colors.red), + const SizedBox(width: 20), + Text(title) + ], + ), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(translate("This file exists, skip or overwrite this file?"), + style: const TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 5), + Text(content), + showCheckbox + ? CheckboxListTile( + contentPadding: const EdgeInsets.all(0), + dense: true, + controlAffinity: ListTileControlAffinity.leading, + title: Text( + translate("Do this for all conflicts"), + ), + value: fileConfirmCheckboxRemember, + onChanged: (v) { + if (v == null) return; + setState(() => fileConfirmCheckboxRemember = v); + }, + ) + : const SizedBox.shrink() + ]), + actions: [ + TextButton( + style: flatButtonStyle, + onPressed: cancel, + child: Text(translate("Cancel"))), + TextButton( + style: flatButtonStyle, + onPressed: () => close(null), + child: Text(translate("Skip"))), + TextButton( + style: flatButtonStyle, + onPressed: submit, + child: Text(translate("OK"))), + ], + onSubmit: submit, + onCancel: cancel, + ); + }, useAnimation: false); } sendRemoveFile(String path, int fileNum, bool isLocal) { diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 5d23dc949..f78f8cf70 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -209,46 +209,48 @@ class ServerModel with ChangeNotifier { /// Toggle the screen sharing service. toggleService() async { if (_isStart) { - final res = await parent.target?.dialogManager - .show((setState, close) => CustomAlertDialog( - title: Row(children: [ - Icon(Icons.warning_amber_sharp, - color: Colors.redAccent, size: 28), - SizedBox(width: 10), - Text(translate("Warning")), - ]), - content: Text(translate("android_stop_service_tip")), - actions: [ - TextButton( - onPressed: () => close(), - child: Text(translate("Cancel"))), - ElevatedButton( - onPressed: () => close(true), - child: Text(translate("OK"))), - ], - )); + final res = + await parent.target?.dialogManager.show((setState, close) { + submit() => close(true); + return CustomAlertDialog( + title: Row(children: [ + const Icon(Icons.warning_amber_sharp, + color: Colors.redAccent, size: 28), + const SizedBox(width: 10), + Text(translate("Warning")), + ]), + content: Text(translate("android_stop_service_tip")), + actions: [ + TextButton(onPressed: close, child: Text(translate("Cancel"))), + ElevatedButton(onPressed: submit, child: Text(translate("OK"))), + ], + onSubmit: submit, + onCancel: close, + ); + }); if (res == true) { stopService(); } } else { - final res = await parent.target?.dialogManager - .show((setState, close) => CustomAlertDialog( - title: Row(children: [ - Icon(Icons.warning_amber_sharp, - color: Colors.redAccent, size: 28), - SizedBox(width: 10), - Text(translate("Warning")), - ]), - content: Text(translate("android_service_will_start_tip")), - actions: [ - TextButton( - onPressed: () => close(), - child: Text(translate("Cancel"))), - ElevatedButton( - onPressed: () => close(true), - child: Text(translate("OK"))), - ], - )); + final res = + await parent.target?.dialogManager.show((setState, close) { + submit() => close(true); + return CustomAlertDialog( + title: Row(children: [ + const Icon(Icons.warning_amber_sharp, + color: Colors.redAccent, size: 28), + const SizedBox(width: 10), + Text(translate("Warning")), + ]), + content: Text(translate("android_service_will_start_tip")), + actions: [ + TextButton(onPressed: close, child: Text(translate("Cancel"))), + ElevatedButton(onPressed: submit, child: Text(translate("OK"))), + ], + onSubmit: submit, + onCancel: close, + ); + }); if (res == true) { startService(); } @@ -388,49 +390,49 @@ class ServerModel with ChangeNotifier { } void showLoginDialog(Client client) { - parent.target?.dialogManager.show( - (setState, close) => CustomAlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(translate(client.isFileTransfer - ? "File Connection" - : "Screen Connection")), - IconButton( - onPressed: () { - close(); - }, - icon: Icon(Icons.close)) - ]), - content: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(translate("Do you accept?")), - clientInfo(client), - Text( - translate("android_new_connection_tip"), - style: TextStyle(color: Colors.black54), - ), - ], - ), - actions: [ - TextButton( - child: Text(translate("Dismiss")), - onPressed: () { - sendLoginResponse(client, false); - close(); - }), - ElevatedButton( - child: Text(translate("Accept")), - onPressed: () { - sendLoginResponse(client, true); - close(); - }), - ], + parent.target?.dialogManager.show((setState, close) { + cancel() { + sendLoginResponse(client, false); + close(); + } + + submit() { + sendLoginResponse(client, true); + close(); + } + + return CustomAlertDialog( + title: + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text(translate( + client.isFileTransfer ? "File Connection" : "Screen Connection")), + IconButton( + onPressed: () { + close(); + }, + icon: const Icon(Icons.close)) + ]), + content: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(translate("Do you accept?")), + clientInfo(client), + Text( + translate("android_new_connection_tip"), + style: const TextStyle(color: Colors.black54), ), - tag: getLoginDialogTag(client.id)); + ], + ), + actions: [ + TextButton(onPressed: cancel, child: Text(translate("Dismiss"))), + ElevatedButton(onPressed: submit, child: Text(translate("Accept"))), + ], + onSubmit: submit, + onCancel: cancel, + ); + }, tag: getLoginDialogTag(client.id)); } scrollToBottom() { @@ -563,24 +565,29 @@ String getLoginDialogTag(int id) { } showInputWarnAlert(FFI ffi) { - ffi.dialogManager.show((setState, close) => CustomAlertDialog( - title: Text(translate("How to get Android input permission?")), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(translate("android_input_permission_tip1")), - SizedBox(height: 10), - Text(translate("android_input_permission_tip2")), - ], - ), - actions: [ - TextButton(child: Text(translate("Cancel")), onPressed: close), - ElevatedButton( - child: Text(translate("Open System Setting")), - onPressed: () { - ffi.serverModel.initInput(); - close(); - }), + ffi.dialogManager.show((setState, close) { + submit() { + ffi.serverModel.initInput(); + close(); + } + + return CustomAlertDialog( + title: Text(translate("How to get Android input permission?")), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(translate("android_input_permission_tip1")), + const SizedBox(height: 10), + Text(translate("android_input_permission_tip2")), ], - )); + ), + actions: [ + TextButton(onPressed: close, child: Text(translate("Cancel"))), + ElevatedButton( + onPressed: submit, child: Text(translate("Open System Setting"))), + ], + onSubmit: submit, + onCancel: close, + ); + }); }