commit
c9c0d13dc8
@ -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";
|
||||
|
@ -57,7 +57,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(color: isDarkTheme() ? null : MyTheme.grayBg),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
|
@ -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<ConnectionTabPage>
|
||||
with TickerProviderStateMixin {
|
||||
// refactor List<int> when using multi-tab
|
||||
// this singleton is only for test
|
||||
var connectionIds = RxList<String>.empty(growable: true);
|
||||
var initialIndex = 0;
|
||||
RxList<TabInfo> tabs = RxList<TabInfo>.empty(growable: true);
|
||||
late Rx<TabController> tabController;
|
||||
static final Rx<int> _selected = 0.obs;
|
||||
IconData icon = Icons.desktop_windows_sharp;
|
||||
|
||||
var connectionMap = RxList<Widget>.empty(growable: true);
|
||||
|
||||
_ConnectionTabPageState(Map<String, dynamic> 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<ConnectionTabPage>
|
||||
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<FFI>(tag: tag);
|
||||
});
|
||||
@ -83,20 +70,21 @@ class _ConnectionTabPageState extends State<ConnectionTabPage>
|
||||
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<ConnectionTabPage>
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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<DesktopHomePage>
|
||||
|
||||
@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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
15
flutter/lib/desktop/pages/desktop_setting_page.dart
Normal file
15
flutter/lib/desktop/pages/desktop_setting_page.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class DesktopSettingPage extends StatefulWidget {
|
||||
DesktopSettingPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DesktopSettingPage> createState() => _DesktopSettingPageState();
|
||||
}
|
||||
|
||||
class _DesktopSettingPageState extends State<DesktopSettingPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text("Settings");
|
||||
}
|
||||
}
|
73
flutter/lib/desktop/pages/desktop_tab_page.dart
Normal file
73
flutter/lib/desktop/pages/desktop_tab_page.dart
Normal file
@ -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<DesktopTabPage> createState() => _DesktopTabPageState();
|
||||
}
|
||||
|
||||
class _DesktopTabPageState extends State<DesktopTabPage>
|
||||
with TickerProviderStateMixin {
|
||||
late Rx<TabController> tabController;
|
||||
late RxList<TabInfo> tabs;
|
||||
static final Rx<int> _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));
|
||||
}
|
||||
}
|
@ -73,7 +73,6 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
return false;
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: isDarkTheme() ? MyTheme.dark : MyTheme.grayBg,
|
||||
body: Row(
|
||||
children: [
|
||||
Flexible(flex: 3, child: body(isLocal: true)),
|
||||
|
@ -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<FileManagerTabPage>
|
||||
with TickerProviderStateMixin {
|
||||
// refactor List<int> when using multi-tab
|
||||
// this singleton is only for test
|
||||
var connectionIds = List<String>.empty(growable: true).obs;
|
||||
var initialIndex = 0;
|
||||
RxList<TabInfo> tabs = List<TabInfo>.empty(growable: true).obs;
|
||||
late Rx<TabController> tabController;
|
||||
static final Rx<int> _selected = 0.obs;
|
||||
IconData icon = Icons.file_copy_sharp;
|
||||
|
||||
_FileManagerTabPageState(Map<String, dynamic> 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<FileManagerTabPage>
|
||||
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<FFI>(tag: tag);
|
||||
});
|
||||
@ -80,23 +67,22 @@ class _FileManagerTabPageState extends State<FileManagerTabPage>
|
||||
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<FileManagerTabPage>
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +263,6 @@ class _RemotePageState extends State<RemotePage>
|
||||
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<RemotePage>
|
||||
|
||||
Widget getBodyForDesktop(bool keyboard) {
|
||||
var paints = <Widget>[
|
||||
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<CanvasModel>(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<CanvasModel>(context, listen: false).updateViewStyle();
|
||||
});
|
||||
return ImagePaint(
|
||||
id: widget.id,
|
||||
);
|
||||
}),
|
||||
))
|
||||
];
|
||||
final cursor = bind.getSessionToggleOptionSync(
|
||||
id: widget.id, arg: 'show-remote-cursor');
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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)),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -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<TabController> controller;
|
||||
late final List<String> tabs;
|
||||
late final RxList<TabInfo> tabs;
|
||||
late final Function(String) onTabClose;
|
||||
late final IconData tabIcon;
|
||||
late final Rx<int> 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<TabController> controller,
|
||||
RxList<TabInfo> 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<TabController> controller,
|
||||
RxList<TabInfo> tabs, Rx<int> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<String, dynamic> 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<String, dynamic> 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(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user