From 59f0ffa82fafb7f8ace87b1a5503f2ec2cdd418d Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 31 Aug 2022 16:41:05 +0800 Subject: [PATCH 1/6] flutter_desktop: menu bar, switch menu & shrink-stretch -> adaptive Signed-off-by: fufesou --- flutter/lib/desktop/widgets/popup_menu.dart | 38 ++++++++++--------- .../lib/desktop/widgets/remote_menubar.dart | 7 ++-- flutter/lib/models/model.dart | 38 +++---------------- flutter/lib/models/native_model.dart | 22 +++++------ src/lang/cn.rs | 2 + src/lang/cs.rs | 2 + src/lang/da.rs | 2 + src/lang/de.rs | 2 + src/lang/eo.rs | 2 + src/lang/es.rs | 2 + src/lang/fr.rs | 2 + src/lang/hu.rs | 2 + src/lang/id.rs | 2 + src/lang/it.rs | 2 + src/lang/ja.rs | 2 + src/lang/ko.rs | 2 + src/lang/pl.rs | 2 + src/lang/pt_PT.rs | 2 + src/lang/ptbr.rs | 2 + src/lang/ru.rs | 2 + src/lang/sk.rs | 2 + src/lang/template.rs | 2 + src/lang/tr.rs | 2 + src/lang/tw.rs | 2 + src/lang/vn.rs | 2 + 25 files changed, 81 insertions(+), 66 deletions(-) diff --git a/flutter/lib/desktop/widgets/popup_menu.dart b/flutter/lib/desktop/widgets/popup_menu.dart index 3d5fdf7f6..6dbe4f8cd 100644 --- a/flutter/lib/desktop/widgets/popup_menu.dart +++ b/flutter/lib/desktop/widgets/popup_menu.dart @@ -315,29 +315,31 @@ abstract class MenuEntrySwitchBase extends MenuEntryBase { mod_menu.PopupMenuItem( padding: EdgeInsets.zero, height: conf.height, - child: Obx( - () => SwitchListTile( - value: curOption.value, - onChanged: (v) { - setOption(v); - }, - title: Container( - alignment: AlignmentDirectional.centerStart, - constraints: BoxConstraints(minHeight: conf.height), - child: Text( + child: TextButton( + child: Container( + alignment: AlignmentDirectional.centerStart, + height: conf.height, + child: Row(children: [ + // const SizedBox(width: MenuConfig.midPadding), + Text( text, style: const TextStyle( color: Colors.black, fontSize: MenuConfig.fontSize, fontWeight: FontWeight.normal), - )), - dense: true, - visualDensity: const VisualDensity( - horizontal: VisualDensity.minimumDensity, - vertical: VisualDensity.minimumDensity, - ), - contentPadding: const EdgeInsets.only(left: 8.0), - ), + ), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Obx(() => Switch( + value: curOption.value, + onChanged: (v) => setOption(v), + )), + )) + ])), + onPressed: () { + setOption(!curOption.value); + }, ), ) ]; diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 0e931dd71..2e8c7fb63 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -406,14 +406,13 @@ class _RemoteMenubarState extends State { MenuEntryRadios( text: translate('Ratio'), optionsGetter: () => [ - Tuple2(translate('Original'), 'original'), - Tuple2(translate('Shrink'), 'shrink'), - Tuple2(translate('Stretch'), 'stretch'), + Tuple2(translate('Scale original'), 'original'), + Tuple2(translate('Scale adaptive'), 'adaptive'), ], curOptionGetter: () async { return await bind.sessionGetOption( id: widget.id, arg: 'view-style') ?? - ''; + 'adaptive'; }, optionSetter: (String v) async { await bind.sessionPeerOption( diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index c01451280..da06b7fb9 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -497,39 +497,11 @@ class CanvasModel with ChangeNotifier { return; } - final s1 = size.width / (parent.target?.ffiModel.display.width ?? 720); - final s2 = size.height / (parent.target?.ffiModel.display.height ?? 1280); - - // Closure to perform shrink operation. - final shrinkOp = () { - final s = s1 < s2 ? s1 : s2; - if (s < 1) { - _scale = s; - } - }; - // Closure to perform stretch operation. - final stretchOp = () { - final s = s1 < s2 ? s1 : s2; - if (s > 1) { - _scale = s; - } - }; - // Closure to perform default operation(set the scale to 1.0). - final defaultOp = () { - _scale = 1.0; - }; - - // // On desktop, shrink is the default behavior. - // if (isDesktop) { - // shrinkOp(); - // } else { - defaultOp(); - // } - - if (style == 'shrink') { - shrinkOp(); - } else if (style == 'stretch') { - stretchOp(); + _scale = 1.0; + if (style == 'adaptive') { + final s1 = size.width / (parent.target?.ffiModel.display.width ?? 720); + final s2 = size.height / (parent.target?.ffiModel.display.height ?? 1280); + _scale = s1 < s2 ? s1 : s2; } _x = (size.width - getDisplayWidth() * _scale) / 2; diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index 57372cdb9..2a8391723 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -30,7 +30,7 @@ class PlatformFFI { String _dir = ''; String _homeDir = ''; F2? _translate; - var _eventHandlers = Map>(); + final _eventHandlers = Map>(); late RustdeskImpl _ffiBind; late String _appType; void Function(Map)? _eventCallback; @@ -50,27 +50,27 @@ class PlatformFFI { } bool registerEventHandler( - String event_name, String handler_name, HandleEvent handler) { - debugPrint('registerEventHandler $event_name $handler_name'); - var handlers = _eventHandlers[event_name]; + String eventName, String handlerName, HandleEvent handler) { + debugPrint('registerEventHandler $eventName $handlerName'); + var handlers = _eventHandlers[eventName]; if (handlers == null) { - _eventHandlers[event_name] = {handler_name: handler}; + _eventHandlers[eventName] = {handlerName: handler}; return true; } else { - if (handlers.containsKey(handler_name)) { + if (handlers.containsKey(handlerName)) { return false; } else { - handlers[handler_name] = handler; + handlers[handlerName] = handler; return true; } } } - void unregisterEventHandler(String event_name, String handler_name) { - debugPrint('unregisterEventHandler $event_name $handler_name'); - var handlers = _eventHandlers[event_name]; + void unregisterEventHandler(String eventName, String handlerName) { + debugPrint('unregisterEventHandler $eventName $handlerName'); + var handlers = _eventHandlers[eventName]; if (handlers != null) { - handlers.remove(handler_name); + handlers.remove(handlerName); } } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index fdb23f88e..c6efaee85 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "中继连接"), ("Secure Connection", "安全连接"), ("Insecure Connection", "非安全连接"), + ("Scale original", "原始尺寸"), + ("Scale adaptive", "适应窗口"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index d9ddf78cc..99d0ae694 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Připojení relé"), ("Secure Connection", "Zabezpečené připojení"), ("Insecure Connection", "Nezabezpečené připojení"), + ("Scale original", "Měřítko původní"), + ("Scale adaptive", "Měřítko adaptivní"), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 0e2d99425..883ef27a5 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Relæforbindelse"), ("Secure Connection", "Sikker forbindelse"), ("Insecure Connection", "Usikker forbindelse"), + ("Scale original", "Skala original"), + ("Scale adaptive", "Skala adaptiv"), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 20cd9330e..649199f0d 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Relaisverbindung"), ("Secure Connection", "Sichere Verbindung"), ("Insecure Connection", "Unsichere Verbindung"), + ("Scale original", "Original skalieren"), + ("Scale adaptive", "Adaptiv skalieren"), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index fe12e2d24..34b89c350 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Relajsa Konekto"), ("Secure Connection", "Sekura Konekto"), ("Insecure Connection", "Nesekura Konekto"), + ("Scale original", "Skalo originalo"), + ("Scale adaptive", "Skalo adapta"), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 313ea8cac..82395dd7a 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Conexión de relé"), ("Secure Connection", "Conexión segura"), ("Insecure Connection", "Conexión insegura"), + ("Scale original", "escala originales"), + ("Scale adaptive", "Adaptable a escala"), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index c8b12243f..00cfe3a3c 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Connexion relais"), ("Secure Connection", "Connexion sécurisée"), ("Insecure Connection", "Connexion non sécurisée"), + ("Scale original", "Échelle d'origine"), + ("Scale adaptive", "Échelle adaptative"), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index a6356b000..d1a259119 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Relé csatlakozás"), ("Secure Connection", "Biztonságos kapcsolat"), ("Insecure Connection", "Nem biztonságos kapcsolat"), + ("Scale original", "Eredeti méretarány"), + ("Scale adaptive", "Skála adaptív"), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 8548eb6bc..3447d3388 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Koneksi Relay"), ("Secure Connection", "Koneksi aman"), ("Insecure Connection", "Koneksi Tidak Aman"), + ("Scale original", "Skala asli"), + ("Scale adaptive", "Skala adaptif"), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index fdf8d27d9..65cdd4b3d 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -316,5 +316,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Collegamento a relè"), ("Secure Connection", "Connessione sicura"), ("Insecure Connection", "Connessione insicura"), + ("Scale original", "Scala originale"), + ("Scale adaptive", "Scala adattiva"), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 1d031f2f2..cbe31bf9f 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -314,5 +314,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "リレー接続"), ("Secure Connection", "安全な接続"), ("Insecure Connection", "安全でない接続"), + ("Scale original", "オリジナルサイズ"), + ("Scale adaptive", "フィットウィンドウ"), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 19d4c7ddf..44ba589f6 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -314,5 +314,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "릴레이 연결"), ("Secure Connection", "보안 연결"), ("Insecure Connection", "안전하지 않은 연결"), + ("Scale original", "원래 크기"), + ("Scale adaptive", "맞는 창"), ].iter().cloned().collect(); } \ No newline at end of file diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 251c349a2..42bd49bbb 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -318,5 +318,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Połączenie przekaźnika"), ("Secure Connection", "Bezpieczne połączenie"), ("Insecure Connection", "Niepewne połączenie"), + ("Scale original", "Skala oryginalna"), + ("Scale adaptive", "Skala adaptacyjna"), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index fd4384767..e8c62d78a 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -314,5 +314,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Conexão de relé"), ("Secure Connection", "Conexão segura"), ("Insecure Connection", "Conexão insegura"), + ("Scale original", "Escala original"), + ("Scale adaptive", "Escala adaptável"), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 85eda60e6..cdd4128b5 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", ""), ("Secure Connection", ""), ("Insecure Connection", ""), + ("Scale original", ""), + ("Scale adaptive", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index def560217..5bbdd846d 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Релейное соединение"), ("Secure Connection", "Безопасное соединение"), ("Insecure Connection", "Небезопасное соединение"), + ("Scale original", "Оригинал масштаба"), + ("Scale adaptive", "Масштаб адаптивный"), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 4c04618aa..b92d0aca6 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Reléové pripojenie"), ("Secure Connection", "Zabezpečené pripojenie"), ("Insecure Connection", "Nezabezpečené pripojenie"), + ("Scale original", "Pôvodná mierka"), + ("Scale adaptive", "Prispôsobivá mierka"), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 081b7bf55..8cf46a196 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", ""), ("Secure Connection", ""), ("Insecure Connection", ""), + ("Scale original", ""), + ("Scale adaptive", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 9738ed469..c5ec537b4 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Röle Bağlantısı"), ("Secure Connection", "Güvenli bağlantı"), ("Insecure Connection", "Güvenli Bağlantı"), + ("Scale original", "Orijinali ölçeklendir"), + ("Scale adaptive", "Ölçek uyarlanabilir"), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 46276dd2a..836b5ad12 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "中繼連接"), ("Secure Connection", "安全連接"), ("Insecure Connection", "非安全連接"), + ("Scale original", "原始尺寸"), + ("Scale adaptive", "適應窗口"), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 474e57337..ba9e4cb86 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -317,5 +317,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relay Connection", "Kết nối chuyển tiếp"), ("Secure Connection", "Kết nối an toàn"), ("Insecure Connection", "Kết nối không an toàn"), + ("Scale original", "Quy mô gốc"), + ("Scale adaptive", "Quy mô thích ứng"), ].iter().cloned().collect(); } From 4b9805b0f3211569680cab2f9b23e1966f237c89 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 31 Aug 2022 18:41:55 +0800 Subject: [PATCH 2/6] flutter_desktop: custom image quality Signed-off-by: fufesou --- flutter/lib/common.dart | 86 +++++++------ flutter/lib/consts.dart | 7 +- .../desktop/pages/connection_tab_page.dart | 3 +- flutter/lib/desktop/pages/remote_page.dart | 15 +-- flutter/lib/desktop/widgets/popup_menu.dart | 116 ++++++++++++------ .../lib/desktop/widgets/remote_menubar.dart | 89 ++++++++++---- flutter/lib/models/model.dart | 15 ++- src/flutter_ffi.rs | 30 +++-- 8 files changed, 240 insertions(+), 121 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 6ba917bf3..e8632caaa 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -427,7 +427,45 @@ class CustomAlertDialog extends StatelessWidget { void msgBox( String type, String title, String text, OverlayDialogManager dialogManager, {bool? hasCancel}) { - var wrap = (String text, void Function() onPressed) => ButtonTheme( + dialogManager.dismissAll(); + List buttons = []; + if (type != "connecting" && type != "success" && !type.contains("nook")) { + buttons.insert( + 0, + getMsgBoxButton(translate('OK'), () { + dialogManager.dismissAll(); + // https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263 + if (!type.contains("custom")) { + closeConnection(); + } + })); + } + hasCancel ??= !type.contains("error") && + !type.contains("nocancel") && + type != "restarting"; + if (hasCancel) { + buttons.insert( + 0, + getMsgBoxButton(translate('Cancel'), () { + dialogManager.dismissAll(); + })); + } + // TODO: test this button + if (type.contains("hasclose")) { + buttons.insert( + 0, + getMsgBoxButton(translate('Close'), () { + dialogManager.dismissAll(); + })); + } + dialogManager.show((setState, close) => CustomAlertDialog( + title: Text(translate(title), style: TextStyle(fontSize: 21)), + content: Text(translate(text), style: TextStyle(fontSize: 15)), + actions: buttons)); +} + +Widget getMsgBoxButton(String text, void Function() onPressed) { + return ButtonTheme( padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, //limits the touch area to the button area @@ -439,44 +477,14 @@ void msgBox( onPressed: onPressed, child: Text(translate(text), style: TextStyle(color: MyTheme.accent)))); +} +void msgBoxCommon(OverlayDialogManager dialogManager, String title, + Widget content, List buttons) { dialogManager.dismissAll(); - List buttons = []; - if (type != "connecting" && type != "success" && type.indexOf("nook") < 0) { - buttons.insert( - 0, - wrap(translate('OK'), () { - dialogManager.dismissAll(); - // https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263 - if (type.indexOf("custom") < 0) { - closeConnection(); - } - })); - } - if (hasCancel == null) { - // hasCancel = type != 'error'; - hasCancel = type.indexOf("error") < 0 && - type.indexOf("nocancel") < 0 && - type != "restarting"; - } - if (hasCancel) { - buttons.insert( - 0, - wrap(translate('Cancel'), () { - dialogManager.dismissAll(); - })); - } - // TODO: test this button - if (type.indexOf("hasclose") >= 0) { - buttons.insert( - 0, - wrap(translate('Close'), () { - dialogManager.dismissAll(); - })); - } dialogManager.show((setState, close) => CustomAlertDialog( title: Text(translate(title), style: TextStyle(fontSize: 21)), - content: Text(translate(text), style: TextStyle(fontSize: 15)), + content: content, actions: buttons)); } @@ -495,13 +503,13 @@ const G = M * K; String readableFileSize(double size) { if (size < K) { - return size.toStringAsFixed(2) + " B"; + return "${size.toStringAsFixed(2)} B"; } else if (size < M) { - return (size / K).toStringAsFixed(2) + " KB"; + return "${(size / K).toStringAsFixed(2)} KB"; } else if (size < G) { - return (size / M).toStringAsFixed(2) + " MB"; + return "${(size / M).toStringAsFixed(2)} MB"; } else { - return (size / G).toStringAsFixed(2) + " GB"; + return "${(size / G).toStringAsFixed(2)} GB"; } } diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 6c67e2ab9..95a6faaa2 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -8,7 +8,10 @@ const String kAppTypeDesktopPortForward = "port forward"; const String kTabLabelHomePage = "Home"; const String kTabLabelSettingPage = "Settings"; -const int kDefaultDisplayWidth = 1280; -const int kDefaultDisplayHeight = 720; +const int kMobileDefaultDisplayWidth = 720; +const int kMobileDefaultDisplayHeight = 1280; + +const int kDesktopDefaultDisplayWidth = 1080; +const int kDesktopDefaultDisplayHeight = 720; const kInvalidValueStr = "InvalidValueStr"; diff --git a/flutter/lib/desktop/pages/connection_tab_page.dart b/flutter/lib/desktop/pages/connection_tab_page.dart index 4175bd11b..5687c5c7e 100644 --- a/flutter/lib/desktop/pages/connection_tab_page.dart +++ b/flutter/lib/desktop/pages/connection_tab_page.dart @@ -61,6 +61,7 @@ class _ConnectionTabPageState extends State { final args = jsonDecode(call.arguments); final id = args['id']; window_on_top(windowId()); + ConnectionTypeState.init(id); tabController.add(TabInfo( key: id, label: id, @@ -108,7 +109,7 @@ class _ConnectionTabPageState extends State { }, tabBuilder: (key, icon, label, themeConf) => Obx(() { final connectionType = ConnectionTypeState.find(key); - if (!ConnectionTypeState.find(key).isValid()) { + if (!connectionType.isValid()) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index f723b17d0..16c04f572 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -689,11 +689,11 @@ class ImagePaint extends StatelessWidget { width: c.getDisplayWidth() * s, height: c.getDisplayHeight() * s, child: CustomPaint( - painter: new ImagePainter(image: m.image, x: 0, y: 0, scale: s), + painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s), )); return Center( child: NotificationListener( - onNotification: (_notification) { + onNotification: (notification) { final percentX = _horizontal.position.extentBefore / (_horizontal.position.extentBefore + _horizontal.position.extentInside + @@ -716,8 +716,8 @@ class ImagePaint extends StatelessWidget { width: c.size.width, height: c.size.height, child: CustomPaint( - painter: new ImagePainter( - image: m.image, x: c.x / s, y: c.y / s, scale: s), + painter: + ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s), )); return _buildListener(imageWidget); } @@ -771,7 +771,7 @@ class CursorPaint extends StatelessWidget { // final adjust = m.adjustForKeyboard(); var s = c.scale; return CustomPaint( - painter: new ImagePainter( + painter: ImagePainter( image: m.image, x: m.x * s - m.hotx + c.x, y: m.y * s - m.hoty + c.y, @@ -796,15 +796,16 @@ class ImagePainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { if (image == null) return; + if (x.isNaN || y.isNaN) return; canvas.scale(scale, scale); // https://github.com/flutter/flutter/issues/76187#issuecomment-784628161 // https://api.flutter-io.cn/flutter/dart-ui/FilterQuality.html - var paint = new Paint(); + var paint = Paint(); paint.filterQuality = FilterQuality.medium; if (scale > 10.00000) { paint.filterQuality = FilterQuality.high; } - canvas.drawImage(image!, new Offset(x, y), paint); + canvas.drawImage(image!, Offset(x, y), paint); } @override diff --git a/flutter/lib/desktop/widgets/popup_menu.dart b/flutter/lib/desktop/widgets/popup_menu.dart index 6dbe4f8cd..3512d640f 100644 --- a/flutter/lib/desktop/widgets/popup_menu.dart +++ b/flutter/lib/desktop/widgets/popup_menu.dart @@ -97,6 +97,9 @@ class MenuConfig { } abstract class MenuEntryBase { + bool dismissOnClicked; + + MenuEntryBase({this.dismissOnClicked = false}); List> build(BuildContext context, MenuConfig conf); } @@ -112,9 +115,19 @@ class MenuEntryDivider extends MenuEntryBase { } } -typedef RadioOptionsGetter = List> Function(); +class MenuEntryRadioOption { + String text; + String value; + bool dismissOnClicked; + + MenuEntryRadioOption( + {required this.text, required this.value, this.dismissOnClicked = false}); +} + +typedef RadioOptionsGetter = List Function(); typedef RadioCurOptionGetter = Future Function(); -typedef RadioOptionSetter = Future Function(String); +typedef RadioOptionSetter = Future Function( + String oldValue, String newValue); class MenuEntryRadioUtils {} @@ -129,24 +142,28 @@ class MenuEntryRadios extends MenuEntryBase { {required this.text, required this.optionsGetter, required this.curOptionGetter, - required this.optionSetter}) { + required this.optionSetter, + dismissOnClicked = false}) + : super(dismissOnClicked: dismissOnClicked) { () async { _curOption.value = await curOptionGetter(); }(); } - List> get options => optionsGetter(); + List get options => optionsGetter(); RxString get curOption => _curOption; setOption(String option) async { - await optionSetter(option); - final opt = await curOptionGetter(); - if (_curOption.value != opt) { - _curOption.value = opt; + await optionSetter(_curOption.value, option); + if (_curOption.value != option) { + final opt = await curOptionGetter(); + if (_curOption.value != opt) { + _curOption.value = opt; + } } } mod_menu.PopupMenuEntry _buildMenuItem( - BuildContext context, MenuConfig conf, Tuple2 opt) { + BuildContext context, MenuConfig conf, MenuEntryRadioOption opt) { return mod_menu.PopupMenuItem( padding: EdgeInsets.zero, height: conf.height, @@ -157,7 +174,7 @@ class MenuEntryRadios extends MenuEntryBase { child: Row( children: [ Text( - opt.item1, + opt.text, style: const TextStyle( color: Colors.black, fontSize: MenuConfig.fontSize, @@ -169,7 +186,7 @@ class MenuEntryRadios extends MenuEntryBase { child: SizedBox( width: 20.0, height: 20.0, - child: Obx(() => opt.item2 == curOption.value + child: Obx(() => opt.value == curOption.value ? Icon( Icons.check, color: conf.commonColor, @@ -180,9 +197,10 @@ class MenuEntryRadios extends MenuEntryBase { ), ), onPressed: () { - if (opt.item2 != curOption.value) { - setOption(opt.item2); + if (opt.dismissOnClicked && Navigator.canPop(context)) { + Navigator.pop(context); } + setOption(opt.value); }, ), ); @@ -206,24 +224,28 @@ class MenuEntrySubRadios extends MenuEntryBase { {required this.text, required this.optionsGetter, required this.curOptionGetter, - required this.optionSetter}) { + required this.optionSetter, + dismissOnClicked = false}) + : super(dismissOnClicked: dismissOnClicked) { () async { _curOption.value = await curOptionGetter(); }(); } - List> get options => optionsGetter(); + List get options => optionsGetter(); RxString get curOption => _curOption; setOption(String option) async { - await optionSetter(option); - final opt = await curOptionGetter(); - if (_curOption.value != opt) { - _curOption.value = opt; + await optionSetter(_curOption.value, option); + if (_curOption.value != option) { + final opt = await curOptionGetter(); + if (_curOption.value != opt) { + _curOption.value = opt; + } } } mod_menu.PopupMenuEntry _buildSecondMenu( - BuildContext context, MenuConfig conf, Tuple2 opt) { + BuildContext context, MenuConfig conf, MenuEntryRadioOption opt) { return mod_menu.PopupMenuItem( padding: EdgeInsets.zero, height: conf.height, @@ -234,7 +256,7 @@ class MenuEntrySubRadios extends MenuEntryBase { child: Row( children: [ Text( - opt.item1, + opt.text, style: const TextStyle( color: Colors.black, fontSize: MenuConfig.fontSize, @@ -246,7 +268,7 @@ class MenuEntrySubRadios extends MenuEntryBase { child: SizedBox( width: 20.0, height: 20.0, - child: Obx(() => opt.item2 == curOption.value + child: Obx(() => opt.value == curOption.value ? Icon( Icons.check, color: conf.commonColor, @@ -257,9 +279,10 @@ class MenuEntrySubRadios extends MenuEntryBase { ), ), onPressed: () { - if (opt.item2 != curOption.value) { - setOption(opt.item2); + if (opt.dismissOnClicked && Navigator.canPop(context)) { + Navigator.pop(context); } + setOption(opt.value); }, ), ); @@ -303,7 +326,8 @@ typedef SwitchSetter = Future Function(bool); abstract class MenuEntrySwitchBase extends MenuEntryBase { final String text; - MenuEntrySwitchBase({required this.text}); + MenuEntrySwitchBase({required this.text, required dismissOnClicked}) + : super(dismissOnClicked: dismissOnClicked); RxBool get curOption; Future setOption(bool option); @@ -333,11 +357,20 @@ abstract class MenuEntrySwitchBase extends MenuEntryBase { alignment: Alignment.centerRight, child: Obx(() => Switch( value: curOption.value, - onChanged: (v) => setOption(v), + onChanged: (v) { + if (super.dismissOnClicked && + Navigator.canPop(context)) { + Navigator.pop(context); + } + setOption(v); + }, )), )) ])), onPressed: () { + if (super.dismissOnClicked && Navigator.canPop(context)) { + Navigator.pop(context); + } setOption(!curOption.value); }, ), @@ -352,8 +385,11 @@ class MenuEntrySwitch extends MenuEntrySwitchBase { final RxBool _curOption = false.obs; MenuEntrySwitch( - {required String text, required this.getter, required this.setter}) - : super(text: text) { + {required String text, + required this.getter, + required this.setter, + dismissOnClicked = false}) + : super(text: text, dismissOnClicked: dismissOnClicked) { () async { _curOption.value = await getter(); }(); @@ -379,8 +415,11 @@ class MenuEntrySwitch2 extends MenuEntrySwitchBase { final SwitchSetter setter; MenuEntrySwitch2( - {required String text, required this.getter, required this.setter}) - : super(text: text); + {required String text, + required this.getter, + required this.setter, + dismissOnClicked = false}) + : super(text: text, dismissOnClicked: dismissOnClicked); @override RxBool get curOption => getter(); @@ -394,10 +433,7 @@ class MenuEntrySubMenu extends MenuEntryBase { final String text; final List> entries; - MenuEntrySubMenu({ - required this.text, - required this.entries, - }); + MenuEntrySubMenu({required this.text, required this.entries}); @override List> build( @@ -438,10 +474,11 @@ class MenuEntryButton extends MenuEntryBase { final Widget Function(TextStyle? style) childBuilder; Function() proc; - MenuEntryButton({ - required this.childBuilder, - required this.proc, - }); + MenuEntryButton( + {required this.childBuilder, + required this.proc, + dismissOnClicked = false}) + : super(dismissOnClicked: dismissOnClicked); @override List> build( @@ -461,6 +498,9 @@ class MenuEntryButton extends MenuEntryBase { fontWeight: FontWeight.normal), )), onPressed: () { + if (super.dismissOnClicked && Navigator.canPop(context)) { + Navigator.pop(context); + } proc(); }, ), diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 2e8c7fb63..26789ac4f 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -290,9 +290,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); bind.sessionRefresh(id: widget.id); }, + dismissOnClicked: true, )); } displayMenu.add(MenuEntryButton( @@ -301,9 +301,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); showSetOSPassword(widget.id, false, widget.ffi.dialogManager); }, + dismissOnClicked: true, )); if (!isWebDesktop) { @@ -314,7 +314,6 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); () async { ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); @@ -323,6 +322,7 @@ class _RemoteMenubarState extends State { } }(); }, + dismissOnClicked: true, )); } @@ -332,9 +332,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); widget.ffi.cursorModel.reset(); }, + dismissOnClicked: true, )); } @@ -346,9 +346,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); bind.sessionCtrlAltDel(id: widget.id); }, + dismissOnClicked: true, )); } @@ -358,9 +358,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); bind.sessionLockScreen(id: widget.id); }, + dismissOnClicked: true, )); if (pi.platform == 'Windows') { @@ -371,13 +371,13 @@ class _RemoteMenubarState extends State { style: style, )), proc: () { - Navigator.pop(context); RxBool blockInput = BlockInputState.find(widget.id); bind.sessionToggleOption( id: widget.id, value: '${blockInput.value ? "un" : ""}block-input'); blockInput.value = !blockInput.value; }, + dismissOnClicked: true, )); } } @@ -392,9 +392,9 @@ class _RemoteMenubarState extends State { style: style, ), proc: () { - Navigator.pop(context); showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager); }, + dismissOnClicked: true, )); } @@ -406,44 +406,54 @@ class _RemoteMenubarState extends State { MenuEntryRadios( text: translate('Ratio'), optionsGetter: () => [ - Tuple2(translate('Scale original'), 'original'), - Tuple2(translate('Scale adaptive'), 'adaptive'), + MenuEntryRadioOption( + text: translate('Scale original'), value: 'original'), + MenuEntryRadioOption( + text: translate('Scale adaptive'), value: 'adaptive'), ], curOptionGetter: () async { return await bind.sessionGetOption( id: widget.id, arg: 'view-style') ?? 'adaptive'; }, - optionSetter: (String v) async { + optionSetter: (String oldValue, String newValue) async { await bind.sessionPeerOption( - id: widget.id, name: "view-style", value: v); + id: widget.id, name: "view-style", value: newValue); widget.ffi.canvasModel.updateViewStyle(); }), MenuEntryDivider(), MenuEntryRadios( text: translate('Scroll Style'), optionsGetter: () => [ - Tuple2(translate('ScrollAuto'), 'scrollauto'), - Tuple2(translate('Scrollbar'), 'scrollbar'), + MenuEntryRadioOption( + text: translate('ScrollAuto'), value: 'scrollauto'), + MenuEntryRadioOption( + text: translate('Scrollbar'), value: 'scrollbar'), ], curOptionGetter: () async { return await bind.sessionGetOption( id: widget.id, arg: 'scroll-style') ?? ''; }, - optionSetter: (String v) async { + optionSetter: (String oldValue, String newValue) async { await bind.sessionPeerOption( - id: widget.id, name: "scroll-style", value: v); + id: widget.id, name: "scroll-style", value: newValue); widget.ffi.canvasModel.updateScrollStyle(); }), MenuEntryDivider(), MenuEntryRadios( text: translate('Image Quality'), optionsGetter: () => [ - Tuple2(translate('Good image quality'), 'best'), - Tuple2(translate('Balanced'), 'balanced'), - Tuple2( - translate('Optimize reaction time'), 'low'), + MenuEntryRadioOption( + text: translate('Good image quality'), value: 'best'), + MenuEntryRadioOption( + text: translate('Balanced'), value: 'balanced'), + MenuEntryRadioOption( + text: translate('Optimize reaction time'), value: 'low'), + MenuEntryRadioOption( + text: translate('Custom'), + value: 'custom', + dismissOnClicked: true), ], curOptionGetter: () async { String quality = @@ -451,8 +461,43 @@ class _RemoteMenubarState extends State { if (quality == '') quality = 'balanced'; return quality; }, - optionSetter: (String v) async { - await bind.sessionSetImageQuality(id: widget.id, value: v); + optionSetter: (String oldValue, String newValue) async { + if (oldValue != newValue) { + await bind.sessionSetImageQuality(id: widget.id, value: newValue); + } + + if (newValue == 'custom') { + final btnCancel = getMsgBoxButton(translate('Cancel'), () { + widget.ffi.dialogManager.dismissAll(); + }); + final quality = + await bind.sessionGetCustomImageQuality(id: widget.id); + final double initValue = quality != null && quality.isNotEmpty + ? quality[0].toDouble() + : 50.0; + // final slider = _ImageCustomQualitySlider( + // id: widget.id, v: RxDouble(initValue)); + final RxDouble sliderValue = RxDouble(initValue); + final slider = Obx(() => Slider( + value: sliderValue.value, + max: 100, + label: sliderValue.value.round().toString(), + onChanged: (double value) { + () async { + await bind.sessionSetCustomImageQuality( + id: widget.id, value: value.toInt()); + final quality = await bind.sessionGetCustomImageQuality( + id: widget.id); + sliderValue.value = + quality != null && quality.isNotEmpty + ? quality[0].toDouble() + : 50.0; + }(); + }, + )); + msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality', + slider, [btnCancel]); + } }), MenuEntryDivider(), MenuEntrySwitch( diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index da06b7fb9..ba9981db6 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -7,6 +7,7 @@ import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/generated_bridge.dart'; import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/chat_model.dart'; @@ -499,8 +500,8 @@ class CanvasModel with ChangeNotifier { _scale = 1.0; if (style == 'adaptive') { - final s1 = size.width / (parent.target?.ffiModel.display.width ?? 720); - final s2 = size.height / (parent.target?.ffiModel.display.height ?? 1280); + final s1 = size.width / getDisplayWidth(); + final s2 = size.height / getDisplayHeight(); _scale = s1 < s2 ? s1 : s2; } @@ -529,11 +530,17 @@ class CanvasModel with ChangeNotifier { } int getDisplayWidth() { - return parent.target?.ffiModel.display.width ?? 1080; + final defaultWidth = (isDesktop || isWebDesktop) + ? kDesktopDefaultDisplayWidth + : kMobileDefaultDisplayWidth; + return parent.target?.ffiModel.display.width ?? defaultWidth; } int getDisplayHeight() { - return parent.target?.ffiModel.display.height ?? 720; + final defaultHeight = (isDesktop || isWebDesktop) + ? kDesktopDefaultDisplayHeight + : kMobileDefaultDisplayHeight; + return parent.target?.ffiModel.display.height ?? defaultHeight; } Size get size { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 167124212..716afacb9 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -143,14 +143,6 @@ pub fn session_get_toggle_option_sync(id: String, arg: String) -> SyncReturn Option { - if let Some(session) = SESSIONS.read().unwrap().get(&id) { - Some(session.get_image_quality()) - } else { - None - } -} - pub fn session_get_option(id: String, arg: String) -> Option { if let Some(session) = SESSIONS.read().unwrap().get(&id) { Some(session.get_option(arg)) @@ -190,12 +182,34 @@ pub fn session_toggle_option(id: String, value: String) { } } +pub fn session_get_image_quality(id: String) -> Option { + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + Some(session.get_image_quality()) + } else { + None + } +} + pub fn session_set_image_quality(id: String, value: String) { if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { session.save_image_quality(value); } } +pub fn session_get_custom_image_quality(id: String) -> Option> { + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + Some(session.get_custom_image_quality()) + } else { + None + } +} + +pub fn session_set_custom_image_quality(id: String, value: i32) { + if let Some(session) = SESSIONS.read().unwrap().get(&id) { + session.set_custom_image_quality(value); + } +} + pub fn session_lock_screen(id: String) { if let Some(session) = SESSIONS.read().unwrap().get(&id) { session.lock_screen(); From 7cb079afc8dd30caeb538b0334de74e5e873e920 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 31 Aug 2022 23:02:16 +0800 Subject: [PATCH 3/6] flutter_desktop: add debug print Signed-off-by: fufesou --- flutter/lib/models/model.dart | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index ba9981db6..962998bbd 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -556,9 +556,19 @@ class CanvasModel with ChangeNotifier { var dxOffset = 0; var dyOffset = 0; if (dw > size.width) { + final xxxx = x - dw * (x / size.width) - _x; + if (xxxx.isInfinite || xxxx.isNaN) { + debugPrint( + 'REMOVE ME ============================ xxxx $x,$dw,$_scale,${size.width},$_x'); + } dxOffset = (x - dw * (x / size.width) - _x).toInt(); } if (dh > size.height) { + final yyyy = y - dh * (y / size.height) - _y; + if (yyyy.isInfinite || yyyy.isNaN) { + debugPrint( + 'REMOVE ME ============================ xxxx $y,$dh,$_scale,${size.height},$_y'); + } dyOffset = (y - dh * (y / size.height) - _y).toInt(); } _x += dxOffset; @@ -926,16 +936,16 @@ class FFI { late final QualityMonitorModel qualityMonitorModel; // session FFI() { - this.imageModel = ImageModel(WeakReference(this)); - this.ffiModel = FfiModel(WeakReference(this)); - this.cursorModel = CursorModel(WeakReference(this)); - this.canvasModel = CanvasModel(WeakReference(this)); - this.serverModel = ServerModel(WeakReference(this)); // use global FFI - this.chatModel = ChatModel(WeakReference(this)); - this.fileModel = FileModel(WeakReference(this)); - this.abModel = AbModel(WeakReference(this)); - this.userModel = UserModel(WeakReference(this)); - this.qualityMonitorModel = QualityMonitorModel(WeakReference(this)); + imageModel = ImageModel(WeakReference(this)); + ffiModel = FfiModel(WeakReference(this)); + cursorModel = CursorModel(WeakReference(this)); + canvasModel = CanvasModel(WeakReference(this)); + serverModel = ServerModel(WeakReference(this)); // use global FFI + chatModel = ChatModel(WeakReference(this)); + fileModel = FileModel(WeakReference(this)); + abModel = AbModel(WeakReference(this)); + userModel = UserModel(WeakReference(this)); + qualityMonitorModel = QualityMonitorModel(WeakReference(this)); } /// Send a mouse tap event(down and up). From ce1a504e9fdc564a1d42ec8034f01f9baf05f5a3 Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 31 Aug 2022 23:02:02 -0700 Subject: [PATCH 4/6] flutter_desktop: custom image quality Signed-off-by: fufesou --- flutter/.gitignore | 2 +- flutter/lib/common.dart | 14 +++--- .../lib/desktop/widgets/remote_menubar.dart | 45 ++++++++++--------- flutter/pubspec.yaml | 1 + 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/flutter/.gitignore b/flutter/.gitignore index e5db34d22..ec3fef74e 100644 --- a/flutter/.gitignore +++ b/flutter/.gitignore @@ -48,7 +48,7 @@ lib/generated_bridge.dart lib/generated_bridge.freezed.dart # Flutter Generated Files -**/flutter/GeneratedPluginRegistrant.swift +**/GeneratedPluginRegistrant.swift **/flutter/generated_plugin_registrant.cc **/flutter/generated_plugin_registrant.h **/flutter/generated_plugins.cmake diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index e8632caaa..75328c840 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -432,7 +432,7 @@ void msgBox( if (type != "connecting" && type != "success" && !type.contains("nook")) { buttons.insert( 0, - getMsgBoxButton(translate('OK'), () { + msgBoxButton(translate('OK'), () { dialogManager.dismissAll(); // https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263 if (!type.contains("custom")) { @@ -446,7 +446,7 @@ void msgBox( if (hasCancel) { buttons.insert( 0, - getMsgBoxButton(translate('Cancel'), () { + msgBoxButton(translate('Cancel'), () { dialogManager.dismissAll(); })); } @@ -454,17 +454,17 @@ void msgBox( if (type.contains("hasclose")) { buttons.insert( 0, - getMsgBoxButton(translate('Close'), () { + msgBoxButton(translate('Close'), () { dialogManager.dismissAll(); })); } dialogManager.show((setState, close) => CustomAlertDialog( - title: Text(translate(title), style: TextStyle(fontSize: 21)), + title: _msgBoxTitle(title), content: Text(translate(text), style: TextStyle(fontSize: 15)), actions: buttons)); } -Widget getMsgBoxButton(String text, void Function() onPressed) { +Widget msgBoxButton(String text, void Function() onPressed) { return ButtonTheme( padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -479,11 +479,13 @@ Widget getMsgBoxButton(String text, void Function() onPressed) { Text(translate(text), style: TextStyle(color: MyTheme.accent)))); } +Widget _msgBoxTitle(String title) => Text(translate(title), style: TextStyle(fontSize: 21)); + void msgBoxCommon(OverlayDialogManager dialogManager, String title, Widget content, List buttons) { dialogManager.dismissAll(); dialogManager.show((setState, close) => CustomAlertDialog( - title: Text(translate(title), style: TextStyle(fontSize: 21)), + title: _msgBoxTitle(title), content: content, actions: buttons)); } diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 26789ac4f..66edb7a96 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/models/chat_model.dart'; import 'package:get/get.dart'; -import 'package:tuple/tuple.dart'; +import 'package:rxdart/rxdart.dart' as rxdart; import '../../common.dart'; import '../../mobile/widgets/dialog.dart'; @@ -467,7 +467,7 @@ class _RemoteMenubarState extends State { } if (newValue == 'custom') { - final btnCancel = getMsgBoxButton(translate('Cancel'), () { + final btnCancel = msgBoxButton(translate('Close'), () { widget.ffi.dialogManager.dismissAll(); }); final quality = @@ -475,26 +475,29 @@ class _RemoteMenubarState extends State { final double initValue = quality != null && quality.isNotEmpty ? quality[0].toDouble() : 50.0; - // final slider = _ImageCustomQualitySlider( - // id: widget.id, v: RxDouble(initValue)); final RxDouble sliderValue = RxDouble(initValue); - final slider = Obx(() => Slider( - value: sliderValue.value, - max: 100, - label: sliderValue.value.round().toString(), - onChanged: (double value) { - () async { - await bind.sessionSetCustomImageQuality( - id: widget.id, value: value.toInt()); - final quality = await bind.sessionGetCustomImageQuality( - id: widget.id); - sliderValue.value = - quality != null && quality.isNotEmpty - ? quality[0].toDouble() - : 50.0; - }(); - }, - )); + final rxReplay = rxdart.ReplaySubject(); + rxReplay + .throttleTime(const Duration(milliseconds: 1000), + trailing: true, leading: false) + .listen((double v) { + () async { + await bind.sessionSetCustomImageQuality( + id: widget.id, value: v.toInt()); + }(); + }); + final slider = Obx(() { + return Slider( + value: sliderValue.value, + max: 100, + divisions: 100, + label: sliderValue.value.round().toString(), + onChanged: (double value) { + sliderValue.value = value; + rxReplay.add(value); + }, + ); + }); msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality', slider, [btnCancel]); } diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index a35f1c872..b6ce5d20b 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -73,6 +73,7 @@ dependencies: contextmenu: ^3.0.0 desktop_drop: ^0.3.3 scroll_pos: ^0.3.0 + rxdart: ^0.27.5 dev_dependencies: flutter_launcher_icons: ^0.9.1 From ec02f9e7213779548a24743794345bc7cb034ccd Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 1 Sep 2022 03:56:12 -0700 Subject: [PATCH 5/6] flutter_desktop: refactor peercard menu Signed-off-by: fufesou --- .../lib/desktop/widgets/peercard_widget.dart | 865 ++++++++++-------- flutter/lib/desktop/widgets/popup_menu.dart | 1 - .../lib/desktop/widgets/remote_menubar.dart | 2 +- 3 files changed, 509 insertions(+), 359 deletions(-) diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index 4db43398a..3b4a30ee0 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -8,10 +8,18 @@ import '../../common.dart'; import '../../models/model.dart'; import '../../models/peer_model.dart'; import '../../models/platform_model.dart'; +import './material_mod_popup_menu.dart' as mod_menu; +import './popup_menu.dart'; -typedef PopupMenuItemsFunc = Future>> Function(); +class _PopupMenuTheme { + static const Color commonColor = MyTheme.accent; + // kMinInteractiveDimension + static const double height = 25.0; + static const double dividerHeight = 12.0; +} -enum PeerType { recent, fav, discovered, ab } +typedef PopupMenuEntryBuilder = Future>> + Function(BuildContext); enum PeerUiType { grid, list } @@ -19,14 +27,16 @@ final peerCardUiType = PeerUiType.grid.obs; class _PeerCard extends StatefulWidget { final Peer peer; - final PopupMenuItemsFunc popupMenuItemsFunc; - final PeerType type; + final RxString alias; + final Function(BuildContext, String) connect; + final PopupMenuEntryBuilder popupMenuEntryBuilder; _PeerCard( {required this.peer, - required this.popupMenuItemsFunc, - Key? key, - required this.type}) + required this.alias, + required this.connect, + required this.popupMenuEntryBuilder, + Key? key}) : super(key: key); @override @@ -36,7 +46,6 @@ class _PeerCard extends StatefulWidget { /// State for the connection page. class _PeerCardState extends State<_PeerCard> with AutomaticKeepAliveClientMixin { - var _menuPos = RelativeRect.fill; final double _cardRadis = 20; final double _borderWidth = 2; final RxBool _iconMoreHover = false.obs; @@ -66,7 +75,7 @@ class _PeerCardState extends State<_PeerCard> : null); }, child: GestureDetector( - onDoubleTap: () => _connect(peer.id), + onDoubleTap: () => widget.connect(context, peer.id), child: Obx(() => peerCardUiType.value == PeerUiType.grid ? _buildPeerCard(context, peer, deco) : _buildPeerTile(context, peer, deco))), @@ -185,46 +194,28 @@ class _PeerCardState extends State<_PeerCard> children: [ Container( padding: const EdgeInsets.all(6), - child: - _getPlatformImage('${peer.platform}', 60), + child: _getPlatformImage(peer.platform, 60), ), Row( children: [ Expanded( - child: FutureBuilder( - future: bind.mainGetPeerOption( - id: peer.id, key: 'alias'), - builder: (_, snapshot) { - if (snapshot.hasData) { - final name = snapshot.data!.isEmpty - ? '${peer.username}@${peer.hostname}' - : snapshot.data!; - return Tooltip( - message: name, - waitDuration: Duration(seconds: 1), - child: Text( - name, - style: TextStyle( - color: Colors.white70, - fontSize: 12), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - ), - ); - } else { - // alias has not arrived - return Center( - child: Text( - '${peer.username}@${peer.hostname}', - style: TextStyle( - color: Colors.white70, - fontSize: 12), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - )); - } - }, - ), + child: Obx(() { + final name = widget.alias.value.isEmpty + ? '${peer.username}@${peer.hostname}' + : widget.alias.value; + return Tooltip( + message: name, + waitDuration: Duration(seconds: 1), + child: Text( + name, + style: TextStyle( + color: Colors.white70, + fontSize: 12), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + ), + ); + }), ), ], ), @@ -248,7 +239,7 @@ class _PeerCardState extends State<_PeerCard> backgroundColor: peer.online ? Colors.green : Colors.yellow)), - Text('${peer.id}') + Text(peer.id) ]).paddingSymmetric(vertical: 8), _actionMore(peer), ], @@ -262,32 +253,93 @@ class _PeerCardState extends State<_PeerCard> ); } - Widget _actionMore(Peer peer) => Listener( - onPointerDown: (e) { - final x = e.position.dx; - final y = e.position.dy; - _menuPos = RelativeRect.fromLTRB(x, y, x, y); - }, - onPointerUp: (_) => _showPeerMenu(context, peer.id), - child: MouseRegion( - onEnter: (_) => _iconMoreHover.value = true, - onExit: (_) => _iconMoreHover.value = false, - child: CircleAvatar( - radius: 14, - backgroundColor: _iconMoreHover.value - ? MyTheme.color(context).grayBg! - : MyTheme.color(context).bg!, - child: Icon(Icons.more_vert, - size: 18, - color: _iconMoreHover.value - ? MyTheme.color(context).text - : MyTheme.color(context).lightText)))); + Widget _actionMore(Peer peer) { + return FutureBuilder( + future: widget.popupMenuEntryBuilder(context), + initialData: const >[], + builder: (BuildContext context, + AsyncSnapshot>> snapshot) { + if (snapshot.hasData) { + return Listener( + child: MouseRegion( + onEnter: (_) => _iconMoreHover.value = true, + onExit: (_) => _iconMoreHover.value = false, + child: CircleAvatar( + radius: 14, + backgroundColor: _iconMoreHover.value + ? MyTheme.color(context).grayBg! + : MyTheme.color(context).bg!, + child: mod_menu.PopupMenuButton( + padding: EdgeInsets.zero, + icon: Icon(Icons.more_vert, + size: 18, + color: _iconMoreHover.value + ? MyTheme.color(context).text + : MyTheme.color(context).lightText), + position: mod_menu.PopupMenuPosition.under, + itemBuilder: (BuildContext context) => snapshot.data!, + )))); + } else { + return Container(); + } + }); + } + + /// Get the image for the current [platform]. + Widget _getPlatformImage(String platform, double size) { + platform = platform.toLowerCase(); + if (platform == 'mac os') { + platform = 'mac'; + } else if (platform != 'linux' && platform != 'android') { + platform = 'win'; + } + return Image.asset('assets/$platform.png', height: size, width: size); + } + + @override + bool get wantKeepAlive => true; +} + +abstract class BasePeerCard extends StatelessWidget { + final RxString alias = ''.obs; + final Peer peer; + + BasePeerCard({required this.peer, Key? key}) : super(key: key) { + bind + .mainGetPeerOption(id: peer.id, key: 'alias') + .then((value) => alias.value = value); + } + + @override + Widget build(BuildContext context) { + return _PeerCard( + peer: peer, + alias: alias, + connect: (BuildContext context, String id) => _connect(context, id), + popupMenuEntryBuilder: _buildPopupMenuEntry, + ); + } + + Future>> _buildPopupMenuEntry( + BuildContext context) async => + (await _buildMenuItems(context)) + .map((e) => e.build( + context, + const MenuConfig( + commonColor: _PopupMenuTheme.commonColor, + height: _PopupMenuTheme.height, + dividerHeight: _PopupMenuTheme.dividerHeight))) + .expand((i) => i) + .toList(); + + @protected + Future>> _buildMenuItems(BuildContext context); /// Connect to a peer with [id]. /// If [isFileTransfer], starts a session only for file transfer. /// If [isTcpTunneling], starts a session only for tcp tunneling. /// If [isRDP], starts a session only for rdp. - void _connect(String id, + void _connect(BuildContext context, String id, {bool isFileTransfer = false, bool isTcpTunneling = false, bool isRDP = false}) async { @@ -308,105 +360,369 @@ class _PeerCardState extends State<_PeerCard> } } - /// Show the peer menu and handle user's choice. - /// User might remove the peer or send a file to the peer. - void _showPeerMenu(BuildContext context, String id) async { - var value = await showMenu( - context: context, - position: _menuPos, - items: await super.widget.popupMenuItemsFunc(), - elevation: 8, - ); - if (value == 'connect') { - _connect(id); - } else if (value == 'file') { - _connect(id, isFileTransfer: true); - } else if (value == 'tcp-tunnel') { - _connect(id, isTcpTunneling: true); - } else if (value == 'RDP') { - _connect(id, isRDP: true); - } else if (value == 'remove') { - await bind.mainRemovePeer(id: id); - removePreference(id); - Get.forceAppUpdate(); // TODO use inner model / state - } else if (value == 'add-fav') { - final favs = (await bind.mainGetFav()).toList(); - if (favs.indexOf(id) < 0) { - favs.add(id); - bind.mainStoreFav(favs: favs); - } - } else if (value == 'remove-fav') { - final favs = (await bind.mainGetFav()).toList(); - if (favs.remove(id)) { - bind.mainStoreFav(favs: favs); - Get.forceAppUpdate(); // TODO use inner model / state - } - } else if (value == 'ab-delete') { - gFFI.abModel.deletePeer(id); - await gFFI.abModel.updateAb(); - setState(() {}); - } else if (value == 'ab-edit-tag') { - _abEditTag(id); - } else if (value == 'rename') { - _rename(id); - } else if (value == 'unremember-password') { - await bind.mainForgetPassword(id: id); - } else if (value == 'force-always-relay') { - String value; - String oldValue = - await bind.mainGetPeerOption(id: id, key: 'force-always-relay'); - if (oldValue.isEmpty) { - value = 'Y'; - } else { - value = ''; - } - await bind.mainSetPeerOption( - id: id, key: 'force-always-relay', value: value); - } - } - - Widget _buildTag(String tagName, RxList rxTags, - {Function()? onTap}) { - return ContextMenuArea( - width: 100, - builder: (context) => [ - ListTile( - title: Text(translate("Delete")), - onTap: () { - gFFI.abModel.deleteTag(tagName); - gFFI.abModel.updateAb(); - Future.delayed(Duration.zero, () => Get.back()); - }, - ) - ], - child: GestureDetector( - onTap: onTap, - child: Obx( - () => Container( - decoration: BoxDecoration( - color: rxTags.contains(tagName) ? Colors.blue : null, - border: Border.all(color: MyTheme.darkGray), - borderRadius: BorderRadius.circular(10)), - margin: EdgeInsets.symmetric(horizontal: 4.0, vertical: 8.0), - padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 8.0), - child: Text( - tagName, - style: TextStyle( - color: rxTags.contains(tagName) ? MyTheme.white : null), - ), - ), - ), + MenuEntryBase _connectCommonAction( + BuildContext context, String id, String title, + {bool isFileTransfer = false, + bool isTcpTunneling = false, + bool isRDP = false}) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate(title), + style: style, ), + proc: () { + _connect( + context, + peer.id, + isFileTransfer: isFileTransfer, + isTcpTunneling: isTcpTunneling, + isRDP: isRDP, + ); + }, + dismissOnClicked: true, ); } - /// Get the image for the current [platform]. - Widget _getPlatformImage(String platform, double size) { - platform = platform.toLowerCase(); - if (platform == 'mac os') - platform = 'mac'; - else if (platform != 'linux' && platform != 'android') platform = 'win'; - return Image.asset('assets/$platform.png', height: size, width: size); + @protected + MenuEntryBase _connectAction(BuildContext context, String id) { + return _connectCommonAction(context, id, 'Connect'); + } + + @protected + MenuEntryBase _transferFileAction(BuildContext context, String id) { + return _connectCommonAction( + context, + id, + 'Transfer File', + isFileTransfer: true, + ); + } + + @protected + MenuEntryBase _tcpTunnelingAction(BuildContext context, String id) { + return _connectCommonAction( + context, + id, + 'TCP Tunneling', + isTcpTunneling: true, + ); + } + + @protected + MenuEntryBase _rdpAction(BuildContext context, String id) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Row( + children: [ + Text( + translate('RDP'), + style: style, + ), + SizedBox(width: 20), + IconButton( + icon: Icon(Icons.edit), + onPressed: () => _rdpDialog(id), + ) + ], + ), + proc: () { + _connect(context, id, isRDP: true); + }, + dismissOnClicked: true, + ); + } + + @protected + Future> _forceAlwaysRelayAction(String id) async { + const option = 'force-always-relay'; + return MenuEntrySwitch( + text: translate('Always connect via relay'), + getter: () async { + return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty; + }, + setter: (bool v) async { + String value; + String oldValue = await bind.mainGetPeerOption(id: id, key: option); + if (oldValue.isEmpty) { + value = 'Y'; + } else { + value = ''; + } + await bind.mainSetPeerOption(id: id, key: option, value: value); + }, + dismissOnClicked: true, + ); + } + + @protected + MenuEntryBase _renameAction(String id, bool isAddressBook) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Rename'), + style: style, + ), + proc: () { + _rename(id, isAddressBook); + }, + dismissOnClicked: true, + ); + } + + @protected + MenuEntryBase _removeAction(String id) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Remove'), + style: style, + ), + proc: () { + () async { + await bind.mainRemovePeer(id: id); + removePreference(id); + Get.forceAppUpdate(); // TODO use inner model / state + }(); + }, + dismissOnClicked: true, + ); + } + + @protected + MenuEntryBase _unrememberPasswordAction(String id) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Unremember Password'), + style: style, + ), + proc: () { + bind.mainForgetPassword(id: id); + }, + dismissOnClicked: true, + ); + } + + @protected + MenuEntryBase _addFavAction(String id) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Add to Favorites'), + style: style, + ), + proc: () { + () async { + final favs = (await bind.mainGetFav()).toList(); + if (!favs.contains(id)) { + favs.add(id); + bind.mainStoreFav(favs: favs); + } + }(); + }, + dismissOnClicked: true, + ); + } + + @protected + MenuEntryBase _rmFavAction(String id) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Remove from Favorites'), + style: style, + ), + proc: () { + () async { + final favs = (await bind.mainGetFav()).toList(); + if (favs.remove(id)) { + bind.mainStoreFav(favs: favs); + Get.forceAppUpdate(); // TODO use inner model / state + } + }(); + }, + dismissOnClicked: true, + ); + } + + void _rename(String id, bool isAddressBook) async { + RxBool isInProgress = false.obs; + var name = await bind.mainGetPeerOption(id: id, key: 'alias'); + var controller = TextEditingController(text: name); + if (isAddressBook) { + final peer = gFFI.abModel.peers.firstWhere((p) => id == p['id']); + if (peer == null) { + // this should not happen + } else { + name = peer['alias'] ?? ''; + } + } + gFFI.dialogManager.show((setState, close) { + return CustomAlertDialog( + title: Text(translate('Rename')), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Form( + child: TextFormField( + controller: controller, + decoration: InputDecoration(border: OutlineInputBorder()), + ), + ), + ), + Obx(() => Offstage( + offstage: isInProgress.isFalse, + child: 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"))), + ], + ); + }); + } +} + +class RecentPeerCard extends BasePeerCard { + RecentPeerCard({required Peer peer, Key? key}) : super(peer: peer, key: key); + + @override + Future>> _buildMenuItems( + BuildContext context) async { + final List> menuItems = [ + _connectAction(context, peer.id), + _transferFileAction(context, peer.id), + _tcpTunnelingAction(context, peer.id), + ]; + if (peer.platform == 'Windows') { + menuItems.add(_rdpAction(context, peer.id)); + } + menuItems.add(await _forceAlwaysRelayAction(peer.id)); + menuItems.add(_renameAction(peer.id, false)); + menuItems.add(_removeAction(peer.id)); + menuItems.add(_unrememberPasswordAction(peer.id)); + menuItems.add(_addFavAction(peer.id)); + return menuItems; + } +} + +class FavoritePeerCard extends BasePeerCard { + FavoritePeerCard({required Peer peer, Key? key}) + : super(peer: peer, key: key); + + @override + Future>> _buildMenuItems( + BuildContext context) async { + final List> menuItems = [ + _connectAction(context, peer.id), + _transferFileAction(context, peer.id), + _tcpTunnelingAction(context, peer.id), + ]; + if (peer.platform == 'Windows') { + menuItems.add(_rdpAction(context, peer.id)); + } + menuItems.add(await _forceAlwaysRelayAction(peer.id)); + menuItems.add(_renameAction(peer.id, false)); + menuItems.add(_removeAction(peer.id)); + menuItems.add(_unrememberPasswordAction(peer.id)); + menuItems.add(_rmFavAction(peer.id)); + return menuItems; + } +} + +class DiscoveredPeerCard extends BasePeerCard { + DiscoveredPeerCard({required Peer peer, Key? key}) + : super(peer: peer, key: key); + + @override + Future>> _buildMenuItems( + BuildContext context) async { + final List> menuItems = [ + _connectAction(context, peer.id), + _transferFileAction(context, peer.id), + _tcpTunnelingAction(context, peer.id), + ]; + if (peer.platform == 'Windows') { + menuItems.add(_rdpAction(context, peer.id)); + } + menuItems.add(await _forceAlwaysRelayAction(peer.id)); + menuItems.add(_renameAction(peer.id, false)); + menuItems.add(_removeAction(peer.id)); + menuItems.add(_unrememberPasswordAction(peer.id)); + menuItems.add(_addFavAction(peer.id)); + return menuItems; + } +} + +class AddressBookPeerCard extends BasePeerCard { + AddressBookPeerCard({required Peer peer, Key? key}) + : super(peer: peer, key: key); + + @override + Future>> _buildMenuItems( + BuildContext context) async { + final List> menuItems = [ + _connectAction(context, peer.id), + _transferFileAction(context, peer.id), + _tcpTunnelingAction(context, peer.id), + ]; + if (peer.platform == 'Windows') { + menuItems.add(_rdpAction(context, peer.id)); + } + menuItems.add(await _forceAlwaysRelayAction(peer.id)); + menuItems.add(_renameAction(peer.id, false)); + menuItems.add(_removeAction(peer.id)); + menuItems.add(_unrememberPasswordAction(peer.id)); + menuItems.add(_addFavAction(peer.id)); + menuItems.add(_editTagAction(peer.id)); + return menuItems; + } + + @protected + @override + MenuEntryBase _removeAction(String id) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Remove'), + style: style, + ), + proc: () { + () async { + gFFI.abModel.deletePeer(id); + await gFFI.abModel.updateAb(); + }(); + }, + dismissOnClicked: true, + ); + } + + @protected + MenuEntryBase _editTagAction(String id) { + return MenuEntryButton( + childBuilder: (TextStyle? style) => Text( + translate('Edit Tag'), + style: style, + ), + proc: () { + _abEditTag(id); + }, + dismissOnClicked: true, + ); } void _abEditTag(String id) { @@ -459,205 +775,40 @@ class _PeerCardState extends State<_PeerCard> }); } - void _rename(String id) async { - var isInProgress = false; - var name = await bind.mainGetPeerOption(id: id, key: 'alias'); - var controller = TextEditingController(text: name); - if (widget.type == PeerType.ab) { - final peer = gFFI.abModel.peers.firstWhere((p) => id == p['id']); - if (peer == null) { - // this should not happen - } else { - name = peer['alias'] ?? ""; - } - } - gFFI.dialogManager.show((setState, close) { - return CustomAlertDialog( - title: Text(translate("Rename")), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - child: Form( - child: TextFormField( - controller: controller, - decoration: InputDecoration(border: OutlineInputBorder()), - ), - ), + Widget _buildTag(String tagName, RxList rxTags, + {Function()? onTap}) { + return ContextMenuArea( + width: 100, + builder: (context) => [ + ListTile( + title: Text(translate("Delete")), + onTap: () { + gFFI.abModel.deleteTag(tagName); + gFFI.abModel.updateAb(); + Future.delayed(Duration.zero, () => Get.back()); + }, + ) + ], + child: GestureDetector( + onTap: onTap, + child: Obx( + () => Container( + decoration: BoxDecoration( + color: rxTags.contains(tagName) ? Colors.blue : null, + border: Border.all(color: MyTheme.darkGray), + borderRadius: BorderRadius.circular(10)), + margin: EdgeInsets.symmetric(horizontal: 4.0, vertical: 8.0), + padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 8.0), + child: Text( + tagName, + style: TextStyle( + color: rxTags.contains(tagName) ? MyTheme.white : null), ), - Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) - ], + ), ), - actions: [ - TextButton( - onPressed: () { - close(); - }, - child: Text(translate("Cancel"))), - TextButton( - onPressed: () async { - setState(() { - isInProgress = true; - }); - name = controller.text; - await bind.mainSetPeerOption(id: id, key: 'alias', value: name); - if (widget.type == PeerType.ab) { - gFFI.abModel.setPeerOption(id, 'alias', name); - await gFFI.abModel.updateAb(); - } else { - Future.delayed(Duration.zero, () { - this.setState(() {}); - }); - } - close(); - setState(() { - isInProgress = false; - }); - }, - child: Text(translate("OK"))), - ], - ); - }); - } - - @override - bool get wantKeepAlive => true; -} - -abstract class BasePeerCard extends StatelessWidget { - final Peer peer; - final PeerType type; - - BasePeerCard({required this.peer, required this.type, Key? key}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return _PeerCard( - peer: peer, - popupMenuItemsFunc: _getPopupMenuItems, - type: type, + ), ); } - - @protected - Future>> _getPopupMenuItems(); -} - -class RecentPeerCard extends BasePeerCard { - RecentPeerCard({required Peer peer, Key? key}) - : super(peer: peer, key: key, type: PeerType.recent); - - Future>> _getPopupMenuItems() async { - var items = [ - PopupMenuItem( - child: Text(translate('Connect')), value: 'connect'), - PopupMenuItem( - child: Text(translate('Transfer File')), value: 'file'), - PopupMenuItem( - child: Text(translate('TCP Tunneling')), value: 'tcp-tunnel'), - await _forceAlwaysRelayMenuItem(peer.id), - PopupMenuItem(child: Text(translate('Rename')), value: 'rename'), - PopupMenuItem(child: Text(translate('Remove')), value: 'remove'), - PopupMenuItem( - child: Text(translate('Unremember Password')), - value: 'unremember-password'), - PopupMenuItem( - child: Text(translate('Add to Favorites')), value: 'add-fav'), - ]; - if (peer.platform == 'Windows') { - items.insert(3, _rdpMenuItem(peer.id)); - } - return items; - } -} - -class FavoritePeerCard extends BasePeerCard { - FavoritePeerCard({required Peer peer, Key? key}) - : super(peer: peer, key: key, type: PeerType.fav); - - Future>> _getPopupMenuItems() async { - var items = [ - PopupMenuItem( - child: Text(translate('Connect')), value: 'connect'), - PopupMenuItem( - child: Text(translate('Transfer File')), value: 'file'), - PopupMenuItem( - child: Text(translate('TCP Tunneling')), value: 'tcp-tunnel'), - await _forceAlwaysRelayMenuItem(peer.id), - PopupMenuItem(child: Text(translate('Rename')), value: 'rename'), - PopupMenuItem(child: Text(translate('Remove')), value: 'remove'), - PopupMenuItem( - child: Text(translate('Unremember Password')), - value: 'unremember-password'), - PopupMenuItem( - child: Text(translate('Remove from Favorites')), value: 'remove-fav'), - ]; - if (peer.platform == 'Windows') { - items.insert(3, _rdpMenuItem(peer.id)); - } - return items; - } -} - -class DiscoveredPeerCard extends BasePeerCard { - DiscoveredPeerCard({required Peer peer, Key? key}) - : super(peer: peer, key: key, type: PeerType.discovered); - - Future>> _getPopupMenuItems() async { - var items = [ - PopupMenuItem( - child: Text(translate('Connect')), value: 'connect'), - PopupMenuItem( - child: Text(translate('Transfer File')), value: 'file'), - PopupMenuItem( - child: Text(translate('TCP Tunneling')), value: 'tcp-tunnel'), - await _forceAlwaysRelayMenuItem(peer.id), - PopupMenuItem(child: Text(translate('Rename')), value: 'rename'), - PopupMenuItem(child: Text(translate('Remove')), value: 'remove'), - PopupMenuItem( - child: Text(translate('Unremember Password')), - value: 'unremember-password'), - PopupMenuItem( - child: Text(translate('Add to Favorites')), value: 'add-fav'), - ]; - if (peer.platform == 'Windows') { - items.insert(3, _rdpMenuItem(peer.id)); - } - return items; - } -} - -class AddressBookPeerCard extends BasePeerCard { - AddressBookPeerCard({required Peer peer, Key? key}) - : super(peer: peer, key: key, type: PeerType.ab); - - Future>> _getPopupMenuItems() async { - var items = [ - PopupMenuItem( - child: Text(translate('Connect')), value: 'connect'), - PopupMenuItem( - child: Text(translate('Transfer File')), value: 'file'), - PopupMenuItem( - child: Text(translate('TCP Tunneling')), value: 'tcp-tunnel'), - await _forceAlwaysRelayMenuItem(peer.id), - PopupMenuItem(child: Text(translate('Rename')), value: 'rename'), - PopupMenuItem( - child: Text(translate('Remove')), value: 'ab-delete'), - PopupMenuItem( - child: Text(translate('Unremember Password')), - value: 'unremember-password'), - PopupMenuItem( - child: Text(translate('Add to Favorites')), value: 'add-fav'), - PopupMenuItem( - child: Text(translate('Edit Tag')), value: 'ab-edit-tag'), - ]; - if (peer.platform == 'Windows') { - items.insert(3, _rdpMenuItem(peer.id)); - } - return items; - } } Future> _forceAlwaysRelayMenuItem(String id) async { diff --git a/flutter/lib/desktop/widgets/popup_menu.dart b/flutter/lib/desktop/widgets/popup_menu.dart index 3512d640f..45e52cf81 100644 --- a/flutter/lib/desktop/widgets/popup_menu.dart +++ b/flutter/lib/desktop/widgets/popup_menu.dart @@ -2,7 +2,6 @@ import 'dart:core'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:tuple/tuple.dart'; import './material_mod_popup_menu.dart' as mod_menu; diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index 66edb7a96..47536011d 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -16,7 +16,7 @@ import './material_mod_popup_menu.dart' as mod_menu; class _MenubarTheme { static const Color commonColor = MyTheme.accent; // kMinInteractiveDimension - static const double height = 24.0; + static const double height = 25.0; static const double dividerHeight = 12.0; } From 9085a938884fa8e1497fa557a3665bf36a8b28a0 Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 1 Sep 2022 06:18:29 -0700 Subject: [PATCH 6/6] flutter_desktop: fix peer page bugs Signed-off-by: fufesou --- .../lib/desktop/pages/connection_page.dart | 10 +- flutter/lib/desktop/widgets/peer_widget.dart | 233 ++++++++++-------- .../lib/desktop/widgets/peercard_widget.dart | 30 ++- flutter/lib/models/model.dart | 23 +- flutter/lib/models/peer_model.dart | 58 ++--- flutter/lib/utils/multi_window_manager.dart | 18 +- src/client.rs | 4 +- src/flutter_ffi.rs | 4 +- src/ui_session_interface.rs | 11 +- 9 files changed, 208 insertions(+), 183 deletions(-) diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index d366d06ec..5fd6b4a28 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -33,7 +33,7 @@ class _ConnectionPageState extends State { final _idController = TextEditingController(); /// Update url. If it's not null, means an update is available. - var _updateUrl = ''; + final _updateUrl = ''; Timer? _updateTimer; @@ -92,7 +92,7 @@ class _ConnectionPageState extends State { if (snapshot.hasData) { return snapshot.data!; } else { - return Offstage(); + return const Offstage(); } }), ], @@ -110,7 +110,7 @@ class _ConnectionPageState extends State { /// Callback for the connect button. /// Connects to the selected peer. void onConnect({bool isFileTransfer = false}) { - var id = _idController.text.trim(); + final id = _idController.text.trim(); connect(id, isFileTransfer: isFileTransfer); } @@ -120,9 +120,9 @@ class _ConnectionPageState extends State { if (id == '') return; id = id.replaceAll(' ', ''); if (isFileTransfer) { - await rustDeskWinManager.new_file_transfer(id); + await rustDeskWinManager.newFileTransfer(id); } else { - await rustDeskWinManager.new_remote_desktop(id); + await rustDeskWinManager.newRemoteDesktop(id); } FocusScopeNode currentFocus = FocusScope.of(context); if (!currentFocus.hasPrimaryFocus) { diff --git a/flutter/lib/desktop/widgets/peer_widget.dart b/flutter/lib/desktop/widgets/peer_widget.dart index 3bfff60bf..02b5b9f00 100644 --- a/flutter/lib/desktop/widgets/peer_widget.dart +++ b/flutter/lib/desktop/widgets/peer_widget.dart @@ -21,18 +21,16 @@ final peerSearchTextController = TextEditingController(text: peerSearchText.value); class _PeerWidget extends StatefulWidget { - late final _peers; - late final OffstageFunc _offstageFunc; - late final PeerCardWidgetFunc _peerCardWidgetFunc; + final Peers peers; + final OffstageFunc offstageFunc; + final PeerCardWidgetFunc peerCardWidgetFunc; - _PeerWidget(Peers peers, OffstageFunc offstageFunc, - PeerCardWidgetFunc peerCardWidgetFunc, - {Key? key}) - : super(key: key) { - _peers = peers; - _offstageFunc = offstageFunc; - _peerCardWidgetFunc = peerCardWidgetFunc; - } + const _PeerWidget( + {required this.peers, + required this.offstageFunc, + required this.peerCardWidgetFunc, + Key? key}) + : super(key: key); @override _PeerWidgetState createState() => _PeerWidgetState(); @@ -42,9 +40,9 @@ class _PeerWidget extends StatefulWidget { class _PeerWidgetState extends State<_PeerWidget> with WindowListener { static const int _maxQueryCount = 3; - var _curPeers = Set(); + final _curPeers = {}; var _lastChangeTime = DateTime.now(); - var _lastQueryPeers = Set(); + var _lastQueryPeers = {}; var _lastQueryTime = DateTime.now().subtract(Duration(hours: 1)); var _queryCoun = 0; var _exit = false; @@ -78,65 +76,62 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener { @override Widget build(BuildContext context) { - final space = 12.0; + const space = 12.0; return ChangeNotifierProvider( - create: (context) => super.widget._peers, + create: (context) => widget.peers, child: Consumer( - builder: (context, peers, child) => peers.peers.isEmpty - ? Center( - child: Text(translate("Empty")), - ) - : SingleChildScrollView( - child: ObxValue((searchText) { - return FutureBuilder>( - builder: (context, snapshot) { - if (snapshot.hasData) { - final peers = snapshot.data!; - final cards = []; - for (final peer in peers) { - cards.add(Offstage( - key: ValueKey("off${peer.id}"), - offstage: super.widget._offstageFunc(peer), - child: Obx( - () => SizedBox( - width: 220, - height: - peerCardUiType.value == PeerUiType.grid - ? 140 - : 42, - child: VisibilityDetector( - key: ValueKey(peer.id), - onVisibilityChanged: (info) { - final peerId = - (info.key as ValueKey).value; - if (info.visibleFraction > 0.00001) { - _curPeers.add(peerId); - } else { - _curPeers.remove(peerId); - } - _lastChangeTime = DateTime.now(); - }, - child: super - .widget - ._peerCardWidgetFunc(peer), - ), + builder: (context, peers, child) => peers.peers.isEmpty + ? Center( + child: Text(translate("Empty")), + ) + : SingleChildScrollView( + child: ObxValue((searchText) { + return FutureBuilder>( + builder: (context, snapshot) { + if (snapshot.hasData) { + final peers = snapshot.data!; + final cards = []; + for (final peer in peers) { + cards.add(Offstage( + key: ValueKey("off${peer.id}"), + offstage: widget.offstageFunc(peer), + child: Obx( + () => SizedBox( + width: 220, + height: + peerCardUiType.value == PeerUiType.grid + ? 140 + : 42, + child: VisibilityDetector( + key: ValueKey(peer.id), + onVisibilityChanged: (info) { + final peerId = + (info.key as ValueKey).value; + if (info.visibleFraction > 0.00001) { + _curPeers.add(peerId); + } else { + _curPeers.remove(peerId); + } + _lastChangeTime = DateTime.now(); + }, + child: widget.peerCardWidgetFunc(peer), ), - ))); - } - return Wrap( - spacing: space, - runSpacing: space, - children: cards); - } else { - return const Center( - child: CircularProgressIndicator(), - ); + ), + ))); } - }, - future: matchPeers(searchText.value, peers.peers), - ); - }, peerSearchText), - )), + return Wrap( + spacing: space, runSpacing: space, children: cards); + } else { + return const Center( + child: CircularProgressIndicator(), + ); + } + }, + future: matchPeers(searchText.value, peers.peers), + ); + }, peerSearchText), + ), + ), ); } @@ -175,31 +170,42 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener { } abstract class BasePeerWidget extends StatelessWidget { - late final _name; - late final _loadEvent; - late final OffstageFunc _offstageFunc; - late final PeerCardWidgetFunc _peerCardWidgetFunc; - late final List _initPeers; + final String name; + final String loadEvent; + final OffstageFunc offstageFunc; + final PeerCardWidgetFunc peerCardWidgetFunc; + final List initPeers; - BasePeerWidget({Key? key}) : super(key: key) {} + const BasePeerWidget({ + Key? key, + required this.name, + required this.loadEvent, + required this.offstageFunc, + required this.peerCardWidgetFunc, + required this.initPeers, + }) : super(key: key); @override Widget build(BuildContext context) { - return _PeerWidget(Peers(_name, _loadEvent, _initPeers), _offstageFunc, - _peerCardWidgetFunc); + return _PeerWidget( + peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers), + offstageFunc: offstageFunc, + peerCardWidgetFunc: peerCardWidgetFunc); } } class RecentPeerWidget extends BasePeerWidget { - RecentPeerWidget({Key? key}) : super(key: key) { - super._name = "recent peer"; - super._loadEvent = "load_recent_peers"; - super._offstageFunc = (Peer _peer) => false; - super._peerCardWidgetFunc = (Peer peer) => RecentPeerCard( - peer: peer, + RecentPeerWidget({Key? key}) + : super( + key: key, + name: 'recent peer', + loadEvent: 'load_recent_peers', + offstageFunc: (Peer peer) => false, + peerCardWidgetFunc: (Peer peer) => RecentPeerCard( + peer: peer, + ), + initPeers: [], ); - super._initPeers = []; - } @override Widget build(BuildContext context) { @@ -210,13 +216,17 @@ class RecentPeerWidget extends BasePeerWidget { } class FavoritePeerWidget extends BasePeerWidget { - FavoritePeerWidget({Key? key}) : super(key: key) { - super._name = "favorite peer"; - super._loadEvent = "load_fav_peers"; - super._offstageFunc = (Peer _peer) => false; - super._peerCardWidgetFunc = (Peer peer) => FavoritePeerCard(peer: peer); - super._initPeers = []; - } + FavoritePeerWidget({Key? key}) + : super( + key: key, + name: 'favorite peer', + loadEvent: 'load_fav_peers', + offstageFunc: (Peer peer) => false, + peerCardWidgetFunc: (Peer peer) => FavoritePeerCard( + peer: peer, + ), + initPeers: [], + ); @override Widget build(BuildContext context) { @@ -227,13 +237,17 @@ class FavoritePeerWidget extends BasePeerWidget { } class DiscoveredPeerWidget extends BasePeerWidget { - DiscoveredPeerWidget({Key? key}) : super(key: key) { - super._name = "discovered peer"; - super._loadEvent = "load_lan_peers"; - super._offstageFunc = (Peer _peer) => false; - super._peerCardWidgetFunc = (Peer peer) => DiscoveredPeerCard(peer: peer); - super._initPeers = []; - } + DiscoveredPeerWidget({Key? key}) + : super( + key: key, + name: 'discovered peer', + loadEvent: 'load_lan_peers', + offstageFunc: (Peer peer) => false, + peerCardWidgetFunc: (Peer peer) => DiscoveredPeerCard( + peer: peer, + ), + initPeers: [], + ); @override Widget build(BuildContext context) { @@ -244,21 +258,26 @@ class DiscoveredPeerWidget extends BasePeerWidget { } class AddressBookPeerWidget extends BasePeerWidget { - AddressBookPeerWidget({Key? key}) : super(key: key) { - super._name = "address book peer"; - super._offstageFunc = - (Peer peer) => !_hitTag(gFFI.abModel.selectedTags, peer.tags); - super._peerCardWidgetFunc = (Peer peer) => AddressBookPeerCard(peer: peer); - super._initPeers = _loadPeers(); - } + AddressBookPeerWidget({Key? key}) + : super( + key: key, + name: 'address book peer', + loadEvent: 'load_address_book_peers', + offstageFunc: (Peer peer) => + !_hitTag(gFFI.abModel.selectedTags, peer.tags), + peerCardWidgetFunc: (Peer peer) => DiscoveredPeerCard( + peer: peer, + ), + initPeers: _loadPeers(), + ); - List _loadPeers() { + static List _loadPeers() { return gFFI.abModel.peers.map((e) { return Peer.fromJson(e['id'], e); }).toList(); } - bool _hitTag(List selectedTags, List idents) { + static bool _hitTag(List selectedTags, List idents) { if (selectedTags.isEmpty) { return true; } diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index 3b4a30ee0..114f4146e 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -348,11 +348,11 @@ abstract class BasePeerCard extends StatelessWidget { assert(!(isFileTransfer && isTcpTunneling && isRDP), "more than one connect type"); if (isFileTransfer) { - await rustDeskWinManager.new_file_transfer(id); + await rustDeskWinManager.newFileTransfer(id); } else if (isTcpTunneling || isRDP) { - await rustDeskWinManager.new_port_forward(id, isRDP); + await rustDeskWinManager.newPortForward(id, isRDP); } else { - await rustDeskWinManager.new_remote_desktop(id); + await rustDeskWinManager.newRemoteDesktop(id); } FocusScopeNode currentFocus = FocusScope.of(context); if (!currentFocus.hasPrimaryFocus) { @@ -468,7 +468,8 @@ abstract class BasePeerCard extends StatelessWidget { } @protected - MenuEntryBase _removeAction(String id) { + MenuEntryBase _removeAction( + String id, Future Function() reloadFunc) { return MenuEntryButton( childBuilder: (TextStyle? style) => Text( translate('Remove'), @@ -478,7 +479,8 @@ abstract class BasePeerCard extends StatelessWidget { () async { await bind.mainRemovePeer(id: id); removePreference(id); - Get.forceAppUpdate(); // TODO use inner model / state + await reloadFunc(); + // Get.forceAppUpdate(); // TODO use inner model / state }(); }, dismissOnClicked: true, @@ -614,7 +616,9 @@ class RecentPeerCard extends BasePeerCard { } menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(_renameAction(peer.id, false)); - menuItems.add(_removeAction(peer.id)); + menuItems.add(_removeAction(peer.id, () async { + await bind.mainLoadRecentPeers(); + })); menuItems.add(_unrememberPasswordAction(peer.id)); menuItems.add(_addFavAction(peer.id)); return menuItems; @@ -638,7 +642,9 @@ class FavoritePeerCard extends BasePeerCard { } menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(_renameAction(peer.id, false)); - menuItems.add(_removeAction(peer.id)); + menuItems.add(_removeAction(peer.id, () async { + await bind.mainLoadFavPeers(); + })); menuItems.add(_unrememberPasswordAction(peer.id)); menuItems.add(_rmFavAction(peer.id)); return menuItems; @@ -662,9 +668,10 @@ class DiscoveredPeerCard extends BasePeerCard { } menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(_renameAction(peer.id, false)); - menuItems.add(_removeAction(peer.id)); + menuItems.add(_removeAction(peer.id, () async { + await bind.mainLoadLanPeers(); + })); menuItems.add(_unrememberPasswordAction(peer.id)); - menuItems.add(_addFavAction(peer.id)); return menuItems; } } @@ -686,7 +693,7 @@ class AddressBookPeerCard extends BasePeerCard { } menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(_renameAction(peer.id, false)); - menuItems.add(_removeAction(peer.id)); + menuItems.add(_removeAction(peer.id, () async {})); menuItems.add(_unrememberPasswordAction(peer.id)); menuItems.add(_addFavAction(peer.id)); menuItems.add(_editTagAction(peer.id)); @@ -695,7 +702,8 @@ class AddressBookPeerCard extends BasePeerCard { @protected @override - MenuEntryBase _removeAction(String id) { + MenuEntryBase _removeAction( + String id, Future Function() reloadFunc) { return MenuEntryButton( childBuilder: (TextStyle? style) => Text( translate('Remove'), diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 962998bbd..887bf7d35 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -301,6 +301,9 @@ class FfiModel with ChangeNotifier { /// Handle the peer info event based on [evt]. void handlePeerInfo(Map evt, String peerId) async { + // recent peer updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs) + bind.mainLoadRecentPeers(); + parent.target?.dialogManager.dismissAll(); _pi.version = evt['version']; _pi.username = evt['username']; @@ -556,18 +559,18 @@ class CanvasModel with ChangeNotifier { var dxOffset = 0; var dyOffset = 0; if (dw > size.width) { - final xxxx = x - dw * (x / size.width) - _x; - if (xxxx.isInfinite || xxxx.isNaN) { + final X_debugNanOrInfinite = x - dw * (x / size.width) - _x; + if (X_debugNanOrInfinite.isInfinite || X_debugNanOrInfinite.isNaN) { debugPrint( - 'REMOVE ME ============================ xxxx $x,$dw,$_scale,${size.width},$_x'); + 'REMOVE ME ============================ X_debugNanOrInfinite $x,$dw,$_scale,${size.width},$_x'); } dxOffset = (x - dw * (x / size.width) - _x).toInt(); } if (dh > size.height) { - final yyyy = y - dh * (y / size.height) - _y; - if (yyyy.isInfinite || yyyy.isNaN) { + final Y_debugNanOrInfinite = y - dh * (y / size.height) - _y; + if (Y_debugNanOrInfinite.isInfinite || Y_debugNanOrInfinite.isNaN) { debugPrint( - 'REMOVE ME ============================ xxxx $y,$dh,$_scale,${size.height},$_y'); + 'REMOVE ME ============================ Y_debugNanOrInfinite $y,$dh,$_scale,${size.height},$_y'); } dyOffset = (y - dh * (y / size.height) - _y).toInt(); } @@ -1249,20 +1252,20 @@ class PeerInfo { Future savePreference(String id, double xCursor, double yCursor, double xCanvas, double yCanvas, double scale, int currentDisplay) async { SharedPreferences prefs = await SharedPreferences.getInstance(); - final p = Map(); + final p = {}; p['xCursor'] = xCursor; p['yCursor'] = yCursor; p['xCanvas'] = xCanvas; p['yCanvas'] = yCanvas; p['scale'] = scale; p['currentDisplay'] = currentDisplay; - prefs.setString('peer' + id, json.encode(p)); + prefs.setString('peer$id', json.encode(p)); } Future?> getPreference(String id) async { if (!isWebDesktop) return null; SharedPreferences prefs = await SharedPreferences.getInstance(); - var p = prefs.getString('peer' + id); + var p = prefs.getString('peer$id'); if (p == null) return null; Map m = json.decode(p); return m; @@ -1270,7 +1273,7 @@ Future?> getPreference(String id) async { void removePreference(String id) async { SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.remove('peer' + id); + prefs.remove('peer$id'); } void initializeCursorAndCanvas(FFI ffi) async { diff --git a/flutter/lib/models/peer_model.dart b/flutter/lib/models/peer_model.dart index 5c889e60f..79b71e6db 100644 --- a/flutter/lib/models/peer_model.dart +++ b/flutter/lib/models/peer_model.dart @@ -10,9 +10,8 @@ class Peer { final List tags; bool online = false; - Peer.fromJson(String id, Map json) - : id = id, - username = json['username'] ?? '', + Peer.fromJson(this.id, Map json) + : username = json['username'] ?? '', hostname = json['hostname'] ?? '', platform = json['platform'] ?? '', tags = json['tags'] ?? []; @@ -35,57 +34,52 @@ class Peer { } class Peers extends ChangeNotifier { - late String _name; - late List _peers; - late final _loadEvent; + final String name; + final String loadEvent; + List peers; static const _cbQueryOnlines = 'callback_query_onlines'; - Peers(String name, String loadEvent, List _initPeers) { - _name = name; - _loadEvent = loadEvent; - _peers = _initPeers; - platformFFI.registerEventHandler(_cbQueryOnlines, _name, (evt) { + Peers({required this.name, required this.peers, required this.loadEvent}) { + platformFFI.registerEventHandler(_cbQueryOnlines, name, (evt) { _updateOnlineState(evt); }); - platformFFI.registerEventHandler(_loadEvent, _name, (evt) { + platformFFI.registerEventHandler(loadEvent, name, (evt) { _updatePeers(evt); }); } - List get peers => _peers; - @override void dispose() { - platformFFI.unregisterEventHandler(_cbQueryOnlines, _name); - platformFFI.unregisterEventHandler(_loadEvent, _name); + platformFFI.unregisterEventHandler(_cbQueryOnlines, name); + platformFFI.unregisterEventHandler(loadEvent, name); super.dispose(); } Peer getByIndex(int index) { - if (index < _peers.length) { - return _peers[index]; + if (index < peers.length) { + return peers[index]; } else { return Peer.loading(); } } int getPeersCount() { - return _peers.length; + return peers.length; } void _updateOnlineState(Map evt) { evt['onlines'].split(',').forEach((online) { - for (var i = 0; i < _peers.length; i++) { - if (_peers[i].id == online) { - _peers[i].online = true; + for (var i = 0; i < peers.length; i++) { + if (peers[i].id == online) { + peers[i].online = true; } } }); evt['offlines'].split(',').forEach((offline) { - for (var i = 0; i < _peers.length; i++) { - if (_peers[i].id == offline) { - _peers[i].online = false; + for (var i = 0; i < peers.length; i++) { + if (peers[i].id == offline) { + peers[i].online = false; } } }); @@ -95,19 +89,19 @@ class Peers extends ChangeNotifier { void _updatePeers(Map evt) { final onlineStates = _getOnlineStates(); - _peers = _decodePeers(evt['peers']); - _peers.forEach((peer) { + peers = _decodePeers(evt['peers']); + for (var peer in peers) { final state = onlineStates[peer.id]; peer.online = state != null && state != false; - }); + } notifyListeners(); } Map _getOnlineStates() { - var onlineStates = new Map(); - _peers.forEach((peer) { + var onlineStates = {}; + for (var peer in peers) { onlineStates[peer.id] = peer.online; - }); + } return onlineStates; } @@ -121,7 +115,7 @@ class Peers extends ChangeNotifier { Peer.fromJson(s[0] as String, s[1] as Map)) .toList(); } catch (e) { - print('peers(): $e'); + debugPrint('peers(): $e'); } return []; } diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index b01b84a9d..97d5a5e23 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:ui'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; @@ -37,9 +36,9 @@ class RustDeskMultiWindowManager { int? _fileTransferWindowId; int? _portForwardWindowId; - Future new_remote_desktop(String remote_id) async { + Future newRemoteDesktop(String remoteId) async { final msg = - jsonEncode({"type": WindowType.RemoteDesktop.index, "id": remote_id}); + jsonEncode({"type": WindowType.RemoteDesktop.index, "id": remoteId}); try { final ids = await DesktopMultiWindow.getAllSubWindowIds(); @@ -63,9 +62,9 @@ class RustDeskMultiWindowManager { } } - Future new_file_transfer(String remote_id) async { + Future newFileTransfer(String remoteId) async { final msg = - jsonEncode({"type": WindowType.FileTransfer.index, "id": remote_id}); + jsonEncode({"type": WindowType.FileTransfer.index, "id": remoteId}); try { final ids = await DesktopMultiWindow.getAllSubWindowIds(); @@ -88,12 +87,9 @@ class RustDeskMultiWindowManager { } } - Future new_port_forward(String remote_id, bool isRDP) async { - final msg = jsonEncode({ - "type": WindowType.PortForward.index, - "id": remote_id, - "isRDP": isRDP - }); + Future newPortForward(String remoteId, bool isRDP) async { + final msg = jsonEncode( + {"type": WindowType.PortForward.index, "id": remoteId, "isRDP": isRDP}); try { final ids = await DesktopMultiWindow.getAllSubWindowIds(); diff --git a/src/client.rs b/src/client.rs index 25061bcfe..32c0003fd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1278,11 +1278,11 @@ impl LoginConfigHandler { /// /// * `username` - The name of the peer. /// * `pi` - The peer info. - pub fn handle_peer_info(&mut self, pi: PeerInfo) { + pub fn handle_peer_info(&mut self, pi: &PeerInfo) { if !pi.version.is_empty() { self.version = hbb_common::get_version_number(&pi.version); } - self.features = pi.features.into_option(); + self.features = pi.features.clone().into_option(); let serde = PeerInfoSerde { username: pi.username.clone(), hostname: pi.hostname.clone(), diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 716afacb9..6a3d19880 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -205,8 +205,8 @@ pub fn session_get_custom_image_quality(id: String) -> Option> { } pub fn session_set_custom_image_quality(id: String, value: i32) { - if let Some(session) = SESSIONS.read().unwrap().get(&id) { - session.set_custom_image_quality(value); + if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { + session.save_custom_image_quality(value); } } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index d89ce2d3b..5ab6089a0 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -47,6 +47,11 @@ impl Session { self.lc.read().unwrap().image_quality.clone() } + /// Get custom image quality. + pub fn get_custom_image_quality(&self) -> Vec { + self.lc.read().unwrap().custom_image_quality.clone() + } + pub fn save_view_style(&mut self, value: String) { self.lc.write().unwrap().save_view_style(value); } @@ -634,7 +639,7 @@ impl Interface for Session { } } else if !self.is_port_forward() { if pi.displays.is_empty() { - self.lc.write().unwrap().handle_peer_info(pi); + self.lc.write().unwrap().handle_peer_info(&pi); self.update_privacy_mode(); self.msgbox("error", "Remote Error", "No Display"); return; @@ -647,9 +652,9 @@ impl Interface for Session { self.set_display(current.x, current.y, current.width, current.height); } self.update_privacy_mode(); + // Save recent peers, then push event to flutter. So flutter can refresh peer page. + self.lc.write().unwrap().handle_peer_info(&pi); self.set_peer_info(&pi); - self.lc.write().unwrap().handle_peer_info(pi); - if self.is_file_transfer() { self.close_success(); } else if !self.is_port_forward() {