Merge pull request #1327 from Heap-Hop/flutter_desktop_cm
Flutter desktop cm
This commit is contained in:
commit
714d474ff2
@ -4,7 +4,9 @@ import 'package:flutter_hbb/main.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
import 'common.dart';
|
||||||
import 'desktop/pages/server_page.dart';
|
import 'desktop/pages/server_page.dart';
|
||||||
|
import 'models/server_model.dart';
|
||||||
|
|
||||||
/// -t lib/cm_main.dart to test cm
|
/// -t lib/cm_main.dart to test cm
|
||||||
void main(List<String> args) async {
|
void main(List<String> args) async {
|
||||||
@ -13,5 +15,16 @@ void main(List<String> args) async {
|
|||||||
await windowManager.setSize(Size(400, 600));
|
await windowManager.setSize(Size(400, 600));
|
||||||
await windowManager.setAlignment(Alignment.topRight);
|
await windowManager.setAlignment(Alignment.topRight);
|
||||||
await initEnv(kAppTypeConnectionManager);
|
await initEnv(kAppTypeConnectionManager);
|
||||||
runApp(GetMaterialApp(theme: getCurrentTheme(), home: DesktopServerPage()));
|
gFFI.serverModel.clients
|
||||||
|
.add(Client(0, false, false, "UserA", "123123123", true, false, false));
|
||||||
|
gFFI.serverModel.clients
|
||||||
|
.add(Client(1, false, false, "UserB", "221123123", true, false, false));
|
||||||
|
gFFI.serverModel.clients
|
||||||
|
.add(Client(2, false, false, "UserC", "331123123", true, false, false));
|
||||||
|
gFFI.serverModel.clients
|
||||||
|
.add(Client(3, false, false, "UserD", "441123123", true, false, false));
|
||||||
|
runApp(GetMaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
theme: getCurrentTheme(),
|
||||||
|
home: DesktopServerPage()));
|
||||||
}
|
}
|
||||||
|
@ -29,14 +29,16 @@ int androidVersion = 0;
|
|||||||
typedef F = String Function(String);
|
typedef F = String Function(String);
|
||||||
typedef FMethod = String Function(String, dynamic);
|
typedef FMethod = String Function(String, dynamic);
|
||||||
|
|
||||||
final iconKeyboard = MemoryImage(Uint8List.fromList(base64Decode(
|
late final iconKeyboard = MemoryImage(Uint8List.fromList(base64Decode(
|
||||||
"iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAgVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9d3yJTAAAAKnRSTlMA0Gd/0y8ILZgbJffDPUwV2nvzt+TMqZxyU7CMb1pYQyzsvKunkXE4AwJnNC24AAAA+0lEQVQ4y83O2U7DMBCF4ZMxk9rZk26kpQs7nPd/QJy4EiLbLf01N5Y/2YP/qxDFQvGB5NPC/ZpVnfJx4b5xyGfF95rkHvNCWH1u+N6J6T0sC7gqRy8uGPfBLEbozPXUjlkQKwGaFPNizwQbwkx0TDvhCii34ExZCSQVBdzIOEOyeclSHgBGXkpeygXSQgStACtWx4Z8rr8COHOvfEP/IbbsQAToFUAAV1M408IIjIGYAPoCSNRP7DQutfQTqxuAiH7UUg1FaJR2AGrrx52sK2ye28LZ0wBAEyR6y8X+NADhm1B4fgiiHXbRrTrxpwEY9RdM9wsepnvFHfUDwYEeiwAJr/gAAAAASUVORK5CYII=")));
|
"iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAgVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9d3yJTAAAAKnRSTlMA0Gd/0y8ILZgbJffDPUwV2nvzt+TMqZxyU7CMb1pYQyzsvKunkXE4AwJnNC24AAAA+0lEQVQ4y83O2U7DMBCF4ZMxk9rZk26kpQs7nPd/QJy4EiLbLf01N5Y/2YP/qxDFQvGB5NPC/ZpVnfJx4b5xyGfF95rkHvNCWH1u+N6J6T0sC7gqRy8uGPfBLEbozPXUjlkQKwGaFPNizwQbwkx0TDvhCii34ExZCSQVBdzIOEOyeclSHgBGXkpeygXSQgStACtWx4Z8rr8COHOvfEP/IbbsQAToFUAAV1M408IIjIGYAPoCSNRP7DQutfQTqxuAiH7UUg1FaJR2AGrrx52sK2ye28LZ0wBAEyR6y8X+NADhm1B4fgiiHXbRrTrxpwEY9RdM9wsepnvFHfUDwYEeiwAJr/gAAAAASUVORK5CYII=")));
|
||||||
final iconClipboard = MemoryImage(Uint8List.fromList(base64Decode(
|
late final iconClipboard = MemoryImage(Uint8List.fromList(base64Decode(
|
||||||
'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAjVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8DizOFAAAALnRSTlMAnIsyZy8YZF3NSAuabRL34cq6trCScyZ4qI9CQDwV+fPl2tnTwzkeB+m/pIFK/Xx0ewAAAQlJREFUOMudktduhDAQRWep69iY3tle0+7/f16Qg7MsJUQ5Dwh8jzRzhemJPIaf3GiW7eFQfOwDPp1ek/iMnKgBi5PrhJAhZAa1lCxE9pw5KWMswOMAQXuQOvqTB7tLFJ36wimKLrufZTzUaoRtdthqRA2vEwS+tR4qguiElRKk1YMrYfUQRkwLmwVBYDMvJKF8R0o3V2MOhNrfo+hXSYYjPn1L/S+n438t8gWh+q1F+cYFBMm1Jh8Ia7y2OWXQxMMRLqr2eTc1crSD84cWfEGwYM4LlaACEee2ZjsQXJxR3qmYb+GpC8ZfNM5oh3yxxbxgQE7lEkb3ZvvH1BiRHn1bu02ICcKGWr4AudUkyYxmvywAAAAASUVORK5CYII=')));
|
'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAjVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8DizOFAAAALnRSTlMAnIsyZy8YZF3NSAuabRL34cq6trCScyZ4qI9CQDwV+fPl2tnTwzkeB+m/pIFK/Xx0ewAAAQlJREFUOMudktduhDAQRWep69iY3tle0+7/f16Qg7MsJUQ5Dwh8jzRzhemJPIaf3GiW7eFQfOwDPp1ek/iMnKgBi5PrhJAhZAa1lCxE9pw5KWMswOMAQXuQOvqTB7tLFJ36wimKLrufZTzUaoRtdthqRA2vEwS+tR4qguiElRKk1YMrYfUQRkwLmwVBYDMvJKF8R0o3V2MOhNrfo+hXSYYjPn1L/S+n438t8gWh+q1F+cYFBMm1Jh8Ia7y2OWXQxMMRLqr2eTc1crSD84cWfEGwYM4LlaACEee2ZjsQXJxR3qmYb+GpC8ZfNM5oh3yxxbxgQE7lEkb3ZvvH1BiRHn1bu02ICcKGWr4AudUkyYxmvywAAAAASUVORK5CYII=')));
|
||||||
final iconAudio = MemoryImage(Uint8List.fromList(base64Decode(
|
late final iconAudio = MemoryImage(Uint8List.fromList(base64Decode(
|
||||||
'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAk1BMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ROyVeAAAAMHRSTlMAgfz08DDqCAThvraZjEcoGA751JxzbGdfTRP25NrIpaGTcEM+HAvMuKinhXhWNx9Yzm/gAAABFUlEQVQ4y82S2XLCMAxFheMsQNghCQFalkL39vz/11V4GpNk0r629+Va1pmxPFfyh1ravOP2Y1ydJmBO0lYP3r+PyQ62s2Y7fgF6VRXOYdToT++ogIuoVhCUtX7YpwJG3F8f6V8rr3WABwwUahlEvr8y3IBniGKdKYBQ5OGQpukQakBpIVcfwptIhJcf8hWGakdndAAhBInIGHbdQGJg6jjbDUgEE5EpmB+AAM4uj6gb+AQT6wdhITLvAHJ4VCtgoAlG1tpNA0gWON/f4ioHdSADc1bfgt+PZFkDlD6ojWF+kVoaHlhvFjPHuVRrefohY1GdcFm1N8JvwEyrJ/X2Th2rIoVgIi3Fo6Xf0z5k8psKu5f/oi+nHjjI92o36AAAAABJRU5ErkJggg==')));
|
'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAk1BMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ROyVeAAAAMHRSTlMAgfz08DDqCAThvraZjEcoGA751JxzbGdfTRP25NrIpaGTcEM+HAvMuKinhXhWNx9Yzm/gAAABFUlEQVQ4y82S2XLCMAxFheMsQNghCQFalkL39vz/11V4GpNk0r629+Va1pmxPFfyh1ravOP2Y1ydJmBO0lYP3r+PyQ62s2Y7fgF6VRXOYdToT++ogIuoVhCUtX7YpwJG3F8f6V8rr3WABwwUahlEvr8y3IBniGKdKYBQ5OGQpukQakBpIVcfwptIhJcf8hWGakdndAAhBInIGHbdQGJg6jjbDUgEE5EpmB+AAM4uj6gb+AQT6wdhITLvAHJ4VCtgoAlG1tpNA0gWON/f4ioHdSADc1bfgt+PZFkDlD6ojWF+kVoaHlhvFjPHuVRrefohY1GdcFm1N8JvwEyrJ/X2Th2rIoVgIi3Fo6Xf0z5k8psKu5f/oi+nHjjI92o36AAAAABJRU5ErkJggg==')));
|
||||||
final iconFile = MemoryImage(Uint8List.fromList(base64Decode(
|
late final iconFile = MemoryImage(Uint8List.fromList(base64Decode(
|
||||||
'iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAH+CAESEN8jyZkcIb5N/ONy3vmHhmiGjUm7UwS+YAAAHZSURBVGje7dnbboMwDIBhBwgQoFAO7Ta//4NOqCAXYZQstatq4r+r5ubrgQSpg8iyC4ZURa+PlIpQYGiwrzyeHtYZjAL8T05O4H8BbbKvFgRa4NoBU8pXeYEkDDgaaLQBcwJrmeErJQB/7wes3QBWGnCIX0+AQycL1PO6BMwPa0nA4ZxbgTvOjUYMGPHRnZkQAY4mxPZBjmy53E7ukSkFKYB/D4XsWZQx64sCeYebOogGsoOBYvv6/UCb8F0IOBZ0TlP6lEYdANY350AJqB9/qPVuOI5evw4A1hgLigAlepnyxW80bcCcwN++A2s82Vcu02ta+ceq9BoL5KGTTRwQPlpqA3gCnwWU2kCDgeWRQPj2jAPCDxgCMjhI6uZnToDpvd/BJeFrJQB/fsAa02gCt3mi1wNuy8GgBNDZlysBNNSrADVSjcJl6vCpUn6jOdx0kz0q6PMhQRa4465SFKhx35cgUCBTwj2/NHwZAb71qR8GEP2H1XcmAtBPTEO67GP6FUUAIKGABbDLQ0EArhN2sAIGesRO+iyy+RMAjckVTlMCKFVAbh/4Af9OPgG61SkDVco3BQGT3GXaDAnTIAcYZDuBTwGsAGDxuBFeAQqIqwoFMlAVLrHr/wId5MPt0nilGgAAAABJRU5ErkJggg==')));
|
'iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAH+CAESEN8jyZkcIb5N/ONy3vmHhmiGjUm7UwS+YAAAHZSURBVGje7dnbboMwDIBhBwgQoFAO7Ta//4NOqCAXYZQstatq4r+r5ubrgQSpg8iyC4ZURa+PlIpQYGiwrzyeHtYZjAL8T05O4H8BbbKvFgRa4NoBU8pXeYEkDDgaaLQBcwJrmeErJQB/7wes3QBWGnCIX0+AQycL1PO6BMwPa0nA4ZxbgTvOjUYMGPHRnZkQAY4mxPZBjmy53E7ukSkFKYB/D4XsWZQx64sCeYebOogGsoOBYvv6/UCb8F0IOBZ0TlP6lEYdANY350AJqB9/qPVuOI5evw4A1hgLigAlepnyxW80bcCcwN++A2s82Vcu02ta+ceq9BoL5KGTTRwQPlpqA3gCnwWU2kCDgeWRQPj2jAPCDxgCMjhI6uZnToDpvd/BJeFrJQB/fsAa02gCt3mi1wNuy8GgBNDZlysBNNSrADVSjcJl6vCpUn6jOdx0kz0q6PMhQRa4465SFKhx35cgUCBTwj2/NHwZAb71qR8GEP2H1XcmAtBPTEO67GP6FUUAIKGABbDLQ0EArhN2sAIGesRO+iyy+RMAjckVTlMCKFVAbh/4Af9OPgG61SkDVco3BQGT3GXaDAnTIAcYZDuBTwGsAGDxuBFeAQqIqwoFMlAVLrHr/wId5MPt0nilGgAAAABJRU5ErkJggg==')));
|
||||||
|
late final iconRestart = MemoryImage(Uint8List.fromList(base64Decode(
|
||||||
|
'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAB7BAAAewQHDaVRTAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAbhJREFUWIXVlrFqFGEUhb+7UYxaWCQKlrKKxaZSQVGDJih2tj6MD2DnMwiWvoAIRnENIpZiYxEro6IooiS7SPwsMgNLkk3mjmYmnmb45/73nMNwz/x/qH3gMu2gH6rAU+Blw+Lngau4jpmGxVF7qp1iPWjaQKnZ2WnXbuP/NqAeUPc3ZkA9XDwvqc+BVWCgPlJ7tRwUKThZce819b46VH+pfXVRXVO/q2cSul3VOgZUl0ejq86r39TXI8mqZKDuDEwCw3IREQvAbWAGmMsQZQ0sAl3gHPB1Q+0e8BuYzRDuy2yOiFVgaUxtRf0ETGc4syk4rc6PqU0Cx9j8Zf6dAeAK8Fi9sUXtFjABvEgxJlNwRP2svlNPjbw/q35U36oTFbnyMSwabxb/gB/qA3VBHagrauV7RW0DRfP1IvMlXqkXkhz1DYyQTKtHa/Z2VVMx3IiI+PI3/bCHjuOpFrSnAMpL6QfgTcMGesDx0kBr2BMzsNyi/vtQu8CJlgwsRbZDnWP90NkKaxHxJMOXMqAeAn5u0ydwMCKGY+qbkB3C2W3EKWoXk5zVoHbUZ+6Mh7tl4G4F8RJ3qvL+AfV3r5Vdpj70AAAAAElFTkSuQmCC')));
|
||||||
|
|
||||||
class IconFont {
|
class IconFont {
|
||||||
static const _family = 'iconfont';
|
static const _family = 'iconfont';
|
||||||
@ -193,7 +195,7 @@ closeConnection({String? id}) {
|
|||||||
if (isAndroid || isIOS) {
|
if (isAndroid || isIOS) {
|
||||||
Navigator.popUntil(globalKey.currentContext!, ModalRoute.withName("/"));
|
Navigator.popUntil(globalKey.currentContext!, ModalRoute.withName("/"));
|
||||||
} else {
|
} else {
|
||||||
closeTab(id);
|
DesktopTabBar.close(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
_ConnectionTabPageState(Map<String, dynamic> params) {
|
_ConnectionTabPageState(Map<String, dynamic> params) {
|
||||||
if (params['id'] != null) {
|
if (params['id'] != null) {
|
||||||
tabs.add(TabInfo(
|
tabs.add(TabInfo(
|
||||||
|
key: params['id'],
|
||||||
label: params['id'],
|
label: params['id'],
|
||||||
selectedIcon: selectedIcon,
|
selectedIcon: selectedIcon,
|
||||||
unselectedIcon: unselectedIcon));
|
unselectedIcon: unselectedIcon));
|
||||||
@ -53,6 +54,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
DesktopTabBar.onAdd(
|
DesktopTabBar.onAdd(
|
||||||
tabs,
|
tabs,
|
||||||
TabInfo(
|
TabInfo(
|
||||||
|
key: id,
|
||||||
label: id,
|
label: id,
|
||||||
selectedIcon: selectedIcon,
|
selectedIcon: selectedIcon,
|
||||||
unselectedIcon: unselectedIcon));
|
unselectedIcon: unselectedIcon));
|
||||||
|
@ -22,6 +22,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
tabs = RxList.from([
|
tabs = RxList.from([
|
||||||
TabInfo(
|
TabInfo(
|
||||||
|
key: kTabLabelHomePage,
|
||||||
label: kTabLabelHomePage,
|
label: kTabLabelHomePage,
|
||||||
selectedIcon: Icons.home_sharp,
|
selectedIcon: Icons.home_sharp,
|
||||||
unselectedIcon: Icons.home_outlined,
|
unselectedIcon: Icons.home_outlined,
|
||||||
@ -70,6 +71,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
|||||||
DesktopTabBar.onAdd(
|
DesktopTabBar.onAdd(
|
||||||
tabs,
|
tabs,
|
||||||
TabInfo(
|
TabInfo(
|
||||||
|
key: kTabLabelSettingPage,
|
||||||
label: kTabLabelSettingPage,
|
label: kTabLabelSettingPage,
|
||||||
selectedIcon: Icons.build_sharp,
|
selectedIcon: Icons.build_sharp,
|
||||||
unselectedIcon: Icons.build_outlined));
|
unselectedIcon: Icons.build_outlined));
|
||||||
|
@ -108,6 +108,31 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
}));
|
}));
|
||||||
|
return Overlay(initialEntries: [
|
||||||
|
OverlayEntry(builder: (context) {
|
||||||
|
_ffi.dialogManager.setOverlayState(Overlay.of(context));
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: _ffi.fileModel,
|
||||||
|
child: Consumer<FileModel>(builder: (_context, _model, _child) {
|
||||||
|
return WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
if (model.selectMode) {
|
||||||
|
model.toggleSelectMode();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
body: Row(
|
||||||
|
children: [
|
||||||
|
Flexible(flex: 3, child: body(isLocal: true)),
|
||||||
|
Flexible(flex: 3, child: body(isLocal: false)),
|
||||||
|
Flexible(flex: 2, child: statusList())
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget menu({bool isLocal = false}) {
|
Widget menu({bool isLocal = false}) {
|
||||||
|
@ -29,6 +29,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
|||||||
_FileManagerTabPageState(Map<String, dynamic> params) {
|
_FileManagerTabPageState(Map<String, dynamic> params) {
|
||||||
if (params['id'] != null) {
|
if (params['id'] != null) {
|
||||||
tabs.add(TabInfo(
|
tabs.add(TabInfo(
|
||||||
|
key: params['id'],
|
||||||
label: params['id'],
|
label: params['id'],
|
||||||
selectedIcon: selectedIcon,
|
selectedIcon: selectedIcon,
|
||||||
unselectedIcon: unselectedIcon));
|
unselectedIcon: unselectedIcon));
|
||||||
@ -49,6 +50,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
|||||||
DesktopTabBar.onAdd(
|
DesktopTabBar.onAdd(
|
||||||
tabs,
|
tabs,
|
||||||
TabInfo(
|
TabInfo(
|
||||||
|
key: id,
|
||||||
label: id,
|
label: id,
|
||||||
selectedIcon: selectedIcon,
|
selectedIcon: selectedIcon,
|
||||||
unselectedIcon: unselectedIcon));
|
unselectedIcon: unselectedIcon));
|
||||||
|
@ -1,110 +1,18 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
||||||
|
import 'package:flutter_hbb/mobile/pages/chat_page.dart';
|
||||||
|
import 'package:flutter_hbb/models/chat_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
// import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../mobile/pages/home_page.dart';
|
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../../models/server_model.dart';
|
import '../../models/server_model.dart';
|
||||||
|
|
||||||
class DesktopServerPage extends StatefulWidget implements PageShape {
|
class DesktopServerPage extends StatefulWidget {
|
||||||
@override
|
|
||||||
final title = translate("Share Screen");
|
|
||||||
|
|
||||||
@override
|
|
||||||
final icon = Icon(Icons.mobile_screen_share);
|
|
||||||
|
|
||||||
@override
|
|
||||||
final appBarActions = [
|
|
||||||
PopupMenuButton<String>(
|
|
||||||
icon: Icon(Icons.more_vert),
|
|
||||||
itemBuilder: (context) {
|
|
||||||
return [
|
|
||||||
PopupMenuItem(
|
|
||||||
child: Text(translate("Change ID")),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
value: "changeID",
|
|
||||||
enabled: false,
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
child: Text(translate("Set permanent password")),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
value: "setPermanentPassword",
|
|
||||||
enabled:
|
|
||||||
gFFI.serverModel.verificationMethod != kUseTemporaryPassword,
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
child: Text(translate("Set temporary password length")),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
value: "setTemporaryPasswordLength",
|
|
||||||
enabled:
|
|
||||||
gFFI.serverModel.verificationMethod != kUsePermanentPassword,
|
|
||||||
),
|
|
||||||
const PopupMenuDivider(),
|
|
||||||
PopupMenuItem(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 0.0),
|
|
||||||
value: kUseTemporaryPassword,
|
|
||||||
child: Container(
|
|
||||||
child: ListTile(
|
|
||||||
title: Text(translate("Use temporary password")),
|
|
||||||
trailing: Icon(
|
|
||||||
Icons.check,
|
|
||||||
color: gFFI.serverModel.verificationMethod ==
|
|
||||||
kUseTemporaryPassword
|
|
||||||
? null
|
|
||||||
: Color(0xFFFFFFFF),
|
|
||||||
))),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 0.0),
|
|
||||||
value: kUsePermanentPassword,
|
|
||||||
child: ListTile(
|
|
||||||
title: Text(translate("Use permanent password")),
|
|
||||||
trailing: Icon(
|
|
||||||
Icons.check,
|
|
||||||
color: gFFI.serverModel.verificationMethod ==
|
|
||||||
kUsePermanentPassword
|
|
||||||
? null
|
|
||||||
: Color(0xFFFFFFFF),
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 0.0),
|
|
||||||
value: kUseBothPasswords,
|
|
||||||
child: ListTile(
|
|
||||||
title: Text(translate("Use both passwords")),
|
|
||||||
trailing: Icon(
|
|
||||||
Icons.check,
|
|
||||||
color: gFFI.serverModel.verificationMethod !=
|
|
||||||
kUseTemporaryPassword &&
|
|
||||||
gFFI.serverModel.verificationMethod !=
|
|
||||||
kUsePermanentPassword
|
|
||||||
? null
|
|
||||||
: Color(0xFFFFFFFF),
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
onSelected: (value) {
|
|
||||||
if (value == "changeID") {
|
|
||||||
// TODO
|
|
||||||
} else if (value == "setPermanentPassword") {
|
|
||||||
// setPermanentPasswordDialog();
|
|
||||||
} else if (value == "setTemporaryPasswordLength") {
|
|
||||||
// setTemporaryPasswordLengthDialog();
|
|
||||||
} else if (value == kUsePermanentPassword ||
|
|
||||||
value == kUseTemporaryPassword ||
|
|
||||||
value == kUseBothPasswords) {
|
|
||||||
bind.mainSetOption(key: "verification-method", value: value);
|
|
||||||
gFFI.serverModel.updatePasswordModel();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _DesktopServerPageState();
|
State<StatefulWidget> createState() => _DesktopServerPageState();
|
||||||
}
|
}
|
||||||
@ -112,36 +20,58 @@ class DesktopServerPage extends StatefulWidget implements PageShape {
|
|||||||
class _DesktopServerPageState extends State<DesktopServerPage>
|
class _DesktopServerPageState extends State<DesktopServerPage>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
@override
|
@override
|
||||||
|
void initState() {
|
||||||
|
gFFI.ffiModel.updateEventListener("");
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
return ChangeNotifierProvider.value(
|
return MultiProvider(
|
||||||
value: gFFI.serverModel,
|
providers: [
|
||||||
|
ChangeNotifierProvider.value(value: gFFI.serverModel),
|
||||||
|
ChangeNotifierProvider.value(value: gFFI.chatModel),
|
||||||
|
],
|
||||||
child: Consumer<ServerModel>(
|
child: Consumer<ServerModel>(
|
||||||
builder: (context, serverModel, child) => Material(
|
builder: (context, serverModel, child) => Material(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: ConnectionManager()),
|
Expanded(child: ConnectionManager()),
|
||||||
SizedBox.fromSize(size: Size(0, 15.0)),
|
SizedBox.fromSize(size: Size(0, 15.0)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConnectionManager extends StatelessWidget {
|
class ConnectionManager extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => ConnectionManagerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionManagerState extends State<ConnectionManager> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
gFFI.serverModel.updateClientState();
|
||||||
|
// test
|
||||||
|
// gFFI.serverModel.clients.forEach((client) {
|
||||||
|
// DesktopTabBar.onAdd(
|
||||||
|
// gFFI.serverModel.tabs,
|
||||||
|
// TabInfo(
|
||||||
|
// key: client.id.toString(), label: client.name, closable: false));
|
||||||
|
// });
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final serverModel = Provider.of<ServerModel>(context);
|
final serverModel = Provider.of<ServerModel>(context);
|
||||||
// test case:
|
|
||||||
// serverModel.clients.clear();
|
|
||||||
// serverModel.clients[0] = Client(
|
|
||||||
// false, false, "Readmi-M21sdfsdf", "123123123", true, false, false);
|
|
||||||
return serverModel.clients.isEmpty
|
return serverModel.clients.isEmpty
|
||||||
? Column(
|
? Column(
|
||||||
children: [
|
children: [
|
||||||
@ -153,27 +83,37 @@ class ConnectionManager extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: DefaultTabController(
|
: Column(
|
||||||
length: serverModel.clients.length,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
SizedBox(
|
||||||
children: [
|
height: kTextTabBarHeight,
|
||||||
SizedBox(
|
child: Obx(() => DesktopTabBar(
|
||||||
height: kTextTabBarHeight,
|
dark: isDarkTheme(),
|
||||||
child: buildTitleBar(TabBar(
|
mainTab: true,
|
||||||
isScrollable: true,
|
tabs: serverModel.tabs,
|
||||||
tabs: serverModel.clients.entries
|
showTitle: false,
|
||||||
.map((entry) => buildTab(entry))
|
showMaximize: false,
|
||||||
.toList(growable: false))),
|
showMinimize: false,
|
||||||
),
|
onSelected: (index) => gFFI.chatModel
|
||||||
Expanded(
|
.changeCurrentID(serverModel.clients[index].id),
|
||||||
child: TabBarView(
|
)),
|
||||||
children: serverModel.clients.entries
|
),
|
||||||
.map((entry) => buildConnectionCard(entry))
|
Expanded(
|
||||||
.toList(growable: false)),
|
child: Row(children: [
|
||||||
)
|
Expanded(
|
||||||
],
|
child: PageView(
|
||||||
),
|
controller: DesktopTabBar.controller.value,
|
||||||
|
children: serverModel.clients
|
||||||
|
.map((client) => buildConnectionCard(client))
|
||||||
|
.toList(growable: false))),
|
||||||
|
Consumer<ChatModel>(
|
||||||
|
builder: (_, model, child) => model.isShowChatPage
|
||||||
|
? Expanded(child: Scaffold(body: ChatPage()))
|
||||||
|
: Offstage())
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,10 +136,11 @@ class ConnectionManager extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildConnectionCard(MapEntry<int, Client> entry) {
|
Widget buildConnectionCard(Client client) {
|
||||||
final client = entry.value;
|
|
||||||
return Column(
|
return Column(
|
||||||
key: ValueKey(entry.key),
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
key: ValueKey(client.id),
|
||||||
children: [
|
children: [
|
||||||
_CmHeader(client: client),
|
_CmHeader(client: client),
|
||||||
client.isFileTransfer ? Offstage() : _PrivilegeBoard(client: client),
|
client.isFileTransfer ? Offstage() : _PrivilegeBoard(client: client),
|
||||||
@ -212,14 +153,14 @@ class ConnectionManager extends StatelessWidget {
|
|||||||
).paddingSymmetric(vertical: 8.0, horizontal: 8.0);
|
).paddingSymmetric(vertical: 8.0, horizontal: 8.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildTab(MapEntry<int, Client> entry) {
|
Widget buildTab(Client client) {
|
||||||
return Tab(
|
return Tab(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 80,
|
width: 80,
|
||||||
child: Text(
|
child: Text(
|
||||||
"${entry.value.name}",
|
"${client.name}",
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@ -302,14 +243,14 @@ class _CmHeaderState extends State<_CmHeader>
|
|||||||
children: [
|
children: [
|
||||||
// icon
|
// icon
|
||||||
Container(
|
Container(
|
||||||
width: 100,
|
width: 90,
|
||||||
height: 100,
|
height: 90,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: BoxDecoration(color: str2color(client.name)),
|
decoration: BoxDecoration(color: str2color(client.name)),
|
||||||
child: Text(
|
child: Text(
|
||||||
"${client.name[0]}",
|
"${client.name[0]}",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold, color: Colors.white, fontSize: 75),
|
fontWeight: FontWeight.bold, color: Colors.white, fontSize: 65),
|
||||||
),
|
),
|
||||||
).marginOnly(left: 4.0, right: 8.0),
|
).marginOnly(left: 4.0, right: 8.0),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -317,7 +258,8 @@ class _CmHeaderState extends State<_CmHeader>
|
|||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
FittedBox(
|
||||||
|
child: Text(
|
||||||
"${client.name}",
|
"${client.name}",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: MyTheme.cmIdColor,
|
color: MyTheme.cmIdColor,
|
||||||
@ -326,26 +268,29 @@ class _CmHeaderState extends State<_CmHeader>
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
),
|
)),
|
||||||
Text("(${client.peerId})",
|
FittedBox(
|
||||||
style: TextStyle(color: MyTheme.cmIdColor, fontSize: 14)),
|
child: Text("(${client.peerId})",
|
||||||
|
style:
|
||||||
|
TextStyle(color: MyTheme.cmIdColor, fontSize: 14))),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 16.0,
|
height: 16.0,
|
||||||
),
|
),
|
||||||
Row(
|
FittedBox(
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text("${translate("Connected")}").marginOnly(right: 8.0),
|
Text("${translate("Connected")}").marginOnly(right: 8.0),
|
||||||
Obx(() => Text(
|
Obx(() => Text(
|
||||||
"${formatDurationToTime(Duration(seconds: _time.value))}"))
|
"${formatDurationToTime(Duration(seconds: _time.value))}"))
|
||||||
],
|
],
|
||||||
)
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Offstage(
|
Offstage(
|
||||||
offstage: client.isFileTransfer,
|
offstage: client.isFileTransfer,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: handleSendMsg,
|
onPressed: () => gFFI.chatModel.toggleCMChatPage(client.id),
|
||||||
icon: Icon(Icons.message_outlined),
|
icon: Icon(Icons.message_outlined),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -353,24 +298,28 @@ class _CmHeaderState extends State<_CmHeader>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleSendMsg() {}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PrivilegeBoard extends StatelessWidget {
|
class _PrivilegeBoard extends StatefulWidget {
|
||||||
final Client client;
|
final Client client;
|
||||||
|
|
||||||
const _PrivilegeBoard({Key? key, required this.client}) : super(key: key);
|
const _PrivilegeBoard({Key? key, required this.client}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _PrivilegeBoardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PrivilegeBoardState extends State<_PrivilegeBoard> {
|
||||||
|
late final client = widget.client;
|
||||||
Widget buildPermissionIcon(bool enabled, ImageProvider icon,
|
Widget buildPermissionIcon(bool enabled, ImageProvider icon,
|
||||||
Function(bool)? onTap, String? tooltip) {
|
Function(bool)? onTap, String? tooltip) {
|
||||||
return Tooltip(
|
return Tooltip(
|
||||||
message: tooltip ?? "",
|
message: tooltip ?? "",
|
||||||
child: Ink(
|
child: Ink(
|
||||||
decoration:
|
decoration:
|
||||||
BoxDecoration(color: enabled ? MyTheme.accent80 : Colors.grey),
|
BoxDecoration(color: enabled ? MyTheme.accent80 : Colors.grey),
|
||||||
padding: EdgeInsets.all(4.0),
|
padding: EdgeInsets.all(4.0),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => onTap?.call(!enabled),
|
onTap: () => onTap?.call(!enabled),
|
||||||
@ -399,18 +348,46 @@ class _PrivilegeBoard extends StatelessWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 8.0,
|
height: 8.0,
|
||||||
),
|
),
|
||||||
Row(
|
FittedBox(
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
buildPermissionIcon(
|
buildPermissionIcon(client.keyboard, iconKeyboard, (enabled) {
|
||||||
client.keyboard, iconKeyboard, (enable) => null, null),
|
bind.cmSwitchPermission(
|
||||||
buildPermissionIcon(
|
connId: client.id, name: "keyboard", enabled: enabled);
|
||||||
client.clipboard, iconClipboard, (enable) => null, null),
|
setState(() {
|
||||||
buildPermissionIcon(
|
client.keyboard = enabled;
|
||||||
client.audio, iconAudio, (enable) => null, null),
|
});
|
||||||
// TODO: file transfer
|
}, null),
|
||||||
buildPermissionIcon(false, iconFile, (enable) => null, null),
|
buildPermissionIcon(client.clipboard, iconClipboard, (enabled) {
|
||||||
|
bind.cmSwitchPermission(
|
||||||
|
connId: client.id, name: "clipboard", enabled: enabled);
|
||||||
|
setState(() {
|
||||||
|
client.clipboard = enabled;
|
||||||
|
});
|
||||||
|
}, null),
|
||||||
|
buildPermissionIcon(client.audio, iconAudio, (enabled) {
|
||||||
|
bind.cmSwitchPermission(
|
||||||
|
connId: client.id, name: "audio", enabled: enabled);
|
||||||
|
setState(() {
|
||||||
|
client.audio = enabled;
|
||||||
|
});
|
||||||
|
}, null),
|
||||||
|
buildPermissionIcon(client.file, iconFile, (enabled) {
|
||||||
|
bind.cmSwitchPermission(
|
||||||
|
connId: client.id, name: "file", enabled: enabled);
|
||||||
|
setState(() {
|
||||||
|
client.file = enabled;
|
||||||
|
});
|
||||||
|
}, null),
|
||||||
|
buildPermissionIcon(client.restart, iconRestart, (enabled) {
|
||||||
|
bind.cmSwitchPermission(
|
||||||
|
connId: client.id, name: "restart", enabled: enabled);
|
||||||
|
setState(() {
|
||||||
|
client.restart = enabled;
|
||||||
|
});
|
||||||
|
}, null),
|
||||||
],
|
],
|
||||||
),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -530,9 +507,9 @@ class PaddingCard extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
titleIcon != null
|
titleIcon != null
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: EdgeInsets.only(right: 10),
|
padding: EdgeInsets.only(right: 10),
|
||||||
child: Icon(titleIcon,
|
child: Icon(titleIcon,
|
||||||
color: MyTheme.accent80, size: 30))
|
color: MyTheme.accent80, size: 30))
|
||||||
: SizedBox.shrink(),
|
: SizedBox.shrink(),
|
||||||
Text(
|
Text(
|
||||||
title!,
|
title!,
|
||||||
@ -579,12 +556,12 @@ Widget clientInfo(Client client) {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(client.name,
|
Text(client.name,
|
||||||
style: TextStyle(color: MyTheme.idColor, fontSize: 18)),
|
style: TextStyle(color: MyTheme.idColor, fontSize: 18)),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Text(client.peerId,
|
Text(client.peerId,
|
||||||
style: TextStyle(color: MyTheme.idColor, fontSize: 10))
|
style: TextStyle(color: MyTheme.idColor, fontSize: 10))
|
||||||
]))
|
]))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]));
|
]));
|
||||||
|
@ -14,36 +14,19 @@ const double _kTabBarHeight = kDesktopRemoteTabBarHeight;
|
|||||||
const double _kIconSize = 18;
|
const double _kIconSize = 18;
|
||||||
const double _kDividerIndent = 10;
|
const double _kDividerIndent = 10;
|
||||||
const double _kActionIconSize = 12;
|
const double _kActionIconSize = 12;
|
||||||
final _tabBarKey = GlobalKey();
|
|
||||||
|
|
||||||
void closeTab(String? id) {
|
|
||||||
final tabBar = _tabBarKey.currentWidget as _ListView?;
|
|
||||||
if (tabBar == null) return;
|
|
||||||
final tabs = tabBar.tabs;
|
|
||||||
if (id == null) {
|
|
||||||
if (tabBar.selected.value < tabs.length) {
|
|
||||||
tabs[tabBar.selected.value].onClose();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (final tab in tabs) {
|
|
||||||
if (tab.label == id) {
|
|
||||||
tab.onClose();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TabInfo {
|
class TabInfo {
|
||||||
|
late final String key;
|
||||||
late final String label;
|
late final String label;
|
||||||
late final IconData selectedIcon;
|
late final IconData? selectedIcon;
|
||||||
late final IconData unselectedIcon;
|
late final IconData? unselectedIcon;
|
||||||
late final bool closable;
|
late final bool closable;
|
||||||
|
|
||||||
TabInfo(
|
TabInfo(
|
||||||
{required this.label,
|
{required this.key,
|
||||||
required this.selectedIcon,
|
required this.label,
|
||||||
required this.unselectedIcon,
|
this.selectedIcon,
|
||||||
|
this.unselectedIcon,
|
||||||
this.closable = true});
|
this.closable = true});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,20 +36,33 @@ class DesktopTabBar extends StatelessWidget {
|
|||||||
late final bool dark;
|
late final bool dark;
|
||||||
late final _Theme _theme;
|
late final _Theme _theme;
|
||||||
late final bool mainTab;
|
late final bool mainTab;
|
||||||
late final Function()? onAddSetting;
|
late final bool showLogo;
|
||||||
|
late final bool showTitle;
|
||||||
|
late final bool showMinimize;
|
||||||
|
late final bool showMaximize;
|
||||||
|
late final bool showClose;
|
||||||
|
late final void Function()? onAddSetting;
|
||||||
|
late final void Function(int)? onSelected;
|
||||||
final ScrollPosController scrollController =
|
final ScrollPosController scrollController =
|
||||||
ScrollPosController(itemCount: 0);
|
ScrollPosController(itemCount: 0);
|
||||||
static final Rx<PageController> controller = PageController().obs;
|
static final Rx<PageController> controller = PageController().obs;
|
||||||
static final Rx<int> selected = 0.obs;
|
static final Rx<int> selected = 0.obs;
|
||||||
|
static final _tabBarListViewKey = GlobalKey();
|
||||||
|
|
||||||
DesktopTabBar({
|
DesktopTabBar(
|
||||||
Key? key,
|
{Key? key,
|
||||||
required this.tabs,
|
required this.tabs,
|
||||||
this.onTabClose,
|
this.onTabClose,
|
||||||
required this.dark,
|
required this.dark,
|
||||||
required this.mainTab,
|
required this.mainTab,
|
||||||
this.onAddSetting,
|
this.onAddSetting,
|
||||||
}) : _theme = dark ? _Theme.dark() : _Theme.light(),
|
this.onSelected,
|
||||||
|
this.showLogo = true,
|
||||||
|
this.showTitle = true,
|
||||||
|
this.showMinimize = true,
|
||||||
|
this.showMaximize = true,
|
||||||
|
this.showClose = true})
|
||||||
|
: _theme = dark ? _Theme.dark() : _Theme.light(),
|
||||||
super(key: key) {
|
super(key: key) {
|
||||||
scrollController.itemCount = tabs.length;
|
scrollController.itemCount = tabs.length;
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
@ -88,22 +84,23 @@ class DesktopTabBar extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Offstage(
|
Row(children: [
|
||||||
offstage: !mainTab,
|
Offstage(
|
||||||
child: Row(children: [
|
offstage: !showLogo,
|
||||||
Image.asset(
|
child: Image.asset(
|
||||||
'assets/logo.ico',
|
'assets/logo.ico',
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
)),
|
||||||
Text(
|
Offstage(
|
||||||
"RustDesk",
|
offstage: !showTitle,
|
||||||
style: TextStyle(fontSize: 13),
|
child: Text(
|
||||||
).marginOnly(left: 2),
|
"RustDesk",
|
||||||
]).marginOnly(
|
style: TextStyle(fontSize: 13),
|
||||||
left: 5,
|
).marginOnly(left: 2))
|
||||||
right: 10,
|
]).marginOnly(
|
||||||
),
|
left: 5,
|
||||||
|
right: 10,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@ -116,13 +113,14 @@ class DesktopTabBar extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: _ListView(
|
child: _ListView(
|
||||||
key: _tabBarKey,
|
key: _tabBarListViewKey,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
tabInfos: tabs,
|
tabInfos: tabs,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
onTabClose: onTabClose,
|
onTabClose: onTabClose,
|
||||||
theme: _theme)),
|
theme: _theme,
|
||||||
|
onSelected: onSelected)),
|
||||||
),
|
),
|
||||||
Offstage(
|
Offstage(
|
||||||
offstage: mainTab,
|
offstage: mainTab,
|
||||||
@ -146,6 +144,9 @@ class DesktopTabBar extends StatelessWidget {
|
|||||||
WindowActionPanel(
|
WindowActionPanel(
|
||||||
mainTab: mainTab,
|
mainTab: mainTab,
|
||||||
theme: _theme,
|
theme: _theme,
|
||||||
|
showMinimize: showMinimize,
|
||||||
|
showMaximize: showMaximize,
|
||||||
|
showClose: showClose,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -160,7 +161,7 @@ class DesktopTabBar extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static onAdd(RxList<TabInfo> tabs, TabInfo tab) {
|
static onAdd(RxList<TabInfo> tabs, TabInfo tab) {
|
||||||
int index = tabs.indexWhere((e) => e.label == tab.label);
|
int index = tabs.indexWhere((e) => e.key == tab.key);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
selected.value = index;
|
selected.value = index;
|
||||||
} else {
|
} else {
|
||||||
@ -168,86 +169,148 @@ class DesktopTabBar extends StatelessWidget {
|
|||||||
selected.value = tabs.length - 1;
|
selected.value = tabs.length - 1;
|
||||||
assert(selected.value >= 0);
|
assert(selected.value >= 0);
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
controller.value.jumpToPage(selected.value);
|
||||||
|
} catch (e) {
|
||||||
|
// call before binding controller will throw
|
||||||
|
debugPrint("Failed to jumpToPage: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static remove(RxList<TabInfo> tabs, int index) {
|
||||||
|
if (index < 0) return;
|
||||||
|
if (index == tabs.length - 1) {
|
||||||
|
selected.value = max(0, selected.value - 1);
|
||||||
|
} else if (index < tabs.length - 1 && index < selected.value) {
|
||||||
|
selected.value = max(0, selected.value - 1);
|
||||||
|
}
|
||||||
|
tabs.removeAt(index);
|
||||||
controller.value.jumpToPage(selected.value);
|
controller.value.jumpToPage(selected.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void jumpTo(RxList<TabInfo> tabs, int index) {
|
||||||
|
if (index < 0 || index >= tabs.length) return;
|
||||||
|
selected.value = index;
|
||||||
|
controller.value.jumpToPage(selected.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void close(String? key) {
|
||||||
|
final tabBar = _tabBarListViewKey.currentWidget as _ListView?;
|
||||||
|
if (tabBar == null) return;
|
||||||
|
final tabs = tabBar.tabs;
|
||||||
|
if (key == null) {
|
||||||
|
if (tabBar.selected.value < tabs.length) {
|
||||||
|
tabs[tabBar.selected.value].onClose();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (final tab in tabs) {
|
||||||
|
if (tab.key == key) {
|
||||||
|
tab.onClose();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WindowActionPanel extends StatelessWidget {
|
class WindowActionPanel extends StatelessWidget {
|
||||||
final bool mainTab;
|
final bool mainTab;
|
||||||
final _Theme theme;
|
final _Theme theme;
|
||||||
|
|
||||||
|
final bool showMinimize;
|
||||||
|
final bool showMaximize;
|
||||||
|
final bool showClose;
|
||||||
|
|
||||||
const WindowActionPanel(
|
const WindowActionPanel(
|
||||||
{Key? key, required this.mainTab, required this.theme})
|
{Key? key,
|
||||||
|
required this.mainTab,
|
||||||
|
required this.theme,
|
||||||
|
this.showMinimize = true,
|
||||||
|
this.showMaximize = true,
|
||||||
|
this.showClose = true})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
_ActionIcon(
|
Offstage(
|
||||||
message: 'Minimize',
|
offstage: !showMinimize,
|
||||||
icon: IconFont.min,
|
child: _ActionIcon(
|
||||||
theme: theme,
|
message: 'Minimize',
|
||||||
onTap: () {
|
icon: IconFont.min,
|
||||||
if (mainTab) {
|
|
||||||
windowManager.minimize();
|
|
||||||
} else {
|
|
||||||
WindowController.fromWindowId(windowId!).minimize();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
is_close: false,
|
|
||||||
),
|
|
||||||
FutureBuilder(builder: (context, snapshot) {
|
|
||||||
RxBool is_maximized = false.obs;
|
|
||||||
if (mainTab) {
|
|
||||||
windowManager.isMaximized().then((maximized) {
|
|
||||||
is_maximized.value = maximized;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
final wc = WindowController.fromWindowId(windowId!);
|
|
||||||
wc.isMaximized().then((maximized) {
|
|
||||||
is_maximized.value = maximized;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Obx(
|
|
||||||
() => _ActionIcon(
|
|
||||||
message: is_maximized.value ? "Restore" : "Maximize",
|
|
||||||
icon: is_maximized.value ? IconFont.restore : IconFont.max,
|
|
||||||
theme: theme,
|
theme: theme,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (mainTab) {
|
if (mainTab) {
|
||||||
if (is_maximized.value) {
|
windowManager.minimize();
|
||||||
windowManager.unmaximize();
|
|
||||||
} else {
|
|
||||||
windowManager.maximize();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
final wc = WindowController.fromWindowId(windowId!);
|
WindowController.fromWindowId(windowId!).minimize();
|
||||||
if (is_maximized.value) {
|
|
||||||
wc.unmaximize();
|
|
||||||
} else {
|
|
||||||
wc.maximize();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is_maximized.value = !is_maximized.value;
|
|
||||||
},
|
},
|
||||||
is_close: false,
|
is_close: false,
|
||||||
),
|
)),
|
||||||
);
|
Offstage(
|
||||||
}),
|
offstage: !showMaximize,
|
||||||
_ActionIcon(
|
child: FutureBuilder(builder: (context, snapshot) {
|
||||||
message: 'Close',
|
RxBool is_maximized = false.obs;
|
||||||
icon: IconFont.close,
|
if (mainTab) {
|
||||||
theme: theme,
|
windowManager.isMaximized().then((maximized) {
|
||||||
onTap: () {
|
is_maximized.value = maximized;
|
||||||
if (mainTab) {
|
});
|
||||||
windowManager.close();
|
} else {
|
||||||
} else {
|
final wc = WindowController.fromWindowId(windowId!);
|
||||||
WindowController.fromWindowId(windowId!).close();
|
wc.isMaximized().then((maximized) {
|
||||||
}
|
is_maximized.value = maximized;
|
||||||
},
|
});
|
||||||
is_close: true,
|
}
|
||||||
),
|
return Obx(
|
||||||
|
() => _ActionIcon(
|
||||||
|
message: is_maximized.value ? "Restore" : "Maximize",
|
||||||
|
icon: is_maximized.value ? IconFont.restore : IconFont.max,
|
||||||
|
theme: theme,
|
||||||
|
onTap: () {
|
||||||
|
if (mainTab) {
|
||||||
|
if (is_maximized.value) {
|
||||||
|
windowManager.unmaximize();
|
||||||
|
} else {
|
||||||
|
WindowController.fromWindowId(windowId!).minimize();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final wc = WindowController.fromWindowId(windowId!);
|
||||||
|
if (is_maximized.value) {
|
||||||
|
wc.unmaximize();
|
||||||
|
} else {
|
||||||
|
final wc = WindowController.fromWindowId(windowId!);
|
||||||
|
wc.isMaximized().then((maximized) {
|
||||||
|
if (maximized) {
|
||||||
|
wc.unmaximize();
|
||||||
|
} else {
|
||||||
|
wc.maximize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is_maximized.value = !is_maximized.value;
|
||||||
|
},
|
||||||
|
is_close: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
})),
|
||||||
|
Offstage(
|
||||||
|
offstage: !showClose,
|
||||||
|
child: _ActionIcon(
|
||||||
|
message: 'Close',
|
||||||
|
icon: IconFont.close,
|
||||||
|
theme: theme,
|
||||||
|
onTap: () {
|
||||||
|
if (mainTab) {
|
||||||
|
windowManager.close();
|
||||||
|
} else {
|
||||||
|
WindowController.fromWindowId(windowId!).close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
is_close: true,
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -259,19 +322,21 @@ class _ListView extends StatelessWidget {
|
|||||||
final ScrollPosController scrollController;
|
final ScrollPosController scrollController;
|
||||||
final RxList<TabInfo> tabInfos;
|
final RxList<TabInfo> tabInfos;
|
||||||
final Rx<int> selected;
|
final Rx<int> selected;
|
||||||
final Function(String label)? onTabClose;
|
final Function(String key)? onTabClose;
|
||||||
final _Theme _theme;
|
final _Theme _theme;
|
||||||
late List<_Tab> tabs;
|
late List<_Tab> tabs;
|
||||||
|
late final void Function(int)? onSelected;
|
||||||
|
|
||||||
_ListView({
|
_ListView(
|
||||||
Key? key,
|
{Key? key,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
required this.tabInfos,
|
required this.tabInfos,
|
||||||
required this.selected,
|
required this.selected,
|
||||||
required this.onTabClose,
|
required this.onTabClose,
|
||||||
required _Theme theme,
|
required _Theme theme,
|
||||||
}) : _theme = theme,
|
this.onSelected})
|
||||||
|
: _theme = theme,
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -279,17 +344,16 @@ class _ListView extends StatelessWidget {
|
|||||||
return Obx(() {
|
return Obx(() {
|
||||||
tabs = tabInfos.asMap().entries.map((e) {
|
tabs = tabInfos.asMap().entries.map((e) {
|
||||||
int index = e.key;
|
int index = e.key;
|
||||||
String label = e.value.label;
|
|
||||||
return _Tab(
|
return _Tab(
|
||||||
index: index,
|
index: index,
|
||||||
label: label,
|
label: e.value.label,
|
||||||
selectedIcon: e.value.selectedIcon,
|
selectedIcon: e.value.selectedIcon,
|
||||||
unselectedIcon: e.value.unselectedIcon,
|
unselectedIcon: e.value.unselectedIcon,
|
||||||
closable: e.value.closable,
|
closable: e.value.closable,
|
||||||
selected: selected.value,
|
selected: selected.value,
|
||||||
onClose: () {
|
onClose: () {
|
||||||
tabInfos.removeWhere((tab) => tab.label == label);
|
tabInfos.removeWhere((tab) => tab.key == e.value.key);
|
||||||
onTabClose?.call(label);
|
onTabClose?.call(e.value.key);
|
||||||
if (index <= selected.value) {
|
if (index <= selected.value) {
|
||||||
selected.value = max(0, selected.value - 1);
|
selected.value = max(0, selected.value - 1);
|
||||||
}
|
}
|
||||||
@ -305,6 +369,7 @@ class _ListView extends StatelessWidget {
|
|||||||
selected.value = index;
|
selected.value = index;
|
||||||
scrollController.scrollToItem(index, center: true, animate: true);
|
scrollController.scrollToItem(index, center: true, animate: true);
|
||||||
controller.value.jumpToPage(index);
|
controller.value.jumpToPage(index);
|
||||||
|
onSelected?.call(selected.value);
|
||||||
},
|
},
|
||||||
theme: _theme,
|
theme: _theme,
|
||||||
);
|
);
|
||||||
@ -322,8 +387,8 @@ class _ListView extends StatelessWidget {
|
|||||||
class _Tab extends StatelessWidget {
|
class _Tab extends StatelessWidget {
|
||||||
late final int index;
|
late final int index;
|
||||||
late final String label;
|
late final String label;
|
||||||
late final IconData selectedIcon;
|
late final IconData? selectedIcon;
|
||||||
late final IconData unselectedIcon;
|
late final IconData? unselectedIcon;
|
||||||
late final bool closable;
|
late final bool closable;
|
||||||
late final int selected;
|
late final int selected;
|
||||||
late final Function() onClose;
|
late final Function() onClose;
|
||||||
@ -335,8 +400,8 @@ class _Tab extends StatelessWidget {
|
|||||||
{Key? key,
|
{Key? key,
|
||||||
required this.index,
|
required this.index,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.selectedIcon,
|
this.selectedIcon,
|
||||||
required this.unselectedIcon,
|
this.unselectedIcon,
|
||||||
required this.closable,
|
required this.closable,
|
||||||
required this.selected,
|
required this.selected,
|
||||||
required this.onClose,
|
required this.onClose,
|
||||||
@ -346,6 +411,7 @@ class _Tab extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
bool show_icon = selectedIcon != null && unselectedIcon != null;
|
||||||
bool is_selected = index == selected;
|
bool is_selected = index == selected;
|
||||||
bool show_divider = index != selected - 1 && index != selected;
|
bool show_divider = index != selected - 1 && index != selected;
|
||||||
return Ink(
|
return Ink(
|
||||||
@ -362,13 +428,15 @@ class _Tab extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Offstage(
|
||||||
is_selected ? selectedIcon : unselectedIcon,
|
offstage: !show_icon,
|
||||||
size: _kIconSize,
|
child: Icon(
|
||||||
color: is_selected
|
is_selected ? selectedIcon : unselectedIcon,
|
||||||
? theme.selectedtabIconColor
|
size: _kIconSize,
|
||||||
: theme.unSelectedtabIconColor,
|
color: is_selected
|
||||||
).paddingOnly(right: 5),
|
? theme.selectedtabIconColor
|
||||||
|
: theme.unSelectedtabIconColor,
|
||||||
|
).paddingOnly(right: 5)),
|
||||||
Text(
|
Text(
|
||||||
translate(label),
|
translate(label),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
@ -359,12 +359,12 @@ class ConnectionManager extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final serverModel = Provider.of<ServerModel>(context);
|
final serverModel = Provider.of<ServerModel>(context);
|
||||||
return Column(
|
return Column(
|
||||||
children: serverModel.clients.entries
|
children: serverModel.clients
|
||||||
.map((entry) => PaddingCard(
|
.map((client) => PaddingCard(
|
||||||
title: translate(entry.value.isFileTransfer
|
title: translate(client.isFileTransfer
|
||||||
? "File Connection"
|
? "File Connection"
|
||||||
: "Screen Connection"),
|
: "Screen Connection"),
|
||||||
titleIcon: entry.value.isFileTransfer
|
titleIcon: client.isFileTransfer
|
||||||
? Icons.folder_outlined
|
? Icons.folder_outlined
|
||||||
: Icons.mobile_screen_share,
|
: Icons.mobile_screen_share,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -373,16 +373,14 @@ class ConnectionManager extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: clientInfo(entry.value)),
|
Expanded(child: clientInfo(client)),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: -1,
|
flex: -1,
|
||||||
child: entry.value.isFileTransfer ||
|
child: client.isFileTransfer || !client.authorized
|
||||||
!entry.value.authorized
|
|
||||||
? SizedBox.shrink()
|
? SizedBox.shrink()
|
||||||
: IconButton(
|
: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
gFFI.chatModel
|
gFFI.chatModel.changeCurrentID(client.id);
|
||||||
.changeCurrentID(entry.value.id);
|
|
||||||
final bar =
|
final bar =
|
||||||
navigationBarKey.currentWidget;
|
navigationBarKey.currentWidget;
|
||||||
if (bar != null) {
|
if (bar != null) {
|
||||||
@ -396,37 +394,35 @@ class ConnectionManager extends StatelessWidget {
|
|||||||
)))
|
)))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
entry.value.authorized
|
client.authorized
|
||||||
? SizedBox.shrink()
|
? SizedBox.shrink()
|
||||||
: Text(
|
: Text(
|
||||||
translate("android_new_connection_tip"),
|
translate("android_new_connection_tip"),
|
||||||
style: TextStyle(color: Colors.black54),
|
style: TextStyle(color: Colors.black54),
|
||||||
),
|
),
|
||||||
entry.value.authorized
|
client.authorized
|
||||||
? ElevatedButton.icon(
|
? ElevatedButton.icon(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
MaterialStateProperty.all(Colors.red)),
|
MaterialStateProperty.all(Colors.red)),
|
||||||
icon: Icon(Icons.close),
|
icon: Icon(Icons.close),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
bind.cmCloseConnection(connId: entry.key);
|
bind.cmCloseConnection(connId: client.id);
|
||||||
gFFI.invokeMethod(
|
gFFI.invokeMethod(
|
||||||
"cancel_notification", entry.key);
|
"cancel_notification", client.id);
|
||||||
},
|
},
|
||||||
label: Text(translate("Close")))
|
label: Text(translate("Close")))
|
||||||
: Row(children: [
|
: Row(children: [
|
||||||
TextButton(
|
TextButton(
|
||||||
child: Text(translate("Dismiss")),
|
child: Text(translate("Dismiss")),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
serverModel.sendLoginResponse(
|
serverModel.sendLoginResponse(client, false);
|
||||||
entry.value, false);
|
|
||||||
}),
|
}),
|
||||||
SizedBox(width: 20),
|
SizedBox(width: 20),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: Text(translate("Accept")),
|
child: Text(translate("Accept")),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
serverModel.sendLoginResponse(
|
serverModel.sendLoginResponse(client, true);
|
||||||
entry.value, true);
|
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
|
@ -2,6 +2,7 @@ import 'package:dash_chat_2/dash_chat_2.dart';
|
|||||||
import 'package:draggable_float_widget/draggable_float_widget.dart';
|
import 'package:draggable_float_widget/draggable_float_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
import '../../mobile/widgets/overlay.dart';
|
import '../../mobile/widgets/overlay.dart';
|
||||||
import '../common.dart';
|
import '../common.dart';
|
||||||
@ -41,11 +42,14 @@ class ChatModel with ChangeNotifier {
|
|||||||
..[clientModeID] = MessageBody(me, []);
|
..[clientModeID] = MessageBody(me, []);
|
||||||
|
|
||||||
var _currentID = clientModeID;
|
var _currentID = clientModeID;
|
||||||
|
late bool _isShowChatPage = false;
|
||||||
|
|
||||||
Map<int, MessageBody> get messages => _messages;
|
Map<int, MessageBody> get messages => _messages;
|
||||||
|
|
||||||
int get currentID => _currentID;
|
int get currentID => _currentID;
|
||||||
|
|
||||||
|
bool get isShowChatPage => _isShowChatPage;
|
||||||
|
|
||||||
WeakReference<FFI> _ffi;
|
WeakReference<FFI> _ffi;
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
@ -149,12 +153,29 @@ class ChatModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleCMChatPage(int id) async {
|
||||||
|
if (gFFI.chatModel.currentID != id) {
|
||||||
|
gFFI.chatModel.changeCurrentID(id);
|
||||||
|
}
|
||||||
|
if (_isShowChatPage) {
|
||||||
|
_isShowChatPage = !_isShowChatPage;
|
||||||
|
notifyListeners();
|
||||||
|
await windowManager.setSize(Size(400, 600));
|
||||||
|
} else {
|
||||||
|
await windowManager.setSize(Size(800, 600));
|
||||||
|
await Future.delayed(Duration(milliseconds: 100));
|
||||||
|
_isShowChatPage = !_isShowChatPage;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
changeCurrentID(int id) {
|
changeCurrentID(int id) {
|
||||||
if (_messages.containsKey(id)) {
|
if (_messages.containsKey(id)) {
|
||||||
_currentID = id;
|
_currentID = id;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} else {
|
} else {
|
||||||
final client = _ffi.target?.serverModel.clients[id];
|
final client = _ffi.target?.serverModel.clients
|
||||||
|
.firstWhere((client) => client.id == id);
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
return debugPrint(
|
return debugPrint(
|
||||||
"Failed to changeCurrentID,remote user doesn't exist");
|
"Failed to changeCurrentID,remote user doesn't exist");
|
||||||
@ -171,10 +192,15 @@ class ChatModel with ChangeNotifier {
|
|||||||
|
|
||||||
receive(int id, String text) async {
|
receive(int id, String text) async {
|
||||||
if (text.isEmpty) return;
|
if (text.isEmpty) return;
|
||||||
// first message show overlay icon
|
// mobile: first message show overlay icon
|
||||||
if (chatIconOverlayEntry == null) {
|
if (chatIconOverlayEntry == null) {
|
||||||
showChatIconOverlay();
|
showChatIconOverlay();
|
||||||
}
|
}
|
||||||
|
// desktop: show chat page
|
||||||
|
if (!_isShowChatPage) {
|
||||||
|
toggleCMChatPage(id);
|
||||||
|
}
|
||||||
|
|
||||||
late final chatUser;
|
late final chatUser;
|
||||||
if (id == clientModeID) {
|
if (id == clientModeID) {
|
||||||
chatUser = ChatUser(
|
chatUser = ChatUser(
|
||||||
|
@ -4,9 +4,11 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
|
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
|
|
||||||
import '../common.dart';
|
import '../common.dart';
|
||||||
|
import '../desktop/widgets/tabbar_widget.dart';
|
||||||
import '../mobile/pages/server_page.dart';
|
import '../mobile/pages/server_page.dart';
|
||||||
import 'model.dart';
|
import 'model.dart';
|
||||||
|
|
||||||
@ -30,7 +32,9 @@ class ServerModel with ChangeNotifier {
|
|||||||
late final TextEditingController _serverId;
|
late final TextEditingController _serverId;
|
||||||
final _serverPasswd = TextEditingController(text: "");
|
final _serverPasswd = TextEditingController(text: "");
|
||||||
|
|
||||||
Map<int, Client> _clients = {};
|
RxList<TabInfo> tabs = RxList<TabInfo>.empty(growable: true);
|
||||||
|
|
||||||
|
List<Client> _clients = [];
|
||||||
|
|
||||||
bool get isStart => _isStart;
|
bool get isStart => _isStart;
|
||||||
|
|
||||||
@ -76,7 +80,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
|
|
||||||
TextEditingController get serverPasswd => _serverPasswd;
|
TextEditingController get serverPasswd => _serverPasswd;
|
||||||
|
|
||||||
Map<int, Client> get clients => _clients;
|
List<Client> get clients => _clients;
|
||||||
|
|
||||||
final controller = ScrollController();
|
final controller = ScrollController();
|
||||||
|
|
||||||
@ -338,6 +342,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// force
|
||||||
updateClientState([String? json]) async {
|
updateClientState([String? json]) async {
|
||||||
var res = await bind.mainGetClientsState();
|
var res = await bind.mainGetClientsState();
|
||||||
try {
|
try {
|
||||||
@ -347,9 +352,16 @@ class ServerModel with ChangeNotifier {
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
_clients.clear();
|
_clients.clear();
|
||||||
|
tabs.clear();
|
||||||
for (var clientJson in clientsJson) {
|
for (var clientJson in clientsJson) {
|
||||||
final client = Client.fromJson(clientJson);
|
final client = Client.fromJson(clientJson);
|
||||||
_clients[client.id] = client;
|
_clients.add(client);
|
||||||
|
DesktopTabBar.onAdd(
|
||||||
|
tabs,
|
||||||
|
TabInfo(
|
||||||
|
key: client.id.toString(),
|
||||||
|
label: client.name,
|
||||||
|
closable: false));
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -360,13 +372,17 @@ class ServerModel with ChangeNotifier {
|
|||||||
void loginRequest(Map<String, dynamic> evt) {
|
void loginRequest(Map<String, dynamic> evt) {
|
||||||
try {
|
try {
|
||||||
final client = Client.fromJson(jsonDecode(evt["client"]));
|
final client = Client.fromJson(jsonDecode(evt["client"]));
|
||||||
if (_clients.containsKey(client.id)) {
|
if (_clients.any((c) => c.id == client.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_clients[client.id] = client;
|
_clients.add(client);
|
||||||
|
DesktopTabBar.onAdd(
|
||||||
|
tabs,
|
||||||
|
TabInfo(
|
||||||
|
key: client.id.toString(), label: client.name, closable: false));
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
showLoginDialog(client);
|
if (isAndroid) showLoginDialog(client);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Failed to call loginRequest,error:$e");
|
debugPrint("Failed to call loginRequest,error:$e");
|
||||||
}
|
}
|
||||||
@ -419,6 +435,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scrollToBottom() {
|
scrollToBottom() {
|
||||||
|
if (isDesktop) return;
|
||||||
Future.delayed(Duration(milliseconds: 200), () {
|
Future.delayed(Duration(milliseconds: 200), () {
|
||||||
controller.animateTo(controller.position.maxScrollExtent,
|
controller.animateTo(controller.position.maxScrollExtent,
|
||||||
duration: Duration(milliseconds: 200),
|
duration: Duration(milliseconds: 200),
|
||||||
@ -433,12 +450,14 @@ class ServerModel with ChangeNotifier {
|
|||||||
parent.target?.invokeMethod("start_capture");
|
parent.target?.invokeMethod("start_capture");
|
||||||
}
|
}
|
||||||
parent.target?.invokeMethod("cancel_notification", client.id);
|
parent.target?.invokeMethod("cancel_notification", client.id);
|
||||||
_clients[client.id]?.authorized = true;
|
client.authorized = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} else {
|
} else {
|
||||||
bind.cmLoginRes(connId: client.id, res: res);
|
bind.cmLoginRes(connId: client.id, res: res);
|
||||||
parent.target?.invokeMethod("cancel_notification", client.id);
|
parent.target?.invokeMethod("cancel_notification", client.id);
|
||||||
_clients.remove(client.id);
|
final index = _clients.indexOf(client);
|
||||||
|
DesktopTabBar.remove(tabs, index);
|
||||||
|
_clients.remove(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,7 +465,11 @@ class ServerModel with ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
final client = Client.fromJson(jsonDecode(evt['client']));
|
final client = Client.fromJson(jsonDecode(evt['client']));
|
||||||
parent.target?.dialogManager.dismissByTag(getLoginDialogTag(client.id));
|
parent.target?.dialogManager.dismissByTag(getLoginDialogTag(client.id));
|
||||||
_clients[client.id] = client;
|
_clients.add(client);
|
||||||
|
DesktopTabBar.onAdd(
|
||||||
|
tabs,
|
||||||
|
TabInfo(
|
||||||
|
key: client.id.toString(), label: client.name, closable: false));
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
@ -455,8 +478,10 @@ class ServerModel with ChangeNotifier {
|
|||||||
void onClientRemove(Map<String, dynamic> evt) {
|
void onClientRemove(Map<String, dynamic> evt) {
|
||||||
try {
|
try {
|
||||||
final id = int.parse(evt['id'] as String);
|
final id = int.parse(evt['id'] as String);
|
||||||
if (_clients.containsKey(id)) {
|
if (_clients.any((c) => c.id == id)) {
|
||||||
_clients.remove(id);
|
final index = _clients.indexWhere((client) => client.id == id);
|
||||||
|
_clients.removeAt(index);
|
||||||
|
DesktopTabBar.remove(tabs, index);
|
||||||
parent.target?.dialogManager.dismissByTag(getLoginDialogTag(id));
|
parent.target?.dialogManager.dismissByTag(getLoginDialogTag(id));
|
||||||
parent.target?.invokeMethod("cancel_notification", id);
|
parent.target?.invokeMethod("cancel_notification", id);
|
||||||
}
|
}
|
||||||
@ -467,10 +492,11 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
closeAll() {
|
closeAll() {
|
||||||
_clients.forEach((id, client) {
|
_clients.forEach((client) {
|
||||||
bind.cmCloseConnection(connId: id);
|
bind.cmCloseConnection(connId: client.id);
|
||||||
});
|
});
|
||||||
_clients.clear();
|
_clients.clear();
|
||||||
|
tabs.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,8 +509,10 @@ class Client {
|
|||||||
bool keyboard = false;
|
bool keyboard = false;
|
||||||
bool clipboard = false;
|
bool clipboard = false;
|
||||||
bool audio = false;
|
bool audio = false;
|
||||||
|
bool file = false;
|
||||||
|
bool restart = false;
|
||||||
|
|
||||||
Client(this.authorized, this.isFileTransfer, this.name, this.peerId,
|
Client(this.id, this.authorized, this.isFileTransfer, this.name, this.peerId,
|
||||||
this.keyboard, this.clipboard, this.audio);
|
this.keyboard, this.clipboard, this.audio);
|
||||||
|
|
||||||
Client.fromJson(Map<String, dynamic> json) {
|
Client.fromJson(Map<String, dynamic> json) {
|
||||||
@ -496,6 +524,8 @@ class Client {
|
|||||||
keyboard = json['keyboard'];
|
keyboard = json['keyboard'];
|
||||||
clipboard = json['clipboard'];
|
clipboard = json['clipboard'];
|
||||||
audio = json['audio'];
|
audio = json['audio'];
|
||||||
|
file = json['file'];
|
||||||
|
restart = json['restart'];
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
|
@ -41,6 +41,7 @@ use crate::{client::*, flutter_ffi::EventToUI, make_fd_flutter};
|
|||||||
pub(super) const APP_TYPE_MAIN: &str = "main";
|
pub(super) const APP_TYPE_MAIN: &str = "main";
|
||||||
pub(super) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
|
pub(super) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
|
||||||
pub(super) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
|
pub(super) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
|
||||||
|
pub(super) const APP_TYPE_DESKTOP_CONNECTION_MANAGER: &str = "connection manager";
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
// static ref SESSION: Arc<RwLock<Option<Session>>> = Default::default();
|
// static ref SESSION: Arc<RwLock<Option<Session>>> = Default::default();
|
||||||
@ -1675,6 +1676,8 @@ pub mod connection_manager {
|
|||||||
keyboard: bool,
|
keyboard: bool,
|
||||||
clipboard: bool,
|
clipboard: bool,
|
||||||
audio: bool,
|
audio: bool,
|
||||||
|
file: bool,
|
||||||
|
restart: bool,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
tx: UnboundedSender<Data>,
|
tx: UnboundedSender<Data>,
|
||||||
}
|
}
|
||||||
@ -1885,8 +1888,8 @@ pub mod connection_manager {
|
|||||||
keyboard: bool,
|
keyboard: bool,
|
||||||
clipboard: bool,
|
clipboard: bool,
|
||||||
audio: bool,
|
audio: bool,
|
||||||
_file: bool,
|
file: bool,
|
||||||
_restart: bool,
|
restart: bool,
|
||||||
tx: mpsc::UnboundedSender<Data>,
|
tx: mpsc::UnboundedSender<Data>,
|
||||||
) {
|
) {
|
||||||
let mut client = Client {
|
let mut client = Client {
|
||||||
@ -1898,6 +1901,8 @@ pub mod connection_manager {
|
|||||||
keyboard,
|
keyboard,
|
||||||
clipboard,
|
clipboard,
|
||||||
audio,
|
audio,
|
||||||
|
file,
|
||||||
|
restart,
|
||||||
tx,
|
tx,
|
||||||
};
|
};
|
||||||
if authorized {
|
if authorized {
|
||||||
@ -1935,7 +1940,7 @@ pub mod connection_manager {
|
|||||||
if let Some(s) = GLOBAL_EVENT_STREAM
|
if let Some(s) = GLOBAL_EVENT_STREAM
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.get(super::APP_TYPE_MAIN)
|
.get(super::APP_TYPE_DESKTOP_CONNECTION_MANAGER)
|
||||||
{
|
{
|
||||||
s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned()));
|
s.add(serde_json::ser::to_string(&h).unwrap_or("".to_owned()));
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user