From d6463f95b9830a4e2979c956da7f78146aa98513 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Fri, 19 Dec 2025 20:45:22 +0800 Subject: [PATCH] refact: remote toolbar show/hide (#13843) Signed-off-by: fufesou --- flutter/lib/consts.dart | 1 + flutter/lib/desktop/pages/remote_page.dart | 1 - .../lib/desktop/pages/remote_tab_page.dart | 4 +- .../lib/desktop/pages/view_camera_page.dart | 1 - .../desktop/pages/view_camera_tab_page.dart | 4 +- .../lib/desktop/widgets/remote_toolbar.dart | 99 +++++++++++++------ 6 files changed, 73 insertions(+), 37 deletions(-) diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index cf91e14d2..6c68d3d91 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -120,6 +120,7 @@ const String kOptionApproveMode = "approve-mode"; const String kOptionAllowNumericOneTimePassword = "allow-numeric-one-time-password"; const String kOptionCollapseToolbar = "collapse_toolbar"; +const String kOptionHideToolbar = "hide-toolbar"; const String kOptionShowRemoteCursor = "show_remote_cursor"; const String kOptionFollowRemoteCursor = "follow_remote_cursor"; const String kOptionFollowRemoteWindow = "follow_remote_window"; diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index e31196dc8..a752efe6b 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -509,7 +509,6 @@ class _RemotePageState extends State () => _ffi.ffiModel.pi.isSet.isFalse ? Container(color: Colors.transparent) : Obx(() { - widget.toolbarState.initShow(sessionId); _ffi.textureModel.updateCurrentDisplay(peerDisplay.value); return ImagePaint( id: widget.id, diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index 6a9f1e89d..af285ac35 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -251,11 +251,11 @@ class _ConnectionTabPageState extends State { MenuEntryButton( childBuilder: (TextStyle? style) => Obx(() => Text( translate( - toolbarState.show.isTrue ? 'Hide Toolbar' : 'Show Toolbar'), + toolbarState.hide.isTrue ? 'Show Toolbar' : 'Hide Toolbar'), style: style, )), proc: () { - toolbarState.switchShow(sessionId); + toolbarState.switchHide(sessionId); cancelFunc(); }, padding: padding, diff --git a/flutter/lib/desktop/pages/view_camera_page.dart b/flutter/lib/desktop/pages/view_camera_page.dart index 4be6fdc57..6be074b59 100644 --- a/flutter/lib/desktop/pages/view_camera_page.dart +++ b/flutter/lib/desktop/pages/view_camera_page.dart @@ -465,7 +465,6 @@ class _ViewCameraPageState extends State () => _ffi.ffiModel.pi.isSet.isFalse ? Container(color: Colors.transparent) : Obx(() { - widget.toolbarState.initShow(sessionId); _ffi.textureModel.updateCurrentDisplay(peerDisplay.value); return ImagePaint( id: widget.id, diff --git a/flutter/lib/desktop/pages/view_camera_tab_page.dart b/flutter/lib/desktop/pages/view_camera_tab_page.dart index 4c04cb8b8..36fa623ff 100644 --- a/flutter/lib/desktop/pages/view_camera_tab_page.dart +++ b/flutter/lib/desktop/pages/view_camera_tab_page.dart @@ -250,11 +250,11 @@ class _ViewCameraTabPageState extends State { MenuEntryButton( childBuilder: (TextStyle? style) => Obx(() => Text( translate( - toolbarState.show.isTrue ? 'Hide Toolbar' : 'Show Toolbar'), + toolbarState.hide.isTrue ? 'Show Toolbar' : 'Hide Toolbar'), style: style, )), proc: () { - toolbarState.switchShow(sessionId); + toolbarState.switchHide(sessionId); cancelFunc(); }, padding: padding, diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index bc3757f1e..06675f9ec 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -31,8 +31,12 @@ import 'package:flutter_hbb/common/widgets/custom_scale_base.dart'; class ToolbarState { late RxBool _pin; - bool isShowInited = false; - RxBool show = false.obs; + RxBool collapse = false.obs; + RxBool hide = false.obs; + + // Track initialization state to prevent flickering + final RxBool initialized = false.obs; + bool _isInitializing = false; ToolbarState() { _pin = RxBool(false); @@ -53,19 +57,39 @@ class ToolbarState { bool get pin => _pin.value; - switchShow(SessionID sessionId) async { - bind.sessionToggleOption( - sessionId: sessionId, value: kOptionCollapseToolbar); - show.value = !show.value; + /// Initialize all toolbar states from session options. + /// This should be called once when the toolbar is first created. + Future init(SessionID sessionId) async { + if (initialized.value || _isInitializing) return; + _isInitializing = true; + + try { + // Load both states in parallel for better performance + final results = await Future.wait([ + bind.sessionGetToggleOption( + sessionId: sessionId, arg: kOptionCollapseToolbar), + bind.sessionGetToggleOption( + sessionId: sessionId, arg: kOptionHideToolbar), + ]); + + collapse.value = results[0] ?? false; + hide.value = results[1] ?? false; + } finally { + _isInitializing = false; + initialized.value = true; + } } - initShow(SessionID sessionId) async { - if (!isShowInited) { - show.value = !(await bind.sessionGetToggleOption( - sessionId: sessionId, arg: kOptionCollapseToolbar) ?? - false); - isShowInited = true; - } + switchCollapse(SessionID sessionId) async { + bind.sessionToggleOption( + sessionId: sessionId, value: kOptionCollapseToolbar); + collapse.value = !collapse.value; + } + + // Switch hide state for entire toolbar visibility + switchHide(SessionID sessionId) async { + bind.sessionToggleOption(sessionId: sessionId, value: kOptionHideToolbar); + hide.value = !hide.value; } switchPin() async { @@ -237,7 +261,8 @@ class _RemoteToolbarState extends State { // setState(() {}); } - RxBool get show => widget.state.show; + RxBool get collapse => widget.state.collapse; + RxBool get hide => widget.state.hide; bool get pin => widget.state.pin; PeerInfo get pi => widget.ffi.ffiModel.pi; @@ -258,6 +283,8 @@ class _RemoteToolbarState extends State { arg: 'remote-menubar-drag-x') ?? '0.5') ?? 0.5; + // Initialize toolbar states (collapse, hide) from session options + widget.state.init(widget.ffi.sessionId); }); _debouncerHide = Debouncer( @@ -277,8 +304,8 @@ class _RemoteToolbarState extends State { } _debouncerHideProc(int v) { - if (!pin && show.isTrue && _isCursorOverImage && _dragging.isFalse) { - show.value = false; + if (!pin && collapse.isFalse && _isCursorOverImage && _dragging.isFalse) { + collapse.value = true; } } @@ -291,17 +318,27 @@ class _RemoteToolbarState extends State { @override Widget build(BuildContext context) { - return Align( - alignment: Alignment.topCenter, - child: Obx(() => show.value - ? _buildToolbar(context) - : _buildDraggableShowHide(context)), - ); + return Obx(() { + // Wait for initialization to complete to prevent flickering + if (!widget.state.initialized.value) { + return const SizedBox.shrink(); + } + // If toolbar is hidden, return empty widget + if (hide.value) { + return const SizedBox.shrink(); + } + return Align( + alignment: Alignment.topCenter, + child: collapse.isFalse + ? _buildToolbar(context) + : _buildDraggableCollapse(context), + ); + }); } - Widget _buildDraggableShowHide(BuildContext context) { + Widget _buildDraggableCollapse(BuildContext context) { return Obx(() { - if (show.isTrue && _dragging.isFalse) { + if (collapse.isFalse && _dragging.isFalse) { triggerAutoHide(); } final borderRadius = BorderRadius.vertical( @@ -398,7 +435,7 @@ class _RemoteToolbarState extends State { ), ), ), - _buildDraggableShowHide(context), + _buildDraggableCollapse(context), ], ); } @@ -2491,7 +2528,7 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { double left = 0.0; double right = 1.0; - RxBool get show => widget.toolbarState.show; + RxBool get collapse => widget.toolbarState.collapse; @override initState() { @@ -2614,20 +2651,20 @@ class _DraggableShowHideState extends State<_DraggableShowHide> { )), buttonWrapper( () => setState(() { - widget.toolbarState.switchShow(widget.sessionId); + widget.toolbarState.switchCollapse(widget.sessionId); }), Obx((() => Tooltip( - message: - translate(show.isTrue ? 'Hide Toolbar' : 'Show Toolbar'), + message: translate( + collapse.isFalse ? 'Hide Toolbar' : 'Show Toolbar'), child: Icon( - show.isTrue ? Icons.expand_less : Icons.expand_more, + collapse.isFalse ? Icons.expand_less : Icons.expand_more, size: iconSize, ), ))), ), if (isWebDesktop) Obx(() { - if (show.isTrue) { + if (collapse.isFalse) { return Offstage(); } else { return buttonWrapper(