From 1b56304d9ad461c18b4ee4f9a08faf0036c4e011 Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 2 Sep 2022 17:19:44 +0800 Subject: [PATCH 1/5] format id Signed-off-by: 21pages --- .../lib/common/formatter/id_formatter.dart | 52 +++++++++++++- .../lib/desktop/pages/connection_page.dart | 71 ++++++++++--------- .../lib/desktop/widgets/peercard_widget.dart | 5 +- flutter/lib/models/server_model.dart | 13 ++-- 4 files changed, 99 insertions(+), 42 deletions(-) diff --git a/flutter/lib/common/formatter/id_formatter.dart b/flutter/lib/common/formatter/id_formatter.dart index 29aea84ff..c7ce14da4 100644 --- a/flutter/lib/common/formatter/id_formatter.dart +++ b/flutter/lib/common/formatter/id_formatter.dart @@ -1,4 +1,52 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; -/// TODO: Divide every 3 number to display ID -class IdFormController extends TextEditingController {} +class IDTextEditingController extends TextEditingController { + IDTextEditingController({String? text}) : super(text: text); + + String get id => trimID(value.text); + + set id(String newID) => text = formatID(newID); +} + +class IDTextInputFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + if (newValue.text.isEmpty) { + return newValue.copyWith(text: ''); + } else if (newValue.text.compareTo(oldValue.text) == 0) { + return newValue; + } else { + int selectionIndexFromTheRight = + newValue.text.length - newValue.selection.extentOffset; + String newID = formatID(newValue.text); + return TextEditingValue( + text: newID, + selection: TextSelection.collapsed( + offset: newID.length - selectionIndexFromTheRight, + ), + ); + } + } +} + +String formatID(String id) { + String id2 = id.replaceAll(' ', ''); + String newID = ''; + if (id2.length <= 3) { + newID = id2; + } else { + var n = id2.length; + var a = n % 3 != 0 ? n % 3 : 3; + newID = id2.substring(0, a); + for (var i = a; i < n; i += 3) { + newID += " ${id2.substring(i, i + 3)}"; + } + } + return newID; +} + +String trimID(String id) { + return id.replaceAll(' ', ''); +} diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 5fd6b4a28..fe363c4c9 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -12,6 +12,7 @@ import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher_string.dart'; import '../../common.dart'; +import '../../common/formatter/id_formatter.dart'; import '../../mobile/pages/scan_page.dart'; import '../../mobile/pages/settings_page.dart'; import '../../models/model.dart'; @@ -30,7 +31,7 @@ class ConnectionPage extends StatefulWidget { /// State for the connection page. class _ConnectionPageState extends State { /// Controller for the id input bar. - final _idController = TextEditingController(); + final _idController = IDTextEditingController(); /// Update url. If it's not null, means an update is available. final _updateUrl = ''; @@ -43,9 +44,9 @@ class _ConnectionPageState extends State { if (_idController.text.isEmpty) { () async { final lastRemoteId = await bind.mainGetLastRemoteId(); - if (lastRemoteId != _idController.text) { + if (lastRemoteId != _idController.id) { setState(() { - _idController.text = lastRemoteId; + _idController.id = lastRemoteId; }); } }(); @@ -110,7 +111,7 @@ class _ConnectionPageState extends State { /// Callback for the connect button. /// Connects to the selected peer. void onConnect({bool isFileTransfer = false}) { - final id = _idController.text.trim(); + final id = _idController.id; connect(id, isFileTransfer: isFileTransfer); } @@ -185,35 +186,41 @@ class _ConnectionPageState extends State { Row( children: [ Expanded( - child: TextField( - autocorrect: false, - enableSuggestions: false, - keyboardType: TextInputType.visiblePassword, - style: TextStyle( - fontFamily: 'WorkSans', - fontSize: 22, - height: 1, - ), - decoration: InputDecoration( - hintText: translate('Enter Remote ID'), - hintStyle: TextStyle( - color: MyTheme.color(context).placeholder), - border: OutlineInputBorder( + child: Obx( + () => TextField( + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.visiblePassword, + focusNode: focusNode, + style: TextStyle( + fontFamily: 'WorkSans', + fontSize: 22, + height: 1, + ), + decoration: InputDecoration( + hintText: inputFocused.value + ? null + : translate('Enter Remote ID'), + hintStyle: TextStyle( + color: MyTheme.color(context).placeholder), + border: OutlineInputBorder( + borderRadius: BorderRadius.zero, + borderSide: BorderSide( + color: MyTheme.color(context).placeholder!)), + focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.zero, - borderSide: BorderSide( - color: MyTheme.color(context).placeholder!)), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.zero, - borderSide: - BorderSide(color: MyTheme.button, width: 3), - ), - isDense: true, - contentPadding: - EdgeInsets.symmetric(horizontal: 10, vertical: 12)), - controller: _idController, - onSubmitted: (s) { - onConnect(); - }, + borderSide: + BorderSide(color: MyTheme.button, width: 3), + ), + isDense: true, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, vertical: 12)), + controller: _idController, + inputFormatters: [IDTextInputFormatter()], + onSubmitted: (s) { + onConnect(); + }, + ), ), ), ], diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index d9c0015f8..13cf02699 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -5,6 +5,7 @@ import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:get/get.dart'; import '../../common.dart'; +import '../../common/formatter/id_formatter.dart'; import '../../models/model.dart'; import '../../models/peer_model.dart'; import '../../models/platform_model.dart'; @@ -119,7 +120,7 @@ class _PeerCardState extends State<_PeerCard> ? Colors.green : Colors.yellow)), Text( - '${peer.id}', + formatID('${peer.id}'), style: TextStyle(fontWeight: FontWeight.w400), ), ]), @@ -240,7 +241,7 @@ class _PeerCardState extends State<_PeerCard> backgroundColor: peer.online ? Colors.green : Colors.yellow)), - Text(peer.id) + Text(formatID(peer.id)) ]).paddingSymmetric(vertical: 8), _actionMore(peer), ], diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 31c579f83..5d23dc949 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -7,6 +7,7 @@ import 'package:flutter_hbb/models/platform_model.dart'; import 'package:wakelock/wakelock.dart'; import '../common.dart'; +import '../common/formatter/id_formatter.dart'; import '../desktop/pages/server_page.dart' as Desktop; import '../desktop/widgets/tabbar_widget.dart'; import '../mobile/pages/server_page.dart'; @@ -29,7 +30,7 @@ class ServerModel with ChangeNotifier { String _temporaryPasswordLength = ""; late String _emptyIdShow; - late final TextEditingController _serverId; + late final IDTextEditingController _serverId; final _serverPasswd = TextEditingController(text: ""); final tabController = DesktopTabController(tabType: DesktopTabType.cm); @@ -88,7 +89,7 @@ class ServerModel with ChangeNotifier { ServerModel(this.parent) { _emptyIdShow = translate("Generating ..."); - _serverId = TextEditingController(text: this._emptyIdShow); + _serverId = IDTextEditingController(text: _emptyIdShow); Timer.periodic(Duration(seconds: 1), (timer) async { var status = await bind.mainGetOnlineStatue(); @@ -300,7 +301,7 @@ class ServerModel with ChangeNotifier { } _fetchID() async { - final old = _serverId.text; + final old = _serverId.id; var count = 0; const maxCount = 10; while (count < maxCount) { @@ -309,12 +310,12 @@ class ServerModel with ChangeNotifier { if (id.isEmpty) { continue; } else { - _serverId.text = id; + _serverId.id = id; } - debugPrint("fetch id again at $count:id:${_serverId.text}"); + debugPrint("fetch id again at $count:id:${_serverId.id}"); count++; - if (_serverId.text != old) { + if (_serverId.id != old) { break; } } From a553334157c6af108f1fd672aa2d102aa84bf87c Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 3 Sep 2022 18:19:50 +0800 Subject: [PATCH 2/5] 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, + ); + }); } From 62870e453ca0b876ca481dc67566d0d787d04e29 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sun, 4 Sep 2022 11:03:16 +0800 Subject: [PATCH 3/5] add tabbar theme extension to fix theme update failure after overlay added Signed-off-by: 21pages --- flutter/lib/common.dart | 10 +- .../desktop/pages/connection_tab_page.dart | 6 +- .../lib/desktop/pages/desktop_tab_page.dart | 8 +- .../desktop/pages/file_manager_tab_page.dart | 7 +- .../desktop/pages/port_forward_tab_page.dart | 6 +- flutter/lib/desktop/pages/server_page.dart | 1 - .../lib/desktop/widgets/tabbar_widget.dart | 262 ++++++++++-------- 7 files changed, 162 insertions(+), 138 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 4a0a0dc82..309ae9892 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -155,7 +155,7 @@ class MyTheme { brightness: Brightness.light, primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, - tabBarTheme: TabBarTheme( + tabBarTheme: const TabBarTheme( labelColor: Colors.black87, ), splashColor: Colors.transparent, @@ -163,13 +163,14 @@ class MyTheme { ).copyWith( extensions: >[ ColorThemeExtension.light, + TabbarTheme.light, ], ); static ThemeData darkTheme = ThemeData( brightness: Brightness.dark, primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, - tabBarTheme: TabBarTheme( + tabBarTheme: const TabBarTheme( labelColor: Colors.white70, ), splashColor: Colors.transparent, @@ -177,12 +178,17 @@ class MyTheme { ).copyWith( extensions: >[ ColorThemeExtension.dark, + TabbarTheme.dark, ], ); static ColorThemeExtension color(BuildContext context) { return Theme.of(context).extension()!; } + + static TabbarTheme tabbar(BuildContext context) { + return Theme.of(context).extension()!; + } } bool isDarkTheme() { diff --git a/flutter/lib/desktop/pages/connection_tab_page.dart b/flutter/lib/desktop/pages/connection_tab_page.dart index d9bc86fe2..8f5350792 100644 --- a/flutter/lib/desktop/pages/connection_tab_page.dart +++ b/flutter/lib/desktop/pages/connection_tab_page.dart @@ -83,7 +83,6 @@ class _ConnectionTabPageState extends State { @override Widget build(BuildContext context) { - final theme = isDarkTheme() ? TarBarTheme.dark() : TarBarTheme.light(); final RxBool fullscreen = Get.find(tag: 'fullscreen'); return Obx(() => SubWindowDragToResizeArea( resizeEdgeSize: fullscreen.value ? 1.0 : 8.0, @@ -95,14 +94,11 @@ class _ConnectionTabPageState extends State { backgroundColor: MyTheme.color(context).bg, body: Obx(() => DesktopTab( controller: tabController, - theme: theme, showTabBar: fullscreen.isFalse, onClose: () { tabController.clear(); }, - tail: AddButton( - theme: theme, - ).paddingOnly(left: 10), + tail: AddButton().paddingOnly(left: 10), pageViewBuilder: (pageView) { WindowController.fromWindowId(windowId()) .setFullscreen(fullscreen.isTrue); diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart index 0546f0503..87082284b 100644 --- a/flutter/lib/desktop/pages/desktop_tab_page.dart +++ b/flutter/lib/desktop/pages/desktop_tab_page.dart @@ -48,17 +48,11 @@ class _DesktopTabPageState extends State { 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, + isClose: false, ), )); }) diff --git a/flutter/lib/desktop/pages/file_manager_tab_page.dart b/flutter/lib/desktop/pages/file_manager_tab_page.dart index add5eed9f..18ea039a7 100644 --- a/flutter/lib/desktop/pages/file_manager_tab_page.dart +++ b/flutter/lib/desktop/pages/file_manager_tab_page.dart @@ -62,8 +62,6 @@ class _FileManagerTabPageState extends State { @override Widget build(BuildContext context) { - final theme = - isDarkTheme() ? const TarBarTheme.dark() : const TarBarTheme.light(); return SubWindowDragToResizeArea( windowId: windowId(), child: Container( @@ -73,13 +71,10 @@ class _FileManagerTabPageState extends State { backgroundColor: MyTheme.color(context).bg, body: DesktopTab( controller: tabController, - theme: theme, onClose: () { tabController.clear(); }, - tail: AddButton( - theme: theme, - ).paddingOnly(left: 10), + tail: AddButton().paddingOnly(left: 10), )), ), ); diff --git a/flutter/lib/desktop/pages/port_forward_tab_page.dart b/flutter/lib/desktop/pages/port_forward_tab_page.dart index 2340a4ca1..e0384b614 100644 --- a/flutter/lib/desktop/pages/port_forward_tab_page.dart +++ b/flutter/lib/desktop/pages/port_forward_tab_page.dart @@ -69,7 +69,6 @@ class _PortForwardTabPageState extends State { @override Widget build(BuildContext context) { - final theme = isDarkTheme() ? TarBarTheme.dark() : TarBarTheme.light(); return SubWindowDragToResizeArea( windowId: windowId(), child: Container( @@ -79,13 +78,10 @@ class _PortForwardTabPageState extends State { backgroundColor: MyTheme.color(context).bg, body: DesktopTab( controller: tabController, - theme: theme, onClose: () { tabController.clear(); }, - tail: AddButton( - theme: theme, - ).paddingOnly(left: 10), + tail: AddButton().paddingOnly(left: 10), )), ), ); diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart index f64adfca2..ac2fb7caa 100644 --- a/flutter/lib/desktop/pages/server_page.dart +++ b/flutter/lib/desktop/pages/server_page.dart @@ -118,7 +118,6 @@ class ConnectionManagerState extends State { ], ) : DesktopTab( - theme: isDarkTheme() ? TarBarTheme.dark() : TarBarTheme.light(), showTitle: false, showMaximize: false, showMinimize: true, diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 3c2b28ab0..5daa1aeb6 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'dart:math'; import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide TabBarTheme; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/main.dart'; @@ -158,8 +158,7 @@ class DesktopTabController { class TabThemeConf { double iconSize; - TarBarTheme theme; - TabThemeConf({required this.iconSize, required this.theme}); + TabThemeConf({required this.iconSize}); } typedef TabBuilder = Widget Function( @@ -168,7 +167,6 @@ typedef LabelGetter = Rx Function(String key); class DesktopTab extends StatelessWidget { final Function(String)? onTabClose; - final TarBarTheme theme; final bool showTabBar; final bool showLogo; final bool showTitle; @@ -189,7 +187,6 @@ class DesktopTab extends StatelessWidget { DesktopTab({ Key? key, required this.controller, - this.theme = const TarBarTheme.light(), this.onTabClose, this.showTabBar = true, this.showLogo = true, @@ -213,15 +210,15 @@ class DesktopTab extends StatelessWidget { return Column(children: [ Offstage( offstage: !showTabBar, - child: Container( + child: SizedBox( height: _kTabBarHeight, child: Column( children: [ - Container( + SizedBox( height: _kTabBarHeight - 1, child: _buildBar(), ), - Divider( + const Divider( height: 1, thickness: 1, ), @@ -300,7 +297,7 @@ class DesktopTab extends StatelessWidget { )), Offstage( offstage: !showTitle, - child: Text( + child: const Text( "RustDesk", style: TextStyle(fontSize: 13), ).marginOnly(left: 2)) @@ -321,7 +318,6 @@ class DesktopTab extends StatelessWidget { child: _ListView( controller: controller, onTabClose: onTabClose, - theme: theme, tabBuilder: tabBuilder, labelGetter: labelGetter, )), @@ -334,7 +330,6 @@ class DesktopTab extends StatelessWidget { mainTab: isMainWindow, tabType: tabType, state: state, - theme: theme, showMinimize: showMinimize, showMaximize: showMaximize, showClose: showClose, @@ -349,7 +344,6 @@ class WindowActionPanel extends StatelessWidget { final bool mainTab; final DesktopTabType tabType; final Rx state; - final TarBarTheme theme; final bool showMinimize; final bool showMaximize; @@ -361,7 +355,6 @@ class WindowActionPanel extends StatelessWidget { required this.mainTab, required this.tabType, required this.state, - required this.theme, this.showMinimize = true, this.showMaximize = true, this.showClose = true, @@ -377,7 +370,6 @@ class WindowActionPanel extends StatelessWidget { child: ActionIcon( message: 'Minimize', icon: IconFont.min, - theme: theme, onTap: () { if (mainTab) { windowManager.minimize(); @@ -385,31 +377,30 @@ class WindowActionPanel extends StatelessWidget { WindowController.fromWindowId(windowId!).minimize(); } }, - is_close: false, + isClose: false, )), // TODO: drag makes window restore Offstage( offstage: !showMaximize, child: FutureBuilder(builder: (context, snapshot) { - RxBool is_maximized = false.obs; + RxBool isMaximized = false.obs; if (mainTab) { windowManager.isMaximized().then((maximized) { - is_maximized.value = maximized; + isMaximized.value = maximized; }); } else { final wc = WindowController.fromWindowId(windowId!); wc.isMaximized().then((maximized) { - is_maximized.value = maximized; + isMaximized.value = maximized; }); } return Obx( () => ActionIcon( - message: is_maximized.value ? "Restore" : "Maximize", - icon: is_maximized.value ? IconFont.restore : IconFont.max, - theme: theme, + message: isMaximized.value ? "Restore" : "Maximize", + icon: isMaximized.value ? IconFont.restore : IconFont.max, onTap: () { if (mainTab) { - if (is_maximized.value) { + if (isMaximized.value) { windowManager.unmaximize(); } else { windowManager.maximize(); @@ -417,15 +408,15 @@ class WindowActionPanel extends StatelessWidget { } else { // TODO: subwindow is maximized but first query result is not maximized. final wc = WindowController.fromWindowId(windowId!); - if (is_maximized.value) { + if (isMaximized.value) { wc.unmaximize(); } else { wc.maximize(); } } - is_maximized.value = !is_maximized.value; + isMaximized.value = !isMaximized.value; }, - is_close: false, + isClose: false, ), ); })), @@ -434,7 +425,6 @@ class WindowActionPanel extends StatelessWidget { child: ActionIcon( message: 'Close', icon: IconFont.close, - theme: theme, onTap: () async { action() { if (mainTab) { @@ -455,7 +445,7 @@ class WindowActionPanel extends StatelessWidget { action(); } }, - is_close: true, + isClose: true, )), ], ); @@ -490,17 +480,15 @@ class WindowActionPanel extends StatelessWidget { class _ListView extends StatelessWidget { final DesktopTabController controller; final Function(String key)? onTabClose; - final TarBarTheme theme; final TabBuilder? tabBuilder; final LabelGetter? labelGetter; Rx get state => controller.state; - _ListView( + const _ListView( {required this.controller, required this.onTabClose, - required this.theme, this.tabBuilder, this.labelGetter}); @@ -510,7 +498,7 @@ class _ListView extends StatelessWidget { controller: state.value.scrollController, scrollDirection: Axis.horizontal, shrinkWrap: true, - physics: BouncingScrollPhysics(), + physics: const BouncingScrollPhysics(), children: state.value.tabs.asMap().entries.map((e) { final index = e.key; final tab = e.value; @@ -525,7 +513,6 @@ class _ListView extends StatelessWidget { selected: state.value.selected, onClose: () => controller.remove(index), onSelected: () => controller.jumpTo(index), - theme: theme, tabBuilder: tabBuilder == null ? null : (Widget icon, Widget labelWidget, TabThemeConf themeConf) { @@ -542,31 +529,29 @@ class _ListView extends StatelessWidget { } class _Tab extends StatefulWidget { - late final int index; - late final Rx label; - late final IconData? selectedIcon; - late final IconData? unselectedIcon; - late final bool closable; - late final int selected; - late final Function() onClose; - late final Function() onSelected; - late final TarBarTheme theme; + final int index; + final Rx label; + final IconData? selectedIcon; + final IconData? unselectedIcon; + final bool closable; + final int selected; + final Function() onClose; + final Function() onSelected; final Widget Function(Widget icon, Widget label, TabThemeConf themeConf)? tabBuilder; - _Tab( - {Key? key, - required this.index, - required this.label, - this.selectedIcon, - this.unselectedIcon, - this.tabBuilder, - required this.closable, - required this.selected, - required this.onClose, - required this.onSelected, - required this.theme}) - : super(key: key); + const _Tab({ + Key? key, + required this.index, + required this.label, + this.selectedIcon, + this.unselectedIcon, + this.tabBuilder, + required this.closable, + required this.selected, + required this.onClose, + required this.onSelected, + }) : super(key: key); @override State<_Tab> createState() => _TabState(); @@ -586,8 +571,8 @@ class _TabState extends State<_Tab> with RestorationMixin { isSelected ? widget.selectedIcon : widget.unselectedIcon, size: _kIconSize, color: isSelected - ? widget.theme.selectedtabIconColor - : widget.theme.unSelectedtabIconColor, + ? MyTheme.tabbar(context).selectedTabIconColor + : MyTheme.tabbar(context).unSelectedTabIconColor, ).paddingOnly(right: 5)); final labelWidget = Obx(() { return Text( @@ -595,8 +580,8 @@ class _TabState extends State<_Tab> with RestorationMixin { textAlign: TextAlign.center, style: TextStyle( color: isSelected - ? widget.theme.selectedTextColor - : widget.theme.unSelectedTextColor), + ? MyTheme.tabbar(context).selectedTextColor + : MyTheme.tabbar(context).unSelectedTextColor), ); }); @@ -609,8 +594,8 @@ class _TabState extends State<_Tab> with RestorationMixin { ], ); } else { - return widget.tabBuilder!(icon, labelWidget, - TabThemeConf(iconSize: _kIconSize, theme: widget.theme)); + return widget.tabBuilder!( + icon, labelWidget, TabThemeConf(iconSize: _kIconSize)); } } @@ -639,7 +624,6 @@ class _TabState extends State<_Tab> with RestorationMixin { visiable: hover.value && widget.closable, tabSelected: isSelected, onClose: () => widget.onClose(), - theme: widget.theme, ))) ])).paddingSymmetric(horizontal: 10), Offstage( @@ -648,7 +632,7 @@ class _TabState extends State<_Tab> with RestorationMixin { width: 1, indent: _kDividerIndent, endIndent: _kDividerIndent, - color: widget.theme.dividerColor, + color: MyTheme.tabbar(context).dividerColor, thickness: 1, ), ) @@ -671,14 +655,12 @@ class _CloseButton extends StatelessWidget { final bool visiable; final bool tabSelected; final Function onClose; - late final TarBarTheme theme; - _CloseButton({ + const _CloseButton({ Key? key, required this.visiable, required this.tabSelected, required this.onClose, - required this.theme, }) : super(key: key); @override @@ -694,8 +676,8 @@ class _CloseButton extends StatelessWidget { Icons.close, size: _kIconSize, color: tabSelected - ? theme.selectedIconColor - : theme.unSelectedIconColor, + ? MyTheme.tabbar(context).selectedIconColor + : MyTheme.tabbar(context).unSelectedIconColor, ), ), )).paddingOnly(left: 5); @@ -705,16 +687,14 @@ class _CloseButton extends StatelessWidget { class ActionIcon extends StatelessWidget { final String message; final IconData icon; - final TarBarTheme theme; final Function() onTap; - final bool is_close; + final bool isClose; const ActionIcon({ Key? key, required this.message, required this.icon, - required this.theme, required this.onTap, - required this.is_close, + required this.isClose, }) : super(key: key); @override @@ -722,34 +702,32 @@ class ActionIcon extends StatelessWidget { RxBool hover = false.obs; return Obx(() => Tooltip( message: translate(message), - waitDuration: Duration(seconds: 1), + waitDuration: const Duration(seconds: 1), child: InkWell( - hoverColor: - is_close ? Color.fromARGB(255, 196, 43, 28) : theme.hoverColor, + hoverColor: isClose + ? const Color.fromARGB(255, 196, 43, 28) + : MyTheme.tabbar(context).hoverColor, onHover: (value) => hover.value = value, - child: Container( + onTap: onTap, + child: SizedBox( height: _kTabBarHeight - 1, width: _kTabBarHeight - 1, child: Icon( icon, - color: hover.value && is_close + color: hover.value && isClose ? Colors.white - : theme.unSelectedIconColor, + : MyTheme.tabbar(context).unSelectedIconColor, size: _kActionIconSize, ), ), - onTap: onTap, ), )); } } class AddButton extends StatelessWidget { - late final TarBarTheme theme; - - AddButton({ + const AddButton({ Key? key, - required this.theme, }) : super(key: key); @override @@ -757,41 +735,101 @@ class AddButton extends StatelessWidget { return ActionIcon( message: 'New Connection', icon: IconFont.add, - theme: theme, onTap: () => rustDeskWinManager.call(WindowType.Main, "main_window_on_top", ""), - is_close: false); + isClose: false); } } -class TarBarTheme { - final Color unSelectedtabIconColor; - final Color selectedtabIconColor; - final Color selectedTextColor; - final Color unSelectedTextColor; - final Color selectedIconColor; - final Color unSelectedIconColor; - final Color dividerColor; - final Color hoverColor; +class TabbarTheme extends ThemeExtension { + final Color? selectedTabIconColor; + final Color? unSelectedTabIconColor; + final Color? selectedTextColor; + final Color? unSelectedTextColor; + final Color? selectedIconColor; + final Color? unSelectedIconColor; + final Color? dividerColor; + final Color? hoverColor; - const TarBarTheme.light() - : unSelectedtabIconColor = const Color.fromARGB(255, 162, 203, 241), - selectedtabIconColor = MyTheme.accent, - selectedTextColor = const Color.fromARGB(255, 26, 26, 26), - unSelectedTextColor = const Color.fromARGB(255, 96, 96, 96), - selectedIconColor = const Color.fromARGB(255, 26, 26, 26), - unSelectedIconColor = const Color.fromARGB(255, 96, 96, 96), - dividerColor = const Color.fromARGB(255, 238, 238, 238), - hoverColor = const Color.fromARGB( - 51, 158, 158, 158); // Colors.grey; //0xFF9E9E9E + const TabbarTheme( + {required this.selectedTabIconColor, + required this.unSelectedTabIconColor, + required this.selectedTextColor, + required this.unSelectedTextColor, + required this.selectedIconColor, + required this.unSelectedIconColor, + required this.dividerColor, + required this.hoverColor}); - const TarBarTheme.dark() - : unSelectedtabIconColor = const Color.fromARGB(255, 30, 65, 98), - selectedtabIconColor = MyTheme.accent, - selectedTextColor = const Color.fromARGB(255, 255, 255, 255), - unSelectedTextColor = const Color.fromARGB(255, 207, 207, 207), - selectedIconColor = const Color.fromARGB(255, 215, 215, 215), - unSelectedIconColor = const Color.fromARGB(255, 255, 255, 255), - dividerColor = const Color.fromARGB(255, 64, 64, 64), - hoverColor = Colors.black26; + static const light = TabbarTheme( + selectedTabIconColor: MyTheme.accent, + unSelectedTabIconColor: Color.fromARGB(255, 162, 203, 241), + selectedTextColor: Color.fromARGB(255, 26, 26, 26), + unSelectedTextColor: Color.fromARGB(255, 96, 96, 96), + selectedIconColor: Color.fromARGB(255, 26, 26, 26), + unSelectedIconColor: Color.fromARGB(255, 96, 96, 96), + dividerColor: Color.fromARGB(255, 238, 238, 238), + hoverColor: Color.fromARGB(51, 158, 158, 158)); + + static const dark = TabbarTheme( + selectedTabIconColor: MyTheme.accent, + unSelectedTabIconColor: Color.fromARGB(255, 30, 65, 98), + selectedTextColor: Color.fromARGB(255, 255, 255, 255), + unSelectedTextColor: Color.fromARGB(255, 207, 207, 207), + selectedIconColor: Color.fromARGB(255, 215, 215, 215), + unSelectedIconColor: Color.fromARGB(255, 255, 255, 255), + dividerColor: Color.fromARGB(255, 64, 64, 64), + hoverColor: Colors.black26); + + @override + ThemeExtension copyWith({ + Color? selectedTabIconColor, + Color? unSelectedTabIconColor, + Color? selectedTextColor, + Color? unSelectedTextColor, + Color? selectedIconColor, + Color? unSelectedIconColor, + Color? dividerColor, + Color? hoverColor, + }) { + return TabbarTheme( + selectedTabIconColor: selectedTabIconColor ?? this.selectedTabIconColor, + unSelectedTabIconColor: + unSelectedTabIconColor ?? this.unSelectedTabIconColor, + selectedTextColor: selectedTextColor ?? this.selectedTextColor, + unSelectedTextColor: unSelectedTextColor ?? this.unSelectedTextColor, + selectedIconColor: selectedIconColor ?? this.selectedIconColor, + unSelectedIconColor: unSelectedIconColor ?? this.unSelectedIconColor, + dividerColor: dividerColor ?? this.dividerColor, + hoverColor: hoverColor ?? this.hoverColor, + ); + } + + @override + ThemeExtension lerp( + ThemeExtension? other, double t) { + if (other is! TabbarTheme) { + return this; + } + return TabbarTheme( + selectedTabIconColor: + Color.lerp(selectedTabIconColor, other.selectedTabIconColor, t), + unSelectedTabIconColor: + Color.lerp(unSelectedTabIconColor, other.unSelectedTabIconColor, t), + selectedTextColor: + Color.lerp(selectedTextColor, other.selectedTextColor, t), + unSelectedTextColor: + Color.lerp(unSelectedTextColor, other.unSelectedTextColor, t), + selectedIconColor: + Color.lerp(selectedIconColor, other.selectedIconColor, t), + unSelectedIconColor: + Color.lerp(unSelectedIconColor, other.unSelectedIconColor, t), + dividerColor: Color.lerp(dividerColor, other.dividerColor, t), + hoverColor: Color.lerp(hoverColor, other.hoverColor, t), + ); + } + + static color(BuildContext context) { + return Theme.of(context).extension()!; + } } From 760ab519198e28cb8e50eef301f092f151bf998e Mon Sep 17 00:00:00 2001 From: 21pages Date: Sun, 4 Sep 2022 16:26:08 +0800 Subject: [PATCH 4/5] dark theme adjustment Signed-off-by: 21pages --- flutter/lib/desktop/widgets/popup_menu.dart | 25 +++++++++++---------- flutter/lib/mobile/pages/chat_page.dart | 11 +++++++-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/flutter/lib/desktop/widgets/popup_menu.dart b/flutter/lib/desktop/widgets/popup_menu.dart index 45e52cf81..ea678673a 100644 --- a/flutter/lib/desktop/widgets/popup_menu.dart +++ b/flutter/lib/desktop/widgets/popup_menu.dart @@ -1,6 +1,7 @@ import 'dart:core'; import 'package:flutter/material.dart'; +import 'package:flutter_hbb/common.dart'; import 'package:get/get.dart'; import './material_mod_popup_menu.dart' as mod_menu; @@ -174,8 +175,8 @@ class MenuEntryRadios extends MenuEntryBase { children: [ Text( opt.text, - style: const TextStyle( - color: Colors.black, + style: TextStyle( + color: MyTheme.color(context).text, fontSize: MenuConfig.fontSize, fontWeight: FontWeight.normal), ), @@ -256,8 +257,8 @@ class MenuEntrySubRadios extends MenuEntryBase { children: [ Text( opt.text, - style: const TextStyle( - color: Colors.black, + style: TextStyle( + color: MyTheme.color(context).text, fontSize: MenuConfig.fontSize, fontWeight: FontWeight.normal), ), @@ -300,8 +301,8 @@ class MenuEntrySubRadios extends MenuEntryBase { const SizedBox(width: MenuConfig.midPadding), Text( text, - style: const TextStyle( - color: Colors.black, + style: TextStyle( + color: MyTheme.color(context).text, fontSize: MenuConfig.fontSize, fontWeight: FontWeight.normal), ), @@ -346,8 +347,8 @@ abstract class MenuEntrySwitchBase extends MenuEntryBase { // const SizedBox(width: MenuConfig.midPadding), Text( text, - style: const TextStyle( - color: Colors.black, + style: TextStyle( + color: MyTheme.color(context).text, fontSize: MenuConfig.fontSize, fontWeight: FontWeight.normal), ), @@ -450,8 +451,8 @@ class MenuEntrySubMenu extends MenuEntryBase { const SizedBox(width: MenuConfig.midPadding), Text( text, - style: const TextStyle( - color: Colors.black, + style: TextStyle( + color: MyTheme.color(context).text, fontSize: MenuConfig.fontSize, fontWeight: FontWeight.normal), ), @@ -491,8 +492,8 @@ class MenuEntryButton extends MenuEntryBase { alignment: AlignmentDirectional.centerStart, constraints: BoxConstraints(minHeight: conf.height), child: childBuilder( - const TextStyle( - color: Colors.black, + TextStyle( + color: MyTheme.color(context).text, fontSize: MenuConfig.fontSize, fontWeight: FontWeight.normal), )), diff --git a/flutter/lib/mobile/pages/chat_page.dart b/flutter/lib/mobile/pages/chat_page.dart index b265f6995..2151f17be 100644 --- a/flutter/lib/mobile/pages/chat_page.dart +++ b/flutter/lib/mobile/pages/chat_page.dart @@ -45,7 +45,7 @@ class ChatPage extends StatelessWidget implements PageShape { return ChangeNotifierProvider.value( value: chatModel, child: Container( - color: MyTheme.grayBg, + color: MyTheme.color(context).grayBg, child: Consumer(builder: (context, chatModel, child) { final currentUser = chatModel.currentUser; return Stack( @@ -59,7 +59,14 @@ class ChatPage extends StatelessWidget implements PageShape { messages: chatModel .messages[chatModel.currentID]?.chatMessages ?? [], - inputOptions: const InputOptions(sendOnEnter: true), + inputOptions: InputOptions( + sendOnEnter: true, + inputDecoration: defaultInputDecoration( + fillColor: MyTheme.color(context).bg), + sendButtonBuilder: defaultSendButton( + color: MyTheme.color(context).text!), + inputTextStyle: + TextStyle(color: MyTheme.color(context).text)), messageOptions: MessageOptions( showOtherUsersAvatar: false, showTime: true, From f47254c5e2fb4c2244f9329355dafca61469f54a Mon Sep 17 00:00:00 2001 From: 21pages Date: Sun, 4 Sep 2022 20:57:57 +0800 Subject: [PATCH 5/5] adjust geometry Signed-off-by: 21pages --- .../lib/desktop/pages/desktop_home_page.dart | 24 +++++++++---------- .../desktop/pages/desktop_setting_page.dart | 4 ++-- .../lib/desktop/widgets/peercard_widget.dart | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 0ccb86d1f..3ce956c23 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -93,8 +93,8 @@ class _DesktopHomePageState extends State buildIDBoard(BuildContext context) { final model = gFFI.serverModel; return Container( - margin: const EdgeInsets.only(left: 20, right: 16), - height: 52, + margin: const EdgeInsets.only(left: 20, right: 11), + height: 57, child: Row( crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, @@ -102,10 +102,10 @@ class _DesktopHomePageState extends State Container( width: 2, decoration: const BoxDecoration(color: MyTheme.accent), - ), + ).marginOnly(top: 5), Expanded( child: Padding( - padding: const EdgeInsets.only(left: 8.0), + padding: const EdgeInsets.only(left: 7), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -120,7 +120,7 @@ class _DesktopHomePageState extends State style: TextStyle( fontSize: 14, color: MyTheme.color(context).lightText), - ), + ).marginOnly(top: 5), buildPopupMenu(context) ], ), @@ -137,7 +137,7 @@ class _DesktopHomePageState extends State readOnly: true, decoration: const InputDecoration( border: InputBorder.none, - contentPadding: EdgeInsets.only(bottom: 18), + contentPadding: EdgeInsets.only(bottom: 20), ), style: const TextStyle( fontSize: 22, @@ -244,7 +244,7 @@ class _DesktopHomePageState extends State }, child: Obx( () => CircleAvatar( - radius: 12, + radius: 15, backgroundColor: hover.value ? MyTheme.color(context).grayBg! : MyTheme.color(context).bg!, @@ -277,7 +277,7 @@ class _DesktopHomePageState extends State ), Expanded( child: Padding( - padding: const EdgeInsets.only(left: 8.0), + padding: const EdgeInsets.only(left: 7), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -303,7 +303,7 @@ class _DesktopHomePageState extends State readOnly: true, decoration: InputDecoration( border: InputBorder.none, - contentPadding: EdgeInsets.only(bottom: 8), + contentPadding: EdgeInsets.only(bottom: 2), ), style: TextStyle(fontSize: 15), ), @@ -317,7 +317,7 @@ class _DesktopHomePageState extends State ? MyTheme.color(context).text : Color(0xFFDDDDDD), size: 22, - ).marginOnly(right: 10, bottom: 8), + ).marginOnly(right: 8, bottom: 2), ), onTap: () => bind.mainUpdateTemporaryPassword(), onHover: (value) => refreshHover.value = value, @@ -425,13 +425,13 @@ class _DesktopHomePageState extends State color: editHover.value ? MyTheme.color(context).text : Color(0xFFDDDDDD)) - .marginOnly(bottom: 8))); + .marginOnly(bottom: 2))); } buildTip(BuildContext context) { return Padding( padding: - const EdgeInsets.only(left: 20.0, right: 16, top: 16.0, bottom: 14), + const EdgeInsets.only(left: 20.0, right: 16, top: 16.0, bottom: 5), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 867c8a54c..b4bb00ab8 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -49,7 +49,7 @@ class _DesktopSettingPageState extends State 'Display', Icons.desktop_windows_outlined, Icons.desktop_windows_sharp), _TabInfo('Audio', Icons.volume_up_outlined, Icons.volume_up_sharp), _TabInfo('Connection', Icons.link_outlined, Icons.link_sharp), - _TabInfo('About RustDesk', Icons.info_outline, Icons.info_sharp) + _TabInfo('About', Icons.info_outline, Icons.info_sharp) ]; late PageController controller; @@ -714,7 +714,7 @@ class _AboutState extends State<_About> { ], ).marginOnly(left: _kContentHMargin) ]), - ]).marginOnly(left: _kCardLeftMargin); + ]); }); } } diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index 1bff02508..13ab92ffe 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -48,7 +48,7 @@ class _PeerCard extends StatefulWidget { class _PeerCardState extends State<_PeerCard> with AutomaticKeepAliveClientMixin { var _menuPos = RelativeRect.fill; - final double _cardRadis = 20; + final double _cardRadis = 16; final double _borderWidth = 2; final RxBool _iconMoreHover = false.obs;