diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index b896fdf9f..47a663768 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -4,6 +4,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/instance_manager.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'models/model.dart'; @@ -38,6 +39,24 @@ class MyTheme { static const Color idColor = Color(0xFF00B6F0); static const Color darkGray = Color(0xFFB9BABC); static const Color dark = Colors.black87; + + static ThemeData lightTheme = ThemeData( + brightness: Brightness.light, + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + tabBarTheme: TabBarTheme(labelColor: Colors.black87), + ); + static ThemeData darkTheme = ThemeData( + brightness: Brightness.dark, + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + tabBarTheme: TabBarTheme(labelColor: Colors.white70)); +} + +bool isDarkTheme() { + final isDark = "Y" == Get.find().getString("darkTheme"); + debugPrint("current is dark theme: $isDark"); + return isDark; } final ButtonStyle flatButtonStyle = TextButton.styleFrom( @@ -327,4 +346,6 @@ Future initGlobalFFI() async { await _globalFFI.ffiModel.init(); // trigger connection status updater await _globalFFI.bind.mainCheckConnectStatus(); + // global shared preference + await Get.putAsync(() => SharedPreferences.getInstance()); } \ No newline at end of file diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 7a5c47c06..fe11857f3 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -16,7 +16,6 @@ import '../../mobile/pages/home_page.dart'; import '../../mobile/pages/scan_page.dart'; import '../../mobile/pages/settings_page.dart'; import '../../models/model.dart'; -import '../../models/peer_model.dart'; enum RemoteType { recently, favorite, discovered, addressBook } @@ -60,7 +59,7 @@ class _ConnectionPageState extends State { Widget build(BuildContext context) { if (_idController.text.isEmpty) _idController.text = gFFI.getId(); return Container( - decoration: BoxDecoration(color: MyTheme.grayBg), + decoration: BoxDecoration(color: isDarkTheme() ? null : MyTheme.grayBg), child: Column( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, @@ -83,7 +82,6 @@ class _ConnectionPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ TabBar( - labelColor: Colors.black87, isScrollable: true, indicatorSize: TabBarIndicatorSize.label, tabs: [ @@ -205,7 +203,7 @@ class _ConnectionPageState extends State { width: 500, padding: EdgeInsets.symmetric(horizontal: 16, vertical: 24), decoration: BoxDecoration( - color: MyTheme.white, + color: isDarkTheme() ? null : MyTheme.white, borderRadius: const BorderRadius.all(Radius.circular(13)), ), child: Ink( @@ -235,13 +233,11 @@ class _ConnectionPageState extends State { helperStyle: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, - color: MyTheme.dark, ), labelStyle: TextStyle( fontWeight: FontWeight.w600, fontSize: 26, letterSpacing: 0.2, - color: MyTheme.dark, ), ), controller: _idController, @@ -269,7 +265,6 @@ class _ConnectionPageState extends State { translate( "Transfer File", ), - style: TextStyle(color: MyTheme.dark), ), ), ), @@ -528,7 +523,6 @@ class _ConnectionPageState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), side: BorderSide(color: MyTheme.grayBg)), - color: Colors.white, child: Container( width: 200, height: double.infinity, diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index 47c066c9c..a162c3535 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -10,6 +10,7 @@ import 'package:flutter_hbb/desktop/widgets/titlebar_widget.dart'; import 'package:flutter_hbb/models/model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:tray_manager/tray_manager.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -64,7 +65,6 @@ class _DesktopHomePageState extends State with TrayListener { return ChangeNotifierProvider.value( value: gFFI.serverModel, child: Container( - decoration: BoxDecoration(color: MyTheme.white), child: Column( children: [ buildTip(context), @@ -339,13 +339,24 @@ class _DesktopHomePageState extends State with TrayListener { super.dispose(); } + void changeTheme(String choice) async { + if (choice == "Y") { + Get.changeTheme(MyTheme.darkTheme); + } else { + Get.changeTheme(MyTheme.lightTheme); + } + Get.find().setString("darkTheme", choice); + } + void onSelectMenu(String value) { if (value.startsWith('enable-')) { final option = gFFI.getOption(value); gFFI.setOption(value, option == "N" ? "" : "N"); } else if (value.startsWith('allow-')) { final option = gFFI.getOption(value); - gFFI.setOption(value, option == "Y" ? "" : "Y"); + final choice = option == "Y" ? "" : "Y"; + gFFI.setOption(value, choice); + changeTheme(choice); } else if (value == "stop-service") { final option = gFFI.getOption(value); gFFI.setOption(value, option == "Y" ? "" : "Y"); @@ -367,9 +378,8 @@ class _DesktopHomePageState extends State with TrayListener { } PopupMenuItem genEnablePopupMenuItem(String label, String value) { - final isEnable = label.startsWith('enable-') - ? gFFI.getOption(value) != "N" - : gFFI.getOption(value) != "Y"; + final v = gFFI.getOption(value); + final isEnable = value.startsWith('enable-') ? v != "N" : v == "Y"; return PopupMenuItem( child: Row( children: [ diff --git a/flutter/lib/desktop/pages/file_manager_page.dart b/flutter/lib/desktop/pages/file_manager_page.dart index de6d981ee..e37f56404 100644 --- a/flutter/lib/desktop/pages/file_manager_page.dart +++ b/flutter/lib/desktop/pages/file_manager_page.dart @@ -72,7 +72,7 @@ class _FileManagerPageState extends State return false; }, child: Scaffold( - backgroundColor: MyTheme.grayBg, + backgroundColor: isDarkTheme() ? MyTheme.dark : MyTheme.grayBg, body: Row( children: [ Flexible(flex: 3, child: body(isLocal: true)), diff --git a/flutter/lib/desktop/widgets/peercard_widget.dart b/flutter/lib/desktop/widgets/peercard_widget.dart index f5a4156c3..39acd0bf2 100644 --- a/flutter/lib/desktop/widgets/peercard_widget.dart +++ b/flutter/lib/desktop/widgets/peercard_widget.dart @@ -1,8 +1,7 @@ +import 'package:contextmenu/contextmenu.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:get/get.dart'; -import 'package:contextmenu/contextmenu.dart'; import '../../common.dart'; import '../../models/model.dart'; @@ -16,11 +15,10 @@ class _PeerCard extends StatefulWidget { final PopupMenuItemsFunc popupMenuItemsFunc; final PeerType type; - _PeerCard( - {required this.peer, - required this.popupMenuItemsFunc, - Key? key, - required this.type}) + _PeerCard({required this.peer, + required this.popupMenuItemsFunc, + Key? key, + required this.type}) : super(key: key); @override @@ -28,11 +26,13 @@ class _PeerCard extends StatefulWidget { } /// State for the connection page. -class _PeerCardState extends State<_PeerCard> { +class _PeerCardState extends State<_PeerCard> + with AutomaticKeepAliveClientMixin { var _menuPos; @override Widget build(BuildContext context) { + super.build(context); final peer = super.widget.peer; var deco = Rx(BoxDecoration( border: Border.all(color: Colors.transparent, width: 1.0), @@ -54,10 +54,9 @@ class _PeerCardState extends State<_PeerCard> { )); } - Widget _buildPeerTile( - BuildContext context, Peer peer, Rx deco) { + Widget _buildPeerTile(BuildContext context, Peer peer, Rx deco) { return Obx( - () => Container( + () => Container( decoration: deco.value, child: Column( mainAxisSize: MainAxisSize.min, @@ -104,7 +103,16 @@ class _PeerCardState extends State<_PeerCard> { ), ); } else { - return Text(translate("Loading")); + // alias has not arrived + return Center( + child: Text( + '${peer.username}@${peer.hostname}', + style: TextStyle( + color: Colors.white70, + fontSize: 12), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + )); } }, ), @@ -127,7 +135,7 @@ class _PeerCardState extends State<_PeerCard> { child: CircleAvatar( radius: 5, backgroundColor: - peer.online ? Colors.green : Colors.yellow)), + peer.online ? Colors.green : Colors.yellow)), Text('${peer.id}') ]), InkWell( @@ -175,13 +183,12 @@ class _PeerCardState extends State<_PeerCard> { ); if (value == 'remove') { setState(() => gFFI.setByName('remove', '$id')); - () async { + () async { removePreference(id); }(); } else if (value == 'file') { _connect(id, isFileTransfer: true); - } else if (value == 'add-fav') { - } else if (value == 'connect') { + } else if (value == 'add-fav') {} else if (value == 'connect') { _connect(id, isFileTransfer: false); } else if (value == 'ab-delete') { gFFI.abModel.deletePeer(id); @@ -191,6 +198,8 @@ class _PeerCardState extends State<_PeerCard> { _abEditTag(id); } else if (value == 'rename') { _rename(id); + } else if (value == 'unremember-password') { + await gFFI.bind.mainForgetPassword(id: id); } } @@ -211,7 +220,7 @@ class _PeerCardState extends State<_PeerCard> { child: GestureDetector( onTap: onTap, child: Obx( - () => Container( + () => Container( decoration: BoxDecoration( color: rxTags.contains(tagName) ? Colors.blue : null, border: Border.all(color: MyTheme.darkGray), @@ -255,12 +264,12 @@ class _PeerCardState extends State<_PeerCard> { child: Wrap( children: tags .map((e) => _buildTag(e, selectedTag, onTap: () { - if (selectedTag.contains(e)) { - selectedTag.remove(e); - } else { - selectedTag.add(e); - } - })) + if (selectedTag.contains(e)) { + selectedTag.remove(e); + } else { + selectedTag.add(e); + } + })) .toList(growable: false), ), ), @@ -366,6 +375,9 @@ class _PeerCardState extends State<_PeerCard> { ); }); } + + @override + bool get wantKeepAlive => true; } abstract class BasePeerCard extends StatelessWidget { diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index bb6684438..f2ebb3134 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter_hbb/desktop/screen/desktop_file_transfer_screen.dart'; import 'package:flutter_hbb/desktop/screen/desktop_remote_screen.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; import 'package:get/route_manager.dart'; import 'package:provider/provider.dart'; @@ -32,6 +33,10 @@ Future main(List args) async { runRustDeskApp(args); } +ThemeData getCurrentTheme() { + return isDarkTheme() ? MyTheme.darkTheme : MyTheme.darkTheme; +} + void runRustDeskApp(List args) async { if (!isDesktop) { runApp(App()); @@ -47,12 +52,17 @@ void runRustDeskApp(List args) async { WindowType wType = type.windowType; switch (wType) { case WindowType.RemoteDesktop: - runApp(DesktopRemoteScreen( - params: argument, + runApp(GetMaterialApp( + theme: getCurrentTheme(), + home: DesktopRemoteScreen( + params: argument, + ), )); break; case WindowType.FileTransfer: - runApp(DesktopFileTransferScreen(params: argument)); + runApp(GetMaterialApp( + theme: getCurrentTheme(), + home: DesktopFileTransferScreen(params: argument))); break; default: break; @@ -85,10 +95,7 @@ class App extends StatelessWidget { navigatorKey: globalKey, debugShowCheckedModeBanner: false, title: 'RustDesk', - theme: ThemeData( - primarySwatch: Colors.blue, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), + theme: getCurrentTheme(), home: isDesktop ? DesktopHomePage() : !isAndroid diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 30c9c7591..afbe35ec8 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -20,11 +20,11 @@ use crate::flutter::{self, Session, SESSIONS}; use crate::start_server; use crate::ui_interface; use crate::ui_interface::{ - change_id, check_connect_status, get_api_server, get_app_name, get_async_job_status, - get_connect_status, get_fav, get_id, get_lan_peers, get_license, get_local_option, get_options, - get_peer, get_peer_option, get_socks, get_sound_inputs, get_uuid, get_version, - has_rendezvous_service, is_ok_change_id, post_request, set_local_option, set_options, - set_peer_option, set_socks, store_fav, test_if_valid_server, using_public_server, + change_id, check_connect_status, forget_password, get_api_server, get_app_name, + get_async_job_status, get_connect_status, get_fav, get_id, get_lan_peers, get_license, + get_local_option, get_options, get_peer, get_peer_option, get_socks, get_sound_inputs, + get_uuid, get_version, has_rendezvous_service, is_ok_change_id, post_request, set_local_option, + set_options, set_peer_option, set_socks, store_fav, test_if_valid_server, using_public_server, }; fn initialize(app_dir: &str) { @@ -504,6 +504,10 @@ pub fn main_set_peer_option(id: String, key: String, value: String) { set_peer_option(id, key, value) } +pub fn main_forget_password(id: String) { + forget_password(id) +} + /// FFI for **get** commands which are idempotent. /// Return result in c string. ///