diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 93c151bee..43b925904 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:get/instance_manager.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:window_manager/window_manager.dart'; @@ -66,11 +67,11 @@ final ButtonStyle flatButtonStyle = TextButton.styleFrom( ), ); -backToHomePage() { +closeConnection({String? id}) { if (isAndroid || isIOS) { Navigator.popUntil(globalKey.currentContext!, ModalRoute.withName("/")); } else { - // TODO desktop + closeTab(id); } } @@ -306,7 +307,7 @@ void msgBox( 0, wrap(translate('OK'), () { dialogManager.dismissAll(); - backToHomePage(); + closeConnection(); })); } if (hasCancel == null) { @@ -482,7 +483,7 @@ RadioListTile getRadio( CheckboxListTile getToggle( String id, void Function(void Function()) setState, option, name, {FFI? ffi}) { - final opt = bind.getSessionToggleOptionSync(id: id, arg: option); + final opt = bind.sessionGetToggleOptionSync(id: id, arg: option); return CheckboxListTile( value: opt, onChanged: (v) { diff --git a/flutter/lib/desktop/pages/connection_tab_page.dart b/flutter/lib/desktop/pages/connection_tab_page.dart index 2a831785e..eb8614dd4 100644 --- a/flutter/lib/desktop/pages/connection_tab_page.dart +++ b/flutter/lib/desktop/pages/connection_tab_page.dart @@ -104,6 +104,7 @@ class _ConnectionTabPageState extends State void onRemoveId(String id) { DesktopTabBar.onClose(this, tabController, tabs, id); + ffi(id).close(); if (tabs.length == 0) { WindowController.fromWindowId(windowId()).close(); } diff --git a/flutter/lib/desktop/pages/file_manager_tab_page.dart b/flutter/lib/desktop/pages/file_manager_tab_page.dart index 4c2dc3c5e..aa8c60afc 100644 --- a/flutter/lib/desktop/pages/file_manager_tab_page.dart +++ b/flutter/lib/desktop/pages/file_manager_tab_page.dart @@ -93,6 +93,7 @@ class _FileManagerTabPageState extends State void onRemoveId(String id) { DesktopTabBar.onClose(this, tabController, tabs, id); + ffi(id).close(); if (tabs.length == 0) { WindowController.fromWindowId(windowId()).close(); } diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index f3996b31b..8aba86d0f 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -60,7 +60,7 @@ class _RemotePageState extends State WidgetsBinding.instance.addPostFrameCallback((_) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); _ffi.dialogManager - .showLoading(translate('Connecting...'), onCancel: backToHomePage); + .showLoading(translate('Connecting...'), onCancel: closeConnection); }); if (!Platform.isLinux) { Wakelock.enable(); @@ -490,7 +490,7 @@ class _RemotePageState extends State }), )) ]; - final cursor = bind.getSessionToggleOptionSync( + final cursor = bind.sessionGetToggleOptionSync( id: widget.id, arg: 'show-remote-cursor'); if (keyboard || cursor) { paints.add(CursorPaint( @@ -565,7 +565,7 @@ class _RemotePageState extends State more.add(PopupMenuItem( child: Text(translate('Insert Lock')), value: 'lock')); if (pi.platform == 'Windows' && - await bind.getSessionToggleOption(id: id, arg: 'privacy-mode') != + await bind.sessionGetToggleOption(id: id, arg: 'privacy-mode') != true) { more.add(PopupMenuItem( child: Text(translate( @@ -610,7 +610,7 @@ class _RemotePageState extends State // TODO icon diff // null means no session of id // empty string means no password - var password = await bind.getSessionOption(id: id, arg: "os-password"); + var password = await bind.sessionGetOption(id: id, arg: "os-password"); if (password != null) { bind.sessionInputOsPassword(id: widget.id, value: password); } else { @@ -837,12 +837,12 @@ class QualityMonitor extends StatelessWidget { void showOptions(String id) async { final _ffi = ffi(id); - String quality = await bind.getSessionImageQuality(id: id) ?? 'balanced'; + String quality = await bind.sessionGetImageQuality(id: id) ?? 'balanced'; if (quality == '') quality = 'balanced'; String viewStyle = - await bind.getSessionOption(id: id, arg: 'view-style') ?? ''; + await bind.sessionGetOption(id: id, arg: 'view-style') ?? ''; String scrollStyle = - await bind.getSessionOption(id: id, arg: 'scroll-style') ?? ''; + await bind.sessionGetOption(id: id, arg: 'scroll-style') ?? ''; var displays = []; final pi = _ffi.ffiModel.pi; final image = _ffi.ffiModel.getConnectionImage(); @@ -957,8 +957,8 @@ void showOptions(String id) async { void showSetOSPassword( String id, bool login, OverlayDialogManager dialogManager) async { final controller = TextEditingController(); - var password = await bind.getSessionOption(id: id, arg: "os-password") ?? ""; - var autoLogin = await bind.getSessionOption(id: id, arg: "auto-login") != ""; + var password = await bind.sessionGetOption(id: id, arg: "os-password") ?? ""; + var autoLogin = await bind.sessionGetOption(id: id, arg: "auto-login") != ""; controller.text = password; dialogManager.show((setState, close) { return CustomAlertDialog( diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index 5a5780431..e260ef391 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -195,6 +195,17 @@ class _PeerCardState extends State<_PeerCard> } else if (value == 'file') { _connect(id, isFileTransfer: true); } 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 == 'connect') { _connect(id, isFileTransfer: false); } else if (value == 'ab-delete') { @@ -425,6 +436,8 @@ class RecentPeerCard extends BasePeerCard { PopupMenuItem( child: Text(translate('Unremember Password')), value: 'unremember-password'), + PopupMenuItem( + child: Text(translate('Add to Favorites')), value: 'add-fav'), ]; } } @@ -469,6 +482,8 @@ class DiscoveredPeerCard extends BasePeerCard { PopupMenuItem( child: Text(translate('Unremember Password')), value: 'unremember-password'), + PopupMenuItem( + child: Text(translate('Add to Favorites')), value: 'add-fav'), ]; } } diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 3398ab33d..f8da7b429 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -10,6 +10,25 @@ const double _kTabBarHeight = kDesktopRemoteTabBarHeight; const double _kIconSize = 18; const double _kDividerIndent = 10; const double _kAddIconSize = _kTabBarHeight - 15; +final tabBarKey = GlobalKey(); + +void closeTab(String? id) { + final tabBar = tabBarKey.currentWidget as TabBar?; + if (tabBar == null) return; + final tabs = tabBar.tabs as List<_Tab>; + if (id == null) { + final current = tabBar.controller?.index; + if (current == null) return; + tabs[current].onClose(); + } else { + for (final tab in tabs) { + if (tab.label == id) { + tab.onClose(); + break; + } + } + } +} class TabInfo { late final String label; @@ -59,6 +78,7 @@ class DesktopTabBar extends StatelessWidget { ), Flexible( child: Obx(() => TabBar( + key: tabBarKey, indicatorColor: _theme.indicatorColor, labelPadding: const EdgeInsets.symmetric( vertical: 0, horizontal: 0), diff --git a/flutter/lib/mobile/pages/file_manager_page.dart b/flutter/lib/mobile/pages/file_manager_page.dart index c361e7b7c..87169b987 100644 --- a/flutter/lib/mobile/pages/file_manager_page.dart +++ b/flutter/lib/mobile/pages/file_manager_page.dart @@ -29,7 +29,7 @@ class _FileManagerPageState extends State { gFFI.connect(widget.id, isFileTransfer: true); WidgetsBinding.instance.addPostFrameCallback((_) { gFFI.dialogManager - .showLoading(translate('Connecting...'), onCancel: backToHomePage); + .showLoading(translate('Connecting...'), onCancel: closeConnection); }); gFFI.ffiModel.updateEventListener(widget.id); Wakelock.enable(); diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 6a5be8b8d..ceb3df0ff 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -51,7 +51,7 @@ class _RemotePageState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); gFFI.dialogManager - .showLoading(translate('Connecting...'), onCancel: backToHomePage); + .showLoading(translate('Connecting...'), onCancel: closeConnection); _interval = Timer.periodic(Duration(milliseconds: 30), (timer) => interval()); }); @@ -623,7 +623,7 @@ class _RemotePageState extends State { Widget getBodyForDesktopWithListener(bool keyboard) { var paints = [ImagePaint()]; - final cursor = bind.getSessionToggleOptionSync( + final cursor = bind.sessionGetToggleOptionSync( id: widget.id, arg: 'show-remote-cursor'); if (keyboard || cursor) { paints.add(CursorPaint()); @@ -694,7 +694,7 @@ class _RemotePageState extends State { more.add(PopupMenuItem( child: Text(translate('Insert Lock')), value: 'lock')); if (pi.platform == 'Windows' && - await bind.getSessionToggleOption(id: id, arg: 'privacy-mode') != + await bind.sessionGetToggleOption(id: id, arg: 'privacy-mode') != true) { more.add(PopupMenuItem( child: Text(translate((gFFI.ffiModel.inputBlocked ? 'Unb' : 'B') + @@ -738,7 +738,7 @@ class _RemotePageState extends State { // FIXME: // null means no session of id // empty string means no password - var password = await bind.getSessionOption(id: id, arg: "os-password"); + var password = await bind.sessionGetOption(id: id, arg: "os-password"); if (password != null) { bind.sessionInputOsPassword(id: widget.id, value: password); } else { @@ -1012,10 +1012,10 @@ class QualityMonitor extends StatelessWidget { } void showOptions(String id, OverlayDialogManager dialogManager) async { - String quality = await bind.getSessionImageQuality(id: id) ?? 'balanced'; + String quality = await bind.sessionGetImageQuality(id: id) ?? 'balanced'; if (quality == '') quality = 'balanced'; String viewStyle = - await bind.getSessionOption(id: id, arg: 'view-style') ?? ''; + await bind.sessionGetOption(id: id, arg: 'view-style') ?? ''; var displays = []; final pi = gFFI.ffiModel.pi; final image = gFFI.ffiModel.getConnectionImage(); @@ -1113,8 +1113,8 @@ void showOptions(String id, OverlayDialogManager dialogManager) async { void showSetOSPassword( String id, bool login, OverlayDialogManager dialogManager) async { final controller = TextEditingController(); - var password = await bind.getSessionOption(id: id, arg: "os-password") ?? ""; - var autoLogin = await bind.getSessionOption(id: id, arg: "auto-login") != ""; + var password = await bind.sessionGetOption(id: id, arg: "os-password") ?? ""; + var autoLogin = await bind.sessionGetOption(id: id, arg: "auto-login") != ""; controller.text = password; dialogManager.show((setState, close) { return CustomAlertDialog( diff --git a/flutter/lib/mobile/pages/scan_page.dart b/flutter/lib/mobile/pages/scan_page.dart index 9f6c36ca8..2487c0f58 100644 --- a/flutter/lib/mobile/pages/scan_page.dart +++ b/flutter/lib/mobile/pages/scan_page.dart @@ -132,7 +132,7 @@ class _ScanPageState extends State { } void showServerSettingFromQr(String data) async { - backToHomePage(); + closeConnection(); await controller?.pauseCamera(); if (!data.startsWith('config=')) { showToast('Invalid QR code'); diff --git a/flutter/lib/mobile/widgets/dialog.dart b/flutter/lib/mobile/widgets/dialog.dart index e0f98443b..d648cd497 100644 --- a/flutter/lib/mobile/widgets/dialog.dart +++ b/flutter/lib/mobile/widgets/dialog.dart @@ -157,7 +157,7 @@ void setTemporaryPasswordLengthDialog( void enterPasswordDialog(String id, OverlayDialogManager dialogManager) async { final controller = TextEditingController(); - var remember = await bind.getSessionRemember(id: id) ?? false; + var remember = await bind.sessionGetRemember(id: id) ?? false; dialogManager.dismissAll(); dialogManager.show((setState, close) { return CustomAlertDialog( @@ -184,7 +184,7 @@ void enterPasswordDialog(String id, OverlayDialogManager dialogManager) async { style: flatButtonStyle, onPressed: () { close(); - backToHomePage(); + closeConnection(); }, child: Text(translate('Cancel')), ), @@ -196,7 +196,7 @@ void enterPasswordDialog(String id, OverlayDialogManager dialogManager) async { gFFI.login(id, text, remember); close(); dialogManager.showLoading(translate('Logging in...'), - onCancel: backToHomePage); + onCancel: closeConnection); }, child: Text(translate('OK')), ), @@ -214,7 +214,7 @@ void wrongPasswordDialog(String id, OverlayDialogManager dialogManager) { style: flatButtonStyle, onPressed: () { close(); - backToHomePage(); + closeConnection(); }, child: Text(translate('Cancel')), ), diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 18fe6a7f9..dda22a779 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -287,7 +287,7 @@ class FfiModel with ChangeNotifier { bind.sessionReconnect(id: id); clearPermissions(); dialogManager.showLoading(translate('Connecting...'), - onCancel: backToHomePage); + onCancel: closeConnection); }); _reconnects *= 2; } else { @@ -312,7 +312,7 @@ class FfiModel with ChangeNotifier { } } else { _touchMode = - await bind.getSessionOption(id: peerId, arg: "touch-mode") != ''; + await bind.sessionGetOption(id: peerId, arg: "touch-mode") != ''; } if (evt['is_file_transfer'] == "true") { @@ -335,7 +335,7 @@ class FfiModel with ChangeNotifier { if (displays.length > 0) { parent.target?.dialogManager.showLoading( translate('Connected, waiting for image...'), - onCancel: backToHomePage); + onCancel: closeConnection); _waitForImage = true; _reconnects = 1; } @@ -471,7 +471,7 @@ class CanvasModel with ChangeNotifier { double get tabBarHeight => _tabBarHeight; void updateViewStyle() async { - final style = await bind.getSessionOption(id: id, arg: 'view-style'); + final style = await bind.sessionGetOption(id: id, arg: 'view-style'); if (style == null) { return; } @@ -517,7 +517,7 @@ class CanvasModel with ChangeNotifier { } updateScrollStyle() async { - final style = await bind.getSessionOption(id: id, arg: 'scroll-style'); + final style = await bind.sessionGetOption(id: id, arg: 'scroll-style'); if (style == 'scrollbar') { _scrollStyle = ScrollStyle.scrollbar; _scrollX = 0.0; @@ -863,7 +863,7 @@ class QualityMonitorModel with ChangeNotifier { QualityMonitorData get data => _data; checkShowQualityMonitor(String id) async { - final show = await bind.getSessionToggleOption( + final show = await bind.sessionGetToggleOption( id: id, arg: 'show-quality-monitor') == true; if (_show != show) { diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 686111715..44d48ca8c 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -116,7 +116,7 @@ pub fn session_connect( Ok(()) } -pub fn get_session_remember(id: String) -> Option { +pub fn session_get_remember(id: String) -> Option { if let Some(session) = SESSIONS.read().unwrap().get(&id) { Some(session.get_remember()) } else { @@ -124,7 +124,7 @@ pub fn get_session_remember(id: String) -> Option { } } -pub fn get_session_toggle_option(id: String, arg: String) -> Option { +pub fn session_get_toggle_option(id: String, arg: String) -> Option { if let Some(session) = SESSIONS.read().unwrap().get(&id) { Some(session.get_toggle_option(&arg)) } else { @@ -132,12 +132,12 @@ pub fn get_session_toggle_option(id: String, arg: String) -> Option { } } -pub fn get_session_toggle_option_sync(id: String, arg: String) -> SyncReturn { - let res = get_session_toggle_option(id, arg) == Some(true); +pub fn session_get_toggle_option_sync(id: String, arg: String) -> SyncReturn { + let res = session_get_toggle_option(id, arg) == Some(true); SyncReturn(res) } -pub fn get_session_image_quality(id: String) -> Option { +pub fn session_get_image_quality(id: String) -> Option { if let Some(session) = SESSIONS.read().unwrap().get(&id) { Some(session.get_image_quality()) } else { @@ -145,7 +145,7 @@ pub fn get_session_image_quality(id: String) -> Option { } } -pub fn get_session_option(id: String, arg: String) -> Option { +pub fn session_get_option(id: String, arg: String) -> Option { if let Some(session) = SESSIONS.read().unwrap().get(&id) { Some(session.get_option(&arg)) } else {