diff --git a/flutter/lib/cm_main.dart b/flutter/lib/cm_main.dart index 99db02232..1f71b9e93 100644 --- a/flutter/lib/cm_main.dart +++ b/flutter/lib/cm_main.dart @@ -4,7 +4,9 @@ import 'package:flutter_hbb/main.dart'; import 'package:get/get.dart'; import 'package:window_manager/window_manager.dart'; +import 'common.dart'; import 'desktop/pages/server_page.dart'; +import 'models/server_model.dart'; /// -t lib/cm_main.dart to test cm void main(List args) async { @@ -13,5 +15,16 @@ void main(List args) async { await windowManager.setSize(Size(400, 600)); await windowManager.setAlignment(Alignment.topRight); 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())); } diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 8570e5b7e..8c4216020 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -29,14 +29,16 @@ int androidVersion = 0; typedef F = String Function(String); 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="))); -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='))); -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=='))); -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=='))); +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 { static const _family = 'iconfont'; @@ -193,7 +195,7 @@ closeConnection({String? id}) { if (isAndroid || isIOS) { Navigator.popUntil(globalKey.currentContext!, ModalRoute.withName("/")); } else { - closeTab(id); + DesktopTabBar.close(id); } } diff --git a/flutter/lib/desktop/pages/connection_tab_page.dart b/flutter/lib/desktop/pages/connection_tab_page.dart index be5ec82af..ece7df5ca 100644 --- a/flutter/lib/desktop/pages/connection_tab_page.dart +++ b/flutter/lib/desktop/pages/connection_tab_page.dart @@ -33,6 +33,7 @@ class _ConnectionTabPageState extends State { _ConnectionTabPageState(Map params) { if (params['id'] != null) { tabs.add(TabInfo( + key: params['id'], label: params['id'], selectedIcon: selectedIcon, unselectedIcon: unselectedIcon)); @@ -53,6 +54,7 @@ class _ConnectionTabPageState extends State { DesktopTabBar.onAdd( tabs, TabInfo( + key: id, label: id, selectedIcon: selectedIcon, unselectedIcon: unselectedIcon)); diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart index 5c108f39f..141b7ca0e 100644 --- a/flutter/lib/desktop/pages/desktop_tab_page.dart +++ b/flutter/lib/desktop/pages/desktop_tab_page.dart @@ -22,6 +22,7 @@ class _DesktopTabPageState extends State { super.initState(); tabs = RxList.from([ TabInfo( + key: kTabLabelHomePage, label: kTabLabelHomePage, selectedIcon: Icons.home_sharp, unselectedIcon: Icons.home_outlined, @@ -70,6 +71,7 @@ class _DesktopTabPageState extends State { DesktopTabBar.onAdd( tabs, TabInfo( + key: kTabLabelSettingPage, label: kTabLabelSettingPage, selectedIcon: Icons.build_sharp, unselectedIcon: Icons.build_outlined)); diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index 2868d2d3b..e07fadf28 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -108,6 +108,31 @@ class _FileManagerPageState extends State ), )); })); + return Overlay(initialEntries: [ + OverlayEntry(builder: (context) { + _ffi.dialogManager.setOverlayState(Overlay.of(context)); + return ChangeNotifierProvider.value( + value: _ffi.fileModel, + child: Consumer(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}) { diff --git a/flutter/lib/desktop/pages/file_manager_tab_page.dart b/flutter/lib/desktop/pages/file_manager_tab_page.dart index 12b5b20ff..7e94724bb 100644 --- a/flutter/lib/desktop/pages/file_manager_tab_page.dart +++ b/flutter/lib/desktop/pages/file_manager_tab_page.dart @@ -29,6 +29,7 @@ class _FileManagerTabPageState extends State { _FileManagerTabPageState(Map params) { if (params['id'] != null) { tabs.add(TabInfo( + key: params['id'], label: params['id'], selectedIcon: selectedIcon, unselectedIcon: unselectedIcon)); @@ -49,6 +50,7 @@ class _FileManagerTabPageState extends State { DesktopTabBar.onAdd( tabs, TabInfo( + key: id, label: id, selectedIcon: selectedIcon, unselectedIcon: unselectedIcon)); diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart index 32130ad2e..e8f9ec26b 100644 --- a/flutter/lib/desktop/pages/server_page.dart +++ b/flutter/lib/desktop/pages/server_page.dart @@ -1,110 +1,18 @@ import 'dart:async'; 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:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:provider/provider.dart'; import 'package:window_manager/window_manager.dart'; import '../../common.dart'; -import '../../mobile/pages/home_page.dart'; import '../../models/platform_model.dart'; import '../../models/server_model.dart'; -class DesktopServerPage extends StatefulWidget implements PageShape { - @override - final title = translate("Share Screen"); - - @override - final icon = Icon(Icons.mobile_screen_share); - - @override - final appBarActions = [ - PopupMenuButton( - 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(); - } - }) - ]; - +class DesktopServerPage extends StatefulWidget { @override State createState() => _DesktopServerPageState(); } @@ -112,36 +20,58 @@ class DesktopServerPage extends StatefulWidget implements PageShape { class _DesktopServerPageState extends State with AutomaticKeepAliveClientMixin { @override + void initState() { + gFFI.ffiModel.updateEventListener(""); + super.initState(); + } + Widget build(BuildContext context) { super.build(context); - return ChangeNotifierProvider.value( - value: gFFI.serverModel, + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: gFFI.serverModel), + ChangeNotifierProvider.value(value: gFFI.chatModel), + ], child: Consumer( builder: (context, serverModel, child) => Material( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded(child: ConnectionManager()), - SizedBox.fromSize(size: Size(0, 15.0)), - ], - ), - ), - ))); + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded(child: ConnectionManager()), + SizedBox.fromSize(size: Size(0, 15.0)), + ], + ), + ), + ))); } @override bool get wantKeepAlive => true; } -class ConnectionManager extends StatelessWidget { +class ConnectionManager extends StatefulWidget { + @override + State createState() => ConnectionManagerState(); +} + +class ConnectionManagerState extends State { + @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 Widget build(BuildContext context) { final serverModel = Provider.of(context); - // test case: - // serverModel.clients.clear(); - // serverModel.clients[0] = Client( - // false, false, "Readmi-M21sdfsdf", "123123123", true, false, false); return serverModel.clients.isEmpty ? Column( children: [ @@ -153,27 +83,37 @@ class ConnectionManager extends StatelessWidget { ), ], ) - : DefaultTabController( - length: serverModel.clients.length, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: kTextTabBarHeight, - child: buildTitleBar(TabBar( - isScrollable: true, - tabs: serverModel.clients.entries - .map((entry) => buildTab(entry)) - .toList(growable: false))), - ), - Expanded( - child: TabBarView( - children: serverModel.clients.entries - .map((entry) => buildConnectionCard(entry)) - .toList(growable: false)), - ) - ], - ), + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: kTextTabBarHeight, + child: Obx(() => DesktopTabBar( + dark: isDarkTheme(), + mainTab: true, + tabs: serverModel.tabs, + showTitle: false, + showMaximize: false, + showMinimize: false, + onSelected: (index) => gFFI.chatModel + .changeCurrentID(serverModel.clients[index].id), + )), + ), + Expanded( + child: Row(children: [ + Expanded( + child: PageView( + controller: DesktopTabBar.controller.value, + children: serverModel.clients + .map((client) => buildConnectionCard(client)) + .toList(growable: false))), + Consumer( + builder: (_, model, child) => model.isShowChatPage + ? Expanded(child: Scaffold(body: ChatPage())) + : Offstage()) + ]), + ) + ], ); } @@ -196,10 +136,11 @@ class ConnectionManager extends StatelessWidget { ); } - Widget buildConnectionCard(MapEntry entry) { - final client = entry.value; + Widget buildConnectionCard(Client client) { return Column( - key: ValueKey(entry.key), + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + key: ValueKey(client.id), children: [ _CmHeader(client: client), client.isFileTransfer ? Offstage() : _PrivilegeBoard(client: client), @@ -212,14 +153,14 @@ class ConnectionManager extends StatelessWidget { ).paddingSymmetric(vertical: 8.0, horizontal: 8.0); } - Widget buildTab(MapEntry entry) { + Widget buildTab(Client client) { return Tab( child: Row( children: [ SizedBox( width: 80, child: Text( - "${entry.value.name}", + "${client.name}", maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, @@ -302,14 +243,14 @@ class _CmHeaderState extends State<_CmHeader> children: [ // icon Container( - width: 100, - height: 100, + width: 90, + height: 90, alignment: Alignment.center, decoration: BoxDecoration(color: str2color(client.name)), child: Text( "${client.name[0]}", 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), Expanded( @@ -317,7 +258,8 @@ class _CmHeaderState extends State<_CmHeader> mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + FittedBox( + child: Text( "${client.name}", style: TextStyle( color: MyTheme.cmIdColor, @@ -326,26 +268,29 @@ class _CmHeaderState extends State<_CmHeader> overflow: TextOverflow.ellipsis, ), maxLines: 1, - ), - Text("(${client.peerId})", - style: TextStyle(color: MyTheme.cmIdColor, fontSize: 14)), + )), + FittedBox( + child: Text("(${client.peerId})", + style: + TextStyle(color: MyTheme.cmIdColor, fontSize: 14))), SizedBox( height: 16.0, ), - Row( + FittedBox( + child: Row( children: [ Text("${translate("Connected")}").marginOnly(right: 8.0), Obx(() => Text( "${formatDurationToTime(Duration(seconds: _time.value))}")) ], - ) + )) ], ), ), Offstage( offstage: client.isFileTransfer, child: IconButton( - onPressed: handleSendMsg, + onPressed: () => gFFI.chatModel.toggleCMChatPage(client.id), icon: Icon(Icons.message_outlined), ), ) @@ -353,24 +298,28 @@ class _CmHeaderState extends State<_CmHeader> ); } - void handleSendMsg() {} - @override bool get wantKeepAlive => true; } -class _PrivilegeBoard extends StatelessWidget { +class _PrivilegeBoard extends StatefulWidget { final Client client; const _PrivilegeBoard({Key? key, required this.client}) : super(key: key); + @override + State createState() => _PrivilegeBoardState(); +} + +class _PrivilegeBoardState extends State<_PrivilegeBoard> { + late final client = widget.client; Widget buildPermissionIcon(bool enabled, ImageProvider icon, Function(bool)? onTap, String? tooltip) { return Tooltip( message: tooltip ?? "", child: Ink( decoration: - BoxDecoration(color: enabled ? MyTheme.accent80 : Colors.grey), + BoxDecoration(color: enabled ? MyTheme.accent80 : Colors.grey), padding: EdgeInsets.all(4.0), child: InkWell( onTap: () => onTap?.call(!enabled), @@ -399,18 +348,46 @@ class _PrivilegeBoard extends StatelessWidget { SizedBox( height: 8.0, ), - Row( + FittedBox( + child: Row( children: [ - buildPermissionIcon( - client.keyboard, iconKeyboard, (enable) => null, null), - buildPermissionIcon( - client.clipboard, iconClipboard, (enable) => null, null), - buildPermissionIcon( - client.audio, iconAudio, (enable) => null, null), - // TODO: file transfer - buildPermissionIcon(false, iconFile, (enable) => null, null), + buildPermissionIcon(client.keyboard, iconKeyboard, (enabled) { + bind.cmSwitchPermission( + connId: client.id, name: "keyboard", enabled: enabled); + setState(() { + client.keyboard = enabled; + }); + }, 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: [ titleIcon != null ? Padding( - padding: EdgeInsets.only(right: 10), - child: Icon(titleIcon, - color: MyTheme.accent80, size: 30)) + padding: EdgeInsets.only(right: 10), + child: Icon(titleIcon, + color: MyTheme.accent80, size: 30)) : SizedBox.shrink(), Text( title!, @@ -579,12 +556,12 @@ Widget clientInfo(Client client) { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(client.name, - style: TextStyle(color: MyTheme.idColor, fontSize: 18)), - SizedBox(width: 8), - Text(client.peerId, - style: TextStyle(color: MyTheme.idColor, fontSize: 10)) - ])) + Text(client.name, + style: TextStyle(color: MyTheme.idColor, fontSize: 18)), + SizedBox(width: 8), + Text(client.peerId, + style: TextStyle(color: MyTheme.idColor, fontSize: 10)) + ])) ], ), ])); diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index bf39f4dc6..74019c815 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -14,36 +14,19 @@ const double _kTabBarHeight = kDesktopRemoteTabBarHeight; const double _kIconSize = 18; const double _kDividerIndent = 10; 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 { + late final String key; late final String label; - late final IconData selectedIcon; - late final IconData unselectedIcon; + late final IconData? selectedIcon; + late final IconData? unselectedIcon; late final bool closable; TabInfo( - {required this.label, - required this.selectedIcon, - required this.unselectedIcon, + {required this.key, + required this.label, + this.selectedIcon, + this.unselectedIcon, this.closable = true}); } @@ -53,20 +36,33 @@ class DesktopTabBar extends StatelessWidget { late final bool dark; late final _Theme _theme; 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 = ScrollPosController(itemCount: 0); static final Rx controller = PageController().obs; static final Rx selected = 0.obs; + static final _tabBarListViewKey = GlobalKey(); - DesktopTabBar({ - Key? key, - required this.tabs, - this.onTabClose, - required this.dark, - required this.mainTab, - this.onAddSetting, - }) : _theme = dark ? _Theme.dark() : _Theme.light(), + DesktopTabBar( + {Key? key, + required this.tabs, + this.onTabClose, + required this.dark, + required this.mainTab, + this.onAddSetting, + 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) { scrollController.itemCount = tabs.length; WidgetsBinding.instance.addPostFrameCallback((_) { @@ -88,22 +84,23 @@ class DesktopTabBar extends StatelessWidget { Expanded( child: Row( children: [ - Offstage( - offstage: !mainTab, - child: Row(children: [ - Image.asset( - 'assets/logo.ico', - width: 20, - height: 20, - ), - Text( - "RustDesk", - style: TextStyle(fontSize: 13), - ).marginOnly(left: 2), - ]).marginOnly( - left: 5, - right: 10, - ), + Row(children: [ + Offstage( + offstage: !showLogo, + child: Image.asset( + 'assets/logo.ico', + width: 20, + height: 20, + )), + Offstage( + offstage: !showTitle, + child: Text( + "RustDesk", + style: TextStyle(fontSize: 13), + ).marginOnly(left: 2)) + ]).marginOnly( + left: 5, + right: 10, ), Expanded( child: GestureDetector( @@ -116,13 +113,14 @@ class DesktopTabBar extends StatelessWidget { } }, child: _ListView( - key: _tabBarKey, + key: _tabBarListViewKey, controller: controller, scrollController: scrollController, tabInfos: tabs, selected: selected, onTabClose: onTabClose, - theme: _theme)), + theme: _theme, + onSelected: onSelected)), ), Offstage( offstage: mainTab, @@ -146,6 +144,9 @@ class DesktopTabBar extends StatelessWidget { WindowActionPanel( mainTab: mainTab, theme: _theme, + showMinimize: showMinimize, + showMaximize: showMaximize, + showClose: showClose, ) ], ), @@ -160,7 +161,7 @@ class DesktopTabBar extends StatelessWidget { } static onAdd(RxList tabs, TabInfo tab) { - int index = tabs.indexWhere((e) => e.label == tab.label); + int index = tabs.indexWhere((e) => e.key == tab.key); if (index >= 0) { selected.value = index; } else { @@ -168,86 +169,148 @@ class DesktopTabBar extends StatelessWidget { selected.value = tabs.length - 1; 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 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); } + + static void jumpTo(RxList 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 { final bool mainTab; final _Theme theme; + final bool showMinimize; + final bool showMaximize; + final bool showClose; + 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); @override Widget build(BuildContext context) { return Row( children: [ - _ActionIcon( - message: 'Minimize', - icon: IconFont.min, - theme: theme, - onTap: () { - 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, + Offstage( + offstage: !showMinimize, + child: _ActionIcon( + message: 'Minimize', + icon: IconFont.min, theme: theme, onTap: () { if (mainTab) { - if (is_maximized.value) { - windowManager.unmaximize(); - } else { - windowManager.maximize(); - } + windowManager.minimize(); } else { - final wc = WindowController.fromWindowId(windowId!); - if (is_maximized.value) { - wc.unmaximize(); - } else { - wc.maximize(); - } + WindowController.fromWindowId(windowId!).minimize(); } - is_maximized.value = !is_maximized.value; }, is_close: false, - ), - ); - }), - _ActionIcon( - message: 'Close', - icon: IconFont.close, - theme: theme, - onTap: () { - if (mainTab) { - windowManager.close(); - } else { - WindowController.fromWindowId(windowId!).close(); - } - }, - is_close: true, - ), + )), + Offstage( + offstage: !showMaximize, + child: 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, + 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 RxList tabInfos; final Rx selected; - final Function(String label)? onTabClose; + final Function(String key)? onTabClose; final _Theme _theme; late List<_Tab> tabs; + late final void Function(int)? onSelected; - _ListView({ - Key? key, - required this.controller, - required this.scrollController, - required this.tabInfos, - required this.selected, - required this.onTabClose, - required _Theme theme, - }) : _theme = theme, + _ListView( + {Key? key, + required this.controller, + required this.scrollController, + required this.tabInfos, + required this.selected, + required this.onTabClose, + required _Theme theme, + this.onSelected}) + : _theme = theme, super(key: key); @override @@ -279,17 +344,16 @@ class _ListView extends StatelessWidget { return Obx(() { tabs = tabInfos.asMap().entries.map((e) { int index = e.key; - String label = e.value.label; return _Tab( index: index, - label: label, + label: e.value.label, selectedIcon: e.value.selectedIcon, unselectedIcon: e.value.unselectedIcon, closable: e.value.closable, selected: selected.value, onClose: () { - tabInfos.removeWhere((tab) => tab.label == label); - onTabClose?.call(label); + tabInfos.removeWhere((tab) => tab.key == e.value.key); + onTabClose?.call(e.value.key); if (index <= selected.value) { selected.value = max(0, selected.value - 1); } @@ -305,6 +369,7 @@ class _ListView extends StatelessWidget { selected.value = index; scrollController.scrollToItem(index, center: true, animate: true); controller.value.jumpToPage(index); + onSelected?.call(selected.value); }, theme: _theme, ); @@ -322,8 +387,8 @@ class _ListView extends StatelessWidget { class _Tab extends StatelessWidget { late final int index; late final String label; - late final IconData selectedIcon; - late final IconData unselectedIcon; + late final IconData? selectedIcon; + late final IconData? unselectedIcon; late final bool closable; late final int selected; late final Function() onClose; @@ -335,8 +400,8 @@ class _Tab extends StatelessWidget { {Key? key, required this.index, required this.label, - required this.selectedIcon, - required this.unselectedIcon, + this.selectedIcon, + this.unselectedIcon, required this.closable, required this.selected, required this.onClose, @@ -346,6 +411,7 @@ class _Tab extends StatelessWidget { @override Widget build(BuildContext context) { + bool show_icon = selectedIcon != null && unselectedIcon != null; bool is_selected = index == selected; bool show_divider = index != selected - 1 && index != selected; return Ink( @@ -362,13 +428,15 @@ class _Tab extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - is_selected ? selectedIcon : unselectedIcon, - size: _kIconSize, - color: is_selected - ? theme.selectedtabIconColor - : theme.unSelectedtabIconColor, - ).paddingOnly(right: 5), + Offstage( + offstage: !show_icon, + child: Icon( + is_selected ? selectedIcon : unselectedIcon, + size: _kIconSize, + color: is_selected + ? theme.selectedtabIconColor + : theme.unSelectedtabIconColor, + ).paddingOnly(right: 5)), Text( translate(label), textAlign: TextAlign.center, diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index 74e436ebb..abbc5aadc 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -359,12 +359,12 @@ class ConnectionManager extends StatelessWidget { Widget build(BuildContext context) { final serverModel = Provider.of(context); return Column( - children: serverModel.clients.entries - .map((entry) => PaddingCard( - title: translate(entry.value.isFileTransfer + children: serverModel.clients + .map((client) => PaddingCard( + title: translate(client.isFileTransfer ? "File Connection" : "Screen Connection"), - titleIcon: entry.value.isFileTransfer + titleIcon: client.isFileTransfer ? Icons.folder_outlined : Icons.mobile_screen_share, child: Column( @@ -373,16 +373,14 @@ class ConnectionManager extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded(child: clientInfo(entry.value)), + Expanded(child: clientInfo(client)), Expanded( flex: -1, - child: entry.value.isFileTransfer || - !entry.value.authorized + child: client.isFileTransfer || !client.authorized ? SizedBox.shrink() : IconButton( onPressed: () { - gFFI.chatModel - .changeCurrentID(entry.value.id); + gFFI.chatModel.changeCurrentID(client.id); final bar = navigationBarKey.currentWidget; if (bar != null) { @@ -396,37 +394,35 @@ class ConnectionManager extends StatelessWidget { ))) ], ), - entry.value.authorized + client.authorized ? SizedBox.shrink() : Text( translate("android_new_connection_tip"), style: TextStyle(color: Colors.black54), ), - entry.value.authorized + client.authorized ? ElevatedButton.icon( style: ButtonStyle( backgroundColor: MaterialStateProperty.all(Colors.red)), icon: Icon(Icons.close), onPressed: () { - bind.cmCloseConnection(connId: entry.key); + bind.cmCloseConnection(connId: client.id); gFFI.invokeMethod( - "cancel_notification", entry.key); + "cancel_notification", client.id); }, label: Text(translate("Close"))) : Row(children: [ TextButton( child: Text(translate("Dismiss")), onPressed: () { - serverModel.sendLoginResponse( - entry.value, false); + serverModel.sendLoginResponse(client, false); }), SizedBox(width: 20), ElevatedButton( child: Text(translate("Accept")), onPressed: () { - serverModel.sendLoginResponse( - entry.value, true); + serverModel.sendLoginResponse(client, true); }), ]), ], diff --git a/flutter/lib/models/chat_model.dart b/flutter/lib/models/chat_model.dart index 524701297..a42b10ee2 100644 --- a/flutter/lib/models/chat_model.dart +++ b/flutter/lib/models/chat_model.dart @@ -2,6 +2,7 @@ import 'package:dash_chat_2/dash_chat_2.dart'; import 'package:draggable_float_widget/draggable_float_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/models/platform_model.dart'; +import 'package:window_manager/window_manager.dart'; import '../../mobile/widgets/overlay.dart'; import '../common.dart'; @@ -41,11 +42,14 @@ class ChatModel with ChangeNotifier { ..[clientModeID] = MessageBody(me, []); var _currentID = clientModeID; + late bool _isShowChatPage = false; Map get messages => _messages; int get currentID => _currentID; + bool get isShowChatPage => _isShowChatPage; + WeakReference _ffi; /// 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) { if (_messages.containsKey(id)) { _currentID = id; notifyListeners(); } else { - final client = _ffi.target?.serverModel.clients[id]; + final client = _ffi.target?.serverModel.clients + .firstWhere((client) => client.id == id); if (client == null) { return debugPrint( "Failed to changeCurrentID,remote user doesn't exist"); @@ -171,10 +192,15 @@ class ChatModel with ChangeNotifier { receive(int id, String text) async { if (text.isEmpty) return; - // first message show overlay icon + // mobile: first message show overlay icon if (chatIconOverlayEntry == null) { showChatIconOverlay(); } + // desktop: show chat page + if (!_isShowChatPage) { + toggleCMChatPage(id); + } + late final chatUser; if (id == clientModeID) { chatUser = ChatUser( diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 0bbb0c13e..527cea689 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -4,9 +4,11 @@ import 'dart:io'; import 'package:flutter/material.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 '../common.dart'; +import '../desktop/widgets/tabbar_widget.dart'; import '../mobile/pages/server_page.dart'; import 'model.dart'; @@ -30,7 +32,9 @@ class ServerModel with ChangeNotifier { late final TextEditingController _serverId; final _serverPasswd = TextEditingController(text: ""); - Map _clients = {}; + RxList tabs = RxList.empty(growable: true); + + List _clients = []; bool get isStart => _isStart; @@ -76,7 +80,7 @@ class ServerModel with ChangeNotifier { TextEditingController get serverPasswd => _serverPasswd; - Map get clients => _clients; + List get clients => _clients; final controller = ScrollController(); @@ -338,6 +342,7 @@ class ServerModel with ChangeNotifier { notifyListeners(); } + // force updateClientState([String? json]) async { var res = await bind.mainGetClientsState(); try { @@ -347,9 +352,16 @@ class ServerModel with ChangeNotifier { exit(0); } _clients.clear(); + tabs.clear(); for (var clientJson in clientsJson) { 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(); } catch (e) { @@ -360,13 +372,17 @@ class ServerModel with ChangeNotifier { void loginRequest(Map evt) { try { final client = Client.fromJson(jsonDecode(evt["client"])); - if (_clients.containsKey(client.id)) { + if (_clients.any((c) => c.id == client.id)) { return; } - _clients[client.id] = client; + _clients.add(client); + DesktopTabBar.onAdd( + tabs, + TabInfo( + key: client.id.toString(), label: client.name, closable: false)); scrollToBottom(); notifyListeners(); - showLoginDialog(client); + if (isAndroid) showLoginDialog(client); } catch (e) { debugPrint("Failed to call loginRequest,error:$e"); } @@ -419,6 +435,7 @@ class ServerModel with ChangeNotifier { } scrollToBottom() { + if (isDesktop) return; Future.delayed(Duration(milliseconds: 200), () { controller.animateTo(controller.position.maxScrollExtent, duration: Duration(milliseconds: 200), @@ -433,12 +450,14 @@ class ServerModel with ChangeNotifier { parent.target?.invokeMethod("start_capture"); } parent.target?.invokeMethod("cancel_notification", client.id); - _clients[client.id]?.authorized = true; + client.authorized = true; notifyListeners(); } else { bind.cmLoginRes(connId: client.id, res: res); 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 { final client = Client.fromJson(jsonDecode(evt['client'])); 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(); notifyListeners(); } catch (e) {} @@ -455,8 +478,10 @@ class ServerModel with ChangeNotifier { void onClientRemove(Map evt) { try { final id = int.parse(evt['id'] as String); - if (_clients.containsKey(id)) { - _clients.remove(id); + if (_clients.any((c) => c.id == id)) { + final index = _clients.indexWhere((client) => client.id == id); + _clients.removeAt(index); + DesktopTabBar.remove(tabs, index); parent.target?.dialogManager.dismissByTag(getLoginDialogTag(id)); parent.target?.invokeMethod("cancel_notification", id); } @@ -467,10 +492,11 @@ class ServerModel with ChangeNotifier { } closeAll() { - _clients.forEach((id, client) { - bind.cmCloseConnection(connId: id); + _clients.forEach((client) { + bind.cmCloseConnection(connId: client.id); }); _clients.clear(); + tabs.clear(); } } @@ -483,8 +509,10 @@ class Client { bool keyboard = false; bool clipboard = 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); Client.fromJson(Map json) { @@ -496,6 +524,8 @@ class Client { keyboard = json['keyboard']; clipboard = json['clipboard']; audio = json['audio']; + file = json['file']; + restart = json['restart']; } Map toJson() { diff --git a/src/flutter.rs b/src/flutter.rs index 5cd43b8b2..5e935642a 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -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_DESKTOP_REMOTE: &str = "remote"; 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! { // static ref SESSION: Arc>> = Default::default(); @@ -1675,6 +1676,8 @@ pub mod connection_manager { keyboard: bool, clipboard: bool, audio: bool, + file: bool, + restart: bool, #[serde(skip)] tx: UnboundedSender, } @@ -1885,8 +1888,8 @@ pub mod connection_manager { keyboard: bool, clipboard: bool, audio: bool, - _file: bool, - _restart: bool, + file: bool, + restart: bool, tx: mpsc::UnboundedSender, ) { let mut client = Client { @@ -1898,6 +1901,8 @@ pub mod connection_manager { keyboard, clipboard, audio, + file, + restart, tx, }; if authorized { @@ -1935,7 +1940,7 @@ pub mod connection_manager { if let Some(s) = GLOBAL_EVENT_STREAM .read() .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())); };