diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 66653a746..662f7cbd2 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -2,3 +2,5 @@ const double kDesktopRemoteTabBarHeight = 48.0; const String kAppTypeMain = "main"; const String kAppTypeDesktopRemote = "remote"; const String kAppTypeDesktopFileTransfer = "file transfer"; +const String kTabLabelHomePage = "Home"; +const String kTabLabelSettingPage = "Settings"; diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 7a80a64a1..182f1d0b4 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -57,7 +57,6 @@ class _ConnectionPageState extends State { @override Widget build(BuildContext context) { return Container( - decoration: BoxDecoration(color: isDarkTheme() ? null : MyTheme.grayBg), child: Column( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, diff --git a/flutter/lib/desktop/pages/connection_tab_page.dart b/flutter/lib/desktop/pages/connection_tab_page.dart index ffe984943..a86afb683 100644 --- a/flutter/lib/desktop/pages/connection_tab_page.dart +++ b/flutter/lib/desktop/pages/connection_tab_page.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:math'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; @@ -9,7 +8,6 @@ import 'package:flutter_hbb/desktop/pages/remote_page.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:get/get.dart'; -import 'package:window_manager/window_manager.dart'; import '../../models/model.dart'; @@ -26,24 +24,23 @@ class _ConnectionTabPageState extends State with TickerProviderStateMixin { // refactor List when using multi-tab // this singleton is only for test - var connectionIds = RxList.empty(growable: true); - var initialIndex = 0; + RxList tabs = RxList.empty(growable: true); late Rx tabController; static final Rx _selected = 0.obs; + IconData icon = Icons.desktop_windows_sharp; var connectionMap = RxList.empty(growable: true); _ConnectionTabPageState(Map params) { if (params['id'] != null) { - connectionIds.add(params['id']); + tabs.add(TabInfo(label: params['id'], icon: icon)); } } @override void initState() { super.initState(); - tabController = - TabController(length: connectionIds.length, vsync: this).obs; + tabController = TabController(length: tabs.length, vsync: this).obs; rustDeskWinManager.setMethodHandler((call, fromWindowId) async { print( "call ${call.method} with args ${call.arguments} from window ${fromWindowId}"); @@ -52,23 +49,13 @@ class _ConnectionTabPageState extends State final args = jsonDecode(call.arguments); final id = args['id']; window_on_top(windowId()); - final indexOf = connectionIds.indexOf(id); - if (indexOf >= 0) { - initialIndex = indexOf; - tabController.value.animateTo(initialIndex, duration: Duration.zero); - } else { - connectionIds.add(id); - initialIndex = connectionIds.length - 1; - tabController.value = TabController( - length: connectionIds.length, - vsync: this, - initialIndex: initialIndex); - } - _selected.value = initialIndex; + DesktopTabBar.onAdd(this, tabController, tabs, _selected, + TabInfo(label: id, icon: icon)); } else if (call.method == "onDestroy") { - print("executing onDestroy hook, closing ${connectionIds}"); - connectionIds.forEach((id) { - final tag = '${id}'; + print( + "executing onDestroy hook, closing ${tabs.map((tab) => tab.label).toList()}"); + tabs.forEach((tab) { + final tag = '${tab.label}'; ffi(tag).close().then((_) { Get.delete(tag: tag); }); @@ -83,20 +70,21 @@ class _ConnectionTabPageState extends State return Scaffold( body: Column( children: [ - Obx(() => DesktopTabBar( - controller: tabController, - tabs: connectionIds.toList(), - onTabClose: onRemoveId, - tabIcon: Icons.desktop_windows_sharp, - selected: _selected, - )), + DesktopTabBar( + controller: tabController, + tabs: tabs, + onTabClose: onRemoveId, + selected: _selected, + dark: isDarkTheme(), + mainTab: false, + ), Expanded( child: Obx(() => TabBarView( controller: tabController.value, - children: connectionIds - .map((e) => RemotePage( - key: ValueKey(e), - id: e, + children: tabs + .map((tab) => RemotePage( + key: ValueKey(tab.label), + id: tab.label, tabBarHeight: kDesktopRemoteTabBarHeight, )) //RemotePage(key: ValueKey(e), id: e)) .toList()))), @@ -106,15 +94,8 @@ class _ConnectionTabPageState extends State } void onRemoveId(String id) { - final indexOf = connectionIds.indexOf(id); - if (indexOf == -1) { - return; - } - connectionIds.removeAt(indexOf); - initialIndex = max(0, initialIndex - 1); - tabController.value = TabController( - length: connectionIds.length, vsync: this, initialIndex: initialIndex); - if (connectionIds.length == 0) { + DesktopTabBar.onClose(this, tabController, tabs, id); + if (tabs.length == 0) { WindowController.fromWindowId(windowId()).close(); } } diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 8496b0eda..770841f09 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart' hide MenuItem; import 'package:flutter/services.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/desktop/pages/connection_page.dart'; -import 'package:flutter_hbb/desktop/widgets/titlebar_widget.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; @@ -46,38 +45,17 @@ class _DesktopHomePageState extends State @override Widget build(BuildContext context) { - return Scaffold( - body: Column( - children: [ - DesktopTitleBar( - child: Center( - child: Text( - "RustDesk", - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold), - ), - ), - ), - Expanded( - child: Container( - child: Row( - children: [ - Flexible( - child: buildServerInfo(context), - flex: 1, - ), - Flexible( - child: buildServerBoard(context), - flex: 4, - ), - ], - ), - ), - ), - ], - ), + return Row( + children: [ + Flexible( + child: buildServerInfo(context), + flex: 1, + ), + Flexible( + child: buildServerBoard(context), + flex: 4, + ), + ], ); } diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart new file mode 100644 index 000000000..4d9a58f3b --- /dev/null +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -0,0 +1,15 @@ +import 'package:flutter/cupertino.dart'; + +class DesktopSettingPage extends StatefulWidget { + DesktopSettingPage({Key? key}) : super(key: key); + + @override + State createState() => _DesktopSettingPageState(); +} + +class _DesktopSettingPageState extends State { + @override + Widget build(BuildContext context) { + return Text("Settings"); + } +} diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart new file mode 100644 index 000000000..24611e439 --- /dev/null +++ b/flutter/lib/desktop/pages/desktop_tab_page.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hbb/common.dart'; +import 'package:flutter_hbb/consts.dart'; +import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; +import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart'; +import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; +import 'package:get/get.dart'; + +class DesktopTabPage extends StatefulWidget { + const DesktopTabPage({Key? key}) : super(key: key); + + @override + State createState() => _DesktopTabPageState(); +} + +class _DesktopTabPageState extends State + with TickerProviderStateMixin { + late Rx tabController; + late RxList tabs; + static final Rx _selected = 0.obs; + + @override + void initState() { + super.initState(); + tabs = RxList.from([ + TabInfo(label: kTabLabelHomePage, icon: Icons.home_sharp, closable: false) + ], growable: true); + tabController = + TabController(length: tabs.length, vsync: this, initialIndex: 0).obs; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + DesktopTabBar( + controller: tabController, + tabs: tabs, + onTabClose: onTabClose, + selected: _selected, + dark: isDarkTheme(), + mainTab: true, + onAddSetting: onAddSetting, + ), + Obx((() => Expanded( + child: TabBarView( + controller: tabController.value, + children: tabs.map((tab) { + switch (tab.label) { + case kTabLabelHomePage: + return DesktopHomePage(key: ValueKey(tab.label)); + case kTabLabelSettingPage: + return DesktopSettingPage(key: ValueKey(tab.label)); + default: + return Container(); + } + }).toList()), + ))), + ], + ), + ); + } + + void onTabClose(String label) { + DesktopTabBar.onClose(this, tabController, tabs, label); + } + + void onAddSetting() { + DesktopTabBar.onAdd(this, tabController, tabs, _selected, + TabInfo(label: kTabLabelSettingPage, icon: Icons.settings)); + } +} diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 581a38a3a..22d46c146 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -73,7 +73,6 @@ class _FileManagerPageState extends State return false; }, child: Scaffold( - backgroundColor: isDarkTheme() ? MyTheme.dark : MyTheme.grayBg, body: Row( children: [ Flexible(flex: 3, child: body(isLocal: true)), diff --git a/flutter/lib/desktop/pages/file_manager_tab_page.dart b/flutter/lib/desktop/pages/file_manager_tab_page.dart index c06dd331d..4c2dc3c5e 100644 --- a/flutter/lib/desktop/pages/file_manager_tab_page.dart +++ b/flutter/lib/desktop/pages/file_manager_tab_page.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:math'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; @@ -9,7 +8,6 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:get/get.dart'; -import 'package:window_manager/window_manager.dart'; /// File Transfer for multi tabs class FileManagerTabPage extends StatefulWidget { @@ -25,22 +23,21 @@ class _FileManagerTabPageState extends State with TickerProviderStateMixin { // refactor List when using multi-tab // this singleton is only for test - var connectionIds = List.empty(growable: true).obs; - var initialIndex = 0; + RxList tabs = List.empty(growable: true).obs; late Rx tabController; static final Rx _selected = 0.obs; + IconData icon = Icons.file_copy_sharp; _FileManagerTabPageState(Map params) { if (params['id'] != null) { - connectionIds.add(params['id']); + tabs.add(TabInfo(label: params['id'], icon: icon)); } } @override void initState() { super.initState(); - tabController = - TabController(length: connectionIds.length, vsync: this).obs; + tabController = TabController(length: tabs.length, vsync: this).obs; rustDeskWinManager.setMethodHandler((call, fromWindowId) async { print( "call ${call.method} with args ${call.arguments} from window ${fromWindowId}"); @@ -49,23 +46,13 @@ class _FileManagerTabPageState extends State final args = jsonDecode(call.arguments); final id = args['id']; window_on_top(windowId()); - final indexOf = connectionIds.indexOf(id); - if (indexOf >= 0) { - initialIndex = indexOf; - tabController.value.animateTo(initialIndex, duration: Duration.zero); - } else { - connectionIds.add(id); - initialIndex = connectionIds.length - 1; - tabController.value = TabController( - length: connectionIds.length, - initialIndex: initialIndex, - vsync: this); - } - _selected.value = initialIndex; + DesktopTabBar.onAdd(this, tabController, tabs, _selected, + TabInfo(label: id, icon: icon)); } else if (call.method == "onDestroy") { - print("executing onDestroy hook, closing ${connectionIds}"); - connectionIds.forEach((id) { - final tag = 'ft_${id}'; + print( + "executing onDestroy hook, closing ${tabs.map((tab) => tab.label).toList()}"); + tabs.forEach((tab) { + final tag = 'ft_${tab.label}'; ffi(tag).close().then((_) { Get.delete(tag: tag); }); @@ -80,23 +67,22 @@ class _FileManagerTabPageState extends State return Scaffold( body: Column( children: [ - Obx( - () => DesktopTabBar( - controller: tabController, - tabs: connectionIds.toList(), - onTabClose: onRemoveId, - tabIcon: Icons.file_copy_sharp, - selected: _selected, - ), + DesktopTabBar( + controller: tabController, + tabs: tabs, + onTabClose: onRemoveId, + selected: _selected, + dark: isDarkTheme(), + mainTab: false, ), Expanded( child: Obx( () => TabBarView( controller: tabController.value, - children: connectionIds - .map((e) => FileManagerPage( - key: ValueKey(e), - id: e)) //RemotePage(key: ValueKey(e), id: e)) + children: tabs + .map((tab) => FileManagerPage( + key: ValueKey(tab.label), + id: tab.label)) //RemotePage(key: ValueKey(e), id: e)) .toList()), ), ) @@ -106,15 +92,8 @@ class _FileManagerTabPageState extends State } void onRemoveId(String id) { - final indexOf = connectionIds.indexOf(id); - if (indexOf == -1) { - return; - } - connectionIds.removeAt(indexOf); - initialIndex = max(0, initialIndex - 1); - tabController.value = TabController( - length: connectionIds.length, initialIndex: initialIndex, vsync: this); - if (connectionIds.length == 0) { + DesktopTabBar.onClose(this, tabController, tabs, id); + 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 fc94d5be8..01744e8e7 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -263,7 +263,6 @@ class _RemotePageState extends State OverlayEntry(builder: (context) { _ffi.chatModel.setOverlayState(Overlay.of(context)); return Container( - color: Colors.black, child: getRawPointerAndKeyBody(getBodyForDesktop(keyboard))); }) ], @@ -500,25 +499,20 @@ class _RemotePageState extends State Widget getBodyForDesktop(bool keyboard) { var paints = [ - MouseRegion( - onEnter: (evt) { - bind.hostStopSystemKeyPropagate(stopped: false); - }, - onExit: (evt) { - bind.hostStopSystemKeyPropagate(stopped: true); - }, - child: Container( - color: MyTheme.canvasColor, - child: LayoutBuilder(builder: (context, constraints) { - Future.delayed(Duration.zero, () { - Provider.of(context, listen: false) - .updateViewStyle(); - }); - return ImagePaint( - id: widget.id, - ); - }), - )) + MouseRegion(onEnter: (evt) { + bind.hostStopSystemKeyPropagate(stopped: false); + }, onExit: (evt) { + bind.hostStopSystemKeyPropagate(stopped: true); + }, child: Container( + child: LayoutBuilder(builder: (context, constraints) { + Future.delayed(Duration.zero, () { + Provider.of(context, listen: false).updateViewStyle(); + }); + return ImagePaint( + id: widget.id, + ); + }), + )) ]; final cursor = bind.getSessionToggleOptionSync( id: widget.id, arg: 'show-remote-cursor'); diff --git a/flutter/lib/desktop/screen/desktop_file_transfer_screen.dart b/flutter/lib/desktop/screen/desktop_file_transfer_screen.dart index 06a71981e..03230b0b0 100644 --- a/flutter/lib/desktop/screen/desktop_file_transfer_screen.dart +++ b/flutter/lib/desktop/screen/desktop_file_transfer_screen.dart @@ -20,27 +20,11 @@ class DesktopFileTransferScreen extends StatelessWidget { ChangeNotifierProvider.value(value: gFFI.cursorModel), ChangeNotifierProvider.value(value: gFFI.canvasModel), ], - child: MaterialApp( - navigatorKey: globalKey, - debugShowCheckedModeBanner: false, - title: 'RustDesk - File Transfer', - theme: ThemeData( - primarySwatch: Colors.blue, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: FileManagerTabPage( - params: params, - ), - navigatorObservers: [ - // FirebaseAnalyticsObserver(analytics: analytics), - FlutterSmartDialog.observer - ], - builder: FlutterSmartDialog.init( - builder: isAndroid - ? (_, child) => AccessibilityListener( - child: child, - ) - : null)), + child: Scaffold( + body: FileManagerTabPage( + params: params, + ), + ), ); } } diff --git a/flutter/lib/desktop/screen/desktop_remote_screen.dart b/flutter/lib/desktop/screen/desktop_remote_screen.dart index c5e5ecbfa..95f6abed5 100644 --- a/flutter/lib/desktop/screen/desktop_remote_screen.dart +++ b/flutter/lib/desktop/screen/desktop_remote_screen.dart @@ -13,33 +13,16 @@ class DesktopRemoteScreen extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( - providers: [ - ChangeNotifierProvider.value(value: gFFI.ffiModel), - ChangeNotifierProvider.value(value: gFFI.imageModel), - ChangeNotifierProvider.value(value: gFFI.cursorModel), - ChangeNotifierProvider.value(value: gFFI.canvasModel), - ], - child: MaterialApp( - navigatorKey: globalKey, - debugShowCheckedModeBanner: false, - title: 'RustDesk - Remote Desktop', - theme: ThemeData( - primarySwatch: Colors.blue, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), - home: ConnectionTabPage( + providers: [ + ChangeNotifierProvider.value(value: gFFI.ffiModel), + ChangeNotifierProvider.value(value: gFFI.imageModel), + ChangeNotifierProvider.value(value: gFFI.cursorModel), + ChangeNotifierProvider.value(value: gFFI.canvasModel), + ], + child: Scaffold( + body: ConnectionTabPage( params: params, ), - navigatorObservers: [ - // FirebaseAnalyticsObserver(analytics: analytics), - FlutterSmartDialog.observer - ], - builder: FlutterSmartDialog.init( - builder: isAndroid - ? (_, child) => AccessibilityListener( - child: child, - ) - : null)), - ); + )); } } diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 1f6d1863c..7a4f1fc79 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -6,275 +6,319 @@ import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:get/get.dart'; -const Color _bgColor = Color.fromARGB(255, 231, 234, 237); -const Color _tabUnselectedColor = Color.fromARGB(255, 240, 240, 240); -const Color _tabHoverColor = Color.fromARGB(255, 245, 245, 245); -const Color _tabSelectedColor = Color.fromARGB(255, 255, 255, 255); -const Color _tabIconColor = MyTheme.accent50; -const Color _tabindicatorColor = _tabIconColor; -const Color _textColor = Color.fromARGB(255, 108, 111, 145); -const Color _iconColor = Color.fromARGB(255, 102, 106, 109); -const Color _iconHoverColor = Colors.black12; -const Color _iconPressedColor = Colors.black26; -const Color _dividerColor = Colors.black12; - const double _kTabBarHeight = kDesktopRemoteTabBarHeight; const double _kTabFixedWidth = 150; const double _kIconSize = 18; const double _kDividerIndent = 10; const double _kAddIconSize = _kTabBarHeight - 15; +class TabInfo { + late final String label; + late final IconData icon; + late final bool closable; + + TabInfo({required this.label, required this.icon, this.closable = true}); +} + class DesktopTabBar extends StatelessWidget { late final Rx controller; - late final List tabs; + late final RxList tabs; late final Function(String) onTabClose; - late final IconData tabIcon; late final Rx selected; + late final bool dark; + late final _Theme _theme; + late final bool mainTab; + late final Function()? onAddSetting; - DesktopTabBar( - {Key? key, - required this.controller, - required this.tabs, - required this.onTabClose, - required this.tabIcon, - required this.selected}) - : super(key: key); + DesktopTabBar({ + Key? key, + required this.controller, + required this.tabs, + required this.onTabClose, + required this.selected, + required this.dark, + required this.mainTab, + this.onAddSetting, + }) : _theme = dark ? _Theme.dark() : _Theme.light(), + super(key: key); @override Widget build(BuildContext context) { return Container( - color: _bgColor, height: _kTabBarHeight, child: Row( children: [ - Flexible( - child: Obx(() => TabBar( - indicatorColor: _tabindicatorColor, - indicatorSize: TabBarIndicatorSize.tab, - indicatorWeight: 4, - labelPadding: - const EdgeInsets.symmetric(vertical: 0, horizontal: 0), - indicatorPadding: EdgeInsets.zero, - isScrollable: true, - physics: BouncingScrollPhysics(), - controller: controller.value, - tabs: tabs - .asMap() - .entries - .map((e) => _Tab( - index: e.key, - text: e.value, - icon: tabIcon, + Expanded( + child: Row( + children: [ + Offstage( + offstage: !mainTab, + child: Row(children: [ + Image.asset('assets/logo.ico'), + Text("RustDesk"), + ]).paddingSymmetric(horizontal: 12, vertical: 5), + ), + Flexible( + child: Obx(() => TabBar( + indicator: BoxDecoration(), + indicatorColor: Colors.transparent, + labelPadding: const EdgeInsets.symmetric( + vertical: 0, horizontal: 0), + isScrollable: true, + indicatorWeight: 0.1, + physics: BouncingScrollPhysics(), + controller: controller.value, + tabs: tabs.asMap().entries.map((e) { + int index = e.key; + String label = e.value.label; + + return _Tab( + index: index, + label: label, + icon: e.value.icon, + closable: e.value.closable, selected: selected.value, onClose: () { - onTabClose(e.value); - // TODO - if (e.key <= selected.value) { + onTabClose(label); + if (index <= selected.value) { selected.value = max(0, selected.value - 1); } - controller.value.animateTo(selected.value); + controller.value.animateTo(selected.value, + duration: Duration.zero); }, onSelected: () { - selected.value = e.key; - controller.value.animateTo(e.key); + selected.value = index; + controller.value + .animateTo(index, duration: Duration.zero); }, - )) - .toList())), - ), - Padding( - padding: EdgeInsets.only(left: 10), - child: _AddButton(), + theme: _theme, + ); + }).toList())), + ), + Offstage( + offstage: mainTab, + child: _AddButton( + theme: _theme, + ).paddingOnly(left: 10), + ) + ], + ), ), + Offstage( + offstage: onAddSetting == null, + child: Tooltip( + message: translate("Settings"), + child: InkWell( + child: Icon( + Icons.menu, + color: _theme.unSelectedIconColor, + ), + onTap: () => onAddSetting?.call(), + ).paddingOnly(right: 10), + ), + ) ], ), ); } + + static onClose( + TickerProvider vsync, + Rx controller, + RxList tabs, + String label, + ) { + tabs.removeWhere((tab) => tab.label == label); + controller.value = TabController( + length: tabs.length, + vsync: vsync, + initialIndex: max(0, tabs.length - 1)); + } + + static onAdd(TickerProvider vsync, Rx controller, + RxList tabs, Rx selected, TabInfo tab) { + int index = tabs.indexWhere((e) => e.label == tab.label); + if (index >= 0) { + controller.value.animateTo(index, duration: Duration.zero); + selected.value = index; + } else { + tabs.add(tab); + controller.value = TabController( + length: tabs.length, vsync: vsync, initialIndex: tabs.length - 1); + controller.value.animateTo(tabs.length - 1, duration: Duration.zero); + selected.value = tabs.length - 1; + } + } } class _Tab extends StatelessWidget { late final int index; - late final String text; + late final String label; late final IconData icon; + late final bool closable; late final int selected; late final Function() onClose; late final Function() onSelected; final RxBool _hover = false.obs; + late final _Theme theme; - _Tab({ - Key? key, - required this.index, - required this.text, - required this.icon, - required this.selected, - required this.onClose, - required this.onSelected, - }) : super(key: key); + _Tab( + {Key? key, + required this.index, + required this.label, + required this.icon, + required this.closable, + required this.selected, + required this.onClose, + required this.onSelected, + required this.theme}) + : super(key: key); @override Widget build(BuildContext context) { bool is_selected = index == selected; bool show_divider = index != selected - 1 && index != selected; - return Obx( - (() => _Hoverable( - onHover: (hover) => _hover.value = hover, - onTapUp: () => onSelected(), - child: Container( - width: _kTabFixedWidth, - decoration: BoxDecoration( - color: is_selected - ? _tabSelectedColor - : _hover.value - ? _tabHoverColor - : _tabUnselectedColor, - ), - child: Row( - children: [ - Expanded( - child: Tab( - key: this.key, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 5), - child: Icon( - icon, - size: _kIconSize, - color: _tabIconColor, - ), - ), - Expanded( - child: Text( - text, - style: const TextStyle(color: _textColor), - ), - ), - _CloseButton( - tabHovered: _hover.value, - onClose: () => onClose(), - ), - ])), - ), - show_divider - ? VerticalDivider( - width: 1, - indent: _kDividerIndent, - endIndent: _kDividerIndent, - color: _dividerColor, - thickness: 1, - ) - : Container(), - ], - ), + return Ink( + width: _kTabFixedWidth, + child: InkWell( + onHover: (hover) => _hover.value = hover, + onTap: () => onSelected(), + child: Row( + children: [ + Expanded( + child: Tab( + key: this.key, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + icon, + size: _kIconSize, + color: is_selected + ? theme.selectedtabIconColor + : theme.unSelectedtabIconColor, + ).paddingSymmetric(horizontal: 5), + Expanded( + child: Text( + translate(label), + style: TextStyle( + color: is_selected + ? theme.selectedTextColor + : theme.unSelectedTextColor), + ), + ), + Obx((() => _CloseButton( + visiable: _hover.value && closable, + tabSelected: is_selected, + onClose: () => onClose(), + theme: theme, + ))), + ])), ), - )), + Offstage( + offstage: !show_divider, + child: VerticalDivider( + width: 1, + indent: _kDividerIndent, + endIndent: _kDividerIndent, + color: theme.dividerColor, + thickness: 1, + ), + ) + ], + ), + ), ); } } class _AddButton extends StatelessWidget { - final RxBool _hover = false.obs; - final RxBool _pressed = false.obs; + late final _Theme theme; _AddButton({ Key? key, + required this.theme, }) : super(key: key); @override Widget build(BuildContext context) { - return _Hoverable( - onHover: (hover) => _hover.value = hover, - onPressed: (pressed) => _pressed.value = pressed, - onTapUp: () => - rustDeskWinManager.call(WindowType.Main, "main_window_on_top", ""), - child: Obx((() => Container( - height: _kTabBarHeight, - decoration: ShapeDecoration( - shape: const CircleBorder(), - color: _pressed.value - ? _iconPressedColor - : _hover.value - ? _iconHoverColor - : Colors.transparent, - ), - child: const Icon( - Icons.add_sharp, - color: _iconColor, - size: _kAddIconSize, - ), - ))), + return Ink( + height: _kTabBarHeight, + child: InkWell( + customBorder: const CircleBorder(), + onTap: () => + rustDeskWinManager.call(WindowType.Main, "main_window_on_top", ""), + child: Icon( + Icons.add_sharp, + size: _kAddIconSize, + color: theme.unSelectedIconColor, + ), + ), ); } } class _CloseButton extends StatelessWidget { - final bool tabHovered; + final bool visiable; + final bool tabSelected; final Function onClose; - final RxBool _hover = false.obs; - final RxBool _pressed = false.obs; + late final _Theme theme; _CloseButton({ Key? key, - required this.tabHovered, + required this.visiable, + required this.tabSelected, required this.onClose, + required this.theme, }) : super(key: key); @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 5), - child: SizedBox( - width: _kIconSize, - child: tabHovered - ? Obx((() => _Hoverable( - onHover: (hover) => _hover.value = hover, - onPressed: (pressed) => _pressed.value = pressed, - onTapUp: () => onClose(), - child: Container( - color: _pressed.value - ? _iconPressedColor - : _hover.value - ? _iconHoverColor - : Colors.transparent, - child: const Icon( - Icons.close, - size: _kIconSize, - color: _iconColor, - )), - ))) - : Container(), - )); + return SizedBox( + width: _kIconSize, + child: Offstage( + offstage: !visiable, + child: InkWell( + customBorder: RoundedRectangleBorder(), + onTap: () => onClose(), + child: Icon( + Icons.close, + size: _kIconSize, + color: tabSelected + ? theme.selectedIconColor + : theme.unSelectedIconColor, + ), + ), + )).paddingSymmetric(horizontal: 5); } } -class _Hoverable extends StatelessWidget { - final Widget child; - final Function(bool hover) onHover; - final Function(bool pressed)? onPressed; - final Function()? onTapUp; +class _Theme { + late Color unSelectedtabIconColor; + late Color selectedtabIconColor; + late Color selectedTextColor; + late Color unSelectedTextColor; + late Color selectedIconColor; + late Color unSelectedIconColor; + late Color dividerColor; - const _Hoverable( - {Key? key, - required this.child, - required this.onHover, - this.onPressed, - this.onTapUp}) - : super(key: key); + _Theme.light() { + unSelectedtabIconColor = Color.fromARGB(255, 162, 203, 241); + selectedtabIconColor = MyTheme.accent; + selectedTextColor = Color.fromARGB(255, 26, 26, 26); + unSelectedTextColor = Color.fromARGB(255, 96, 96, 96); + selectedIconColor = Color.fromARGB(255, 26, 26, 26); + unSelectedIconColor = Color.fromARGB(255, 96, 96, 96); + dividerColor = Color.fromARGB(255, 238, 238, 238); + } - @override - Widget build(BuildContext context) { - return MouseRegion( - onEnter: (_) => onHover(true), - onExit: (_) => onHover(false), - child: onPressed == null && onTapUp == null - ? child - : GestureDetector( - onTapDown: (details) => onPressed?.call(true), - onTapUp: (details) { - onPressed?.call(false); - onTapUp?.call(); - }, - child: child, - )); + _Theme.dark() { + unSelectedtabIconColor = Color.fromARGB(255, 30, 65, 98); + selectedtabIconColor = MyTheme.accent; + selectedTextColor = Color.fromARGB(255, 255, 255, 255); + unSelectedTextColor = Color.fromARGB(255, 207, 207, 207); + selectedIconColor = Color.fromARGB(255, 215, 215, 215); + unSelectedIconColor = Color.fromARGB(255, 255, 255, 255); + dividerColor = Color.fromARGB(255, 64, 64, 64); } } diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index bfae7e097..000202f65 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; +import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/desktop/screen/desktop_file_transfer_screen.dart'; import 'package:flutter_hbb/desktop/screen/desktop_remote_screen.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; @@ -86,18 +86,44 @@ void runMainApp(bool startService) async { void runRemoteScreen(Map argument) async { await initEnv(kAppTypeDesktopRemote); runApp(GetMaterialApp( + navigatorKey: globalKey, + debugShowCheckedModeBanner: false, + title: 'RustDesk - Remote Desktop', theme: getCurrentTheme(), home: DesktopRemoteScreen( params: argument, ), + navigatorObservers: [ + // FirebaseAnalyticsObserver(analytics: analytics), + FlutterSmartDialog.observer + ], + builder: FlutterSmartDialog.init( + builder: isAndroid + ? (_, child) => AccessibilityListener( + child: child, + ) + : null), )); } void runFileTransferScreen(Map argument) async { await initEnv(kAppTypeDesktopFileTransfer); runApp(GetMaterialApp( + navigatorKey: globalKey, + debugShowCheckedModeBanner: false, + title: 'RustDesk - File Transfer', theme: getCurrentTheme(), - home: DesktopFileTransferScreen(params: argument))); + home: DesktopFileTransferScreen(params: argument), + navigatorObservers: [ + // FirebaseAnalyticsObserver(analytics: analytics), + FlutterSmartDialog.observer + ], + builder: FlutterSmartDialog.init( + builder: isAndroid + ? (_, child) => AccessibilityListener( + child: child, + ) + : null))); } class App extends StatelessWidget { @@ -121,7 +147,7 @@ class App extends StatelessWidget { title: 'RustDesk', theme: getCurrentTheme(), home: isDesktop - ? DesktopHomePage() + ? DesktopTabPage() : !isAndroid ? WebHomePage() : HomePage(),