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 dcbb0ef3e..5bd7e2469 100644 --- a/flutter/lib/desktop/pages/connection_tab_page.dart +++ b/flutter/lib/desktop/pages/connection_tab_page.dart @@ -84,11 +84,14 @@ class _ConnectionTabPageState extends State children: [ Obx(() => DesktopTabBar( controller: tabController, - tabs: connectionIds.toList(), + tabs: connectionIds + .map((e) => + TabInfo(label: e, icon: Icons.desktop_windows_sharp)) + .toList(), onTabClose: onRemoveId, - tabIcon: Icons.desktop_windows_sharp, selected: _selected, dark: isDarkTheme(), + mainTab: false, )), Expanded( child: Obx(() => TabBarView( 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..02ef0fea3 --- /dev/null +++ b/flutter/lib/desktop/pages/desktop_tab_page.dart @@ -0,0 +1,88 @@ +import 'dart:math'; + +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: [ + Obx((() => DesktopTabBar( + controller: tabController, + tabs: tabs.toList(), + onTabClose: onTabClose, + selected: _selected, + dark: isDarkTheme(), + mainTab: true, + onMenu: onTabbarMenu, + ))), + 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) { + tabs.removeWhere((tab) => tab.label == label); + tabController.value = TabController( + length: tabs.length, + vsync: this, + initialIndex: max(0, tabs.length - 1)); + } + + void onTabbarMenu() { + int index = tabs.indexWhere((tab) => tab.label == kTabLabelSettingPage); + if (index >= 0) { + tabController.value.animateTo(index, duration: Duration.zero); + _selected.value = index; + } else { + tabs.add(TabInfo(label: kTabLabelSettingPage, icon: Icons.settings)); + tabController.value = TabController( + length: tabs.length, vsync: this, initialIndex: tabs.length - 1); + tabController.value.animateTo(tabs.length - 1, duration: Duration.zero); + _selected.value = tabs.length - 1; + } + } +} 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 791d3c068..c4348fad0 100644 --- a/flutter/lib/desktop/pages/file_manager_tab_page.dart +++ b/flutter/lib/desktop/pages/file_manager_tab_page.dart @@ -82,11 +82,13 @@ class _FileManagerTabPageState extends State Obx( () => DesktopTabBar( controller: tabController, - tabs: connectionIds.toList(), + tabs: connectionIds + .map((e) => TabInfo(label: e, icon: Icons.file_copy_sharp)) + .toList(), onTabClose: onRemoveId, - tabIcon: Icons.file_copy_sharp, selected: _selected, dark: isDarkTheme(), + mainTab: false, ), ), Expanded( 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 420267b44..41dca26c2 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -12,76 +12,109 @@ 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 List 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()? onMenu; - DesktopTabBar( - {Key? key, - required this.controller, - required this.tabs, - required this.onTabClose, - required this.tabIcon, - required this.selected, - required this.dark}) - : _theme = dark ? _Theme.dark() : _Theme.light(), + DesktopTabBar({ + Key? key, + required this.controller, + required this.tabs, + required this.onTabClose, + required this.selected, + required this.dark, + required this.mainTab, + this.onMenu, + }) : _theme = dark ? _Theme.dark() : _Theme.light(), super(key: key); @override Widget build(BuildContext context) { return Container( height: _kTabBarHeight, - child: Scaffold( - backgroundColor: _theme.bgColor, - body: Row( - children: [ - Flexible( - child: Obx(() => TabBar( - indicator: BoxDecoration(), - indicatorColor: Colors.transparent, - labelPadding: - const EdgeInsets.symmetric(vertical: 0, horizontal: 0), - isScrollable: true, - physics: BouncingScrollPhysics(), - controller: controller.value, - tabs: tabs - .asMap() - .entries - .map((e) => _Tab( - index: e.key, - text: e.value, - icon: tabIcon, - selected: selected.value, - onClose: () { - onTabClose(e.value); - if (e.key <= selected.value) { - selected.value = max(0, selected.value - 1); - } - controller.value.animateTo(selected.value, - duration: Duration.zero); - }, - onSelected: () { - selected.value = e.key; - controller.value - .animateTo(e.key, duration: Duration.zero); - }, - theme: _theme, - )) - .toList())), + child: Row( + children: [ + 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(label); + if (index <= selected.value) { + selected.value = max(0, selected.value - 1); + } + controller.value.animateTo(selected.value, + duration: Duration.zero); + }, + onSelected: () { + selected.value = index; + controller.value + .animateTo(index, duration: Duration.zero); + }, + theme: _theme, + ); + }).toList())), + ), + Offstage( + offstage: mainTab, + child: _AddButton( + theme: _theme, + ).paddingOnly(left: 10), + ) + ], ), - Padding( - padding: EdgeInsets.only(left: 10), - child: _AddButton( - theme: _theme, + ), + Offstage( + offstage: onMenu == null, + child: InkWell( + child: Icon( + Icons.menu, + color: _theme.unSelectedIconColor, ), - ), - ], - ), + onTap: () => onMenu?.call(), + ).paddingOnly(right: 10), + ) + ], ), ); } @@ -89,8 +122,9 @@ class DesktopTabBar extends StatelessWidget { 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; @@ -100,8 +134,9 @@ class _Tab extends StatelessWidget { _Tab( {Key? key, required this.index, - required this.text, + required this.label, required this.icon, + required this.closable, required this.selected, required this.onClose, required this.onSelected, @@ -114,7 +149,6 @@ class _Tab extends StatelessWidget { bool show_divider = index != selected - 1 && index != selected; return Ink( width: _kTabFixedWidth, - color: is_selected ? theme.tabSelectedColor : null, child: InkWell( onHover: (hover) => _hover.value = hover, onTap: () => onSelected(), @@ -126,17 +160,16 @@ class _Tab extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 5), - child: Icon( - icon, - size: _kIconSize, - color: theme.tabIconColor, - ), - ), + Icon( + icon, + size: _kIconSize, + color: is_selected + ? theme.selectedtabIconColor + : theme.unSelectedtabIconColor, + ).paddingSymmetric(horizontal: 5), Expanded( child: Text( - text, + label, style: TextStyle( color: is_selected ? theme.selectedTextColor @@ -144,7 +177,7 @@ class _Tab extends StatelessWidget { ), ), Obx((() => _CloseButton( - tabHovered: _hover.value, + visiable: _hover.value && closable, tabSelected: is_selected, onClose: () => onClose(), theme: theme, @@ -195,14 +228,14 @@ class _AddButton extends StatelessWidget { } class _CloseButton extends StatelessWidget { - final bool tabHovered; + final bool visiable; final bool tabSelected; final Function onClose; late final _Theme theme; _CloseButton({ Key? key, - required this.tabHovered, + required this.visiable, required this.tabSelected, required this.onClose, required this.theme, @@ -210,31 +243,28 @@ class _CloseButton extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 5), - child: SizedBox( - width: _kIconSize, - child: Offstage( - offstage: !tabHovered, - child: InkWell( - customBorder: RoundedRectangleBorder(), - onTap: () => onClose(), - child: Icon( - Icons.close, - size: _kIconSize, - color: tabSelected - ? theme.selectedIconColor - : theme.unSelectedIconColor, - ), - ), - ))); + 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 _Theme { - late Color bgColor; - late Color tabSelectedColor; - late Color tabIconColor; + late Color unSelectedtabIconColor; + late Color selectedtabIconColor; late Color selectedTextColor; late Color unSelectedTextColor; late Color selectedIconColor; @@ -242,9 +272,8 @@ class _Theme { late Color dividerColor; _Theme.light() { - bgColor = Color.fromARGB(255, 253, 253, 253); - tabSelectedColor = MyTheme.grayBg; - tabIconColor = MyTheme.accent50; + 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); @@ -253,9 +282,8 @@ class _Theme { } _Theme.dark() { - bgColor = Color.fromARGB(255, 50, 50, 50); - tabSelectedColor = MyTheme.canvasColor; - tabIconColor = Color.fromARGB(255, 84, 197, 248); + 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); 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(),