diff --git a/flutter/analysis_options.yaml b/flutter/analysis_options.yaml index 61b6c4de1..a679f5774 100644 --- a/flutter/analysis_options.yaml +++ b/flutter/analysis_options.yaml @@ -1,29 +1,6 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml +include: package:lints/recommended.yaml linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options + non_constant_identifier_names: false + sort_child_properties_last: false diff --git a/flutter/lib/desktop/pages/connection_page.dart b/flutter/lib/desktop/pages/connection_page.dart index 9024c996f..e857df271 100644 --- a/flutter/lib/desktop/pages/connection_page.dart +++ b/flutter/lib/desktop/pages/connection_page.dart @@ -1,3 +1,5 @@ +// main window right pane + import 'dart:async'; import 'dart:convert'; diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index c8706d5a0..b574900fe 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -5,29 +5,14 @@ import 'package:flutter/material.dart' hide MenuItem; import 'package:flutter/services.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/desktop/pages/connection_page.dart'; -import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart'; -import 'package:flutter_hbb/desktop/widgets/popup_menu.dart'; -import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart' - as mod_menu; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; 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'; import 'package:window_manager/window_manager.dart'; -import '../../common/widgets/dialog.dart'; - -class _MenubarTheme { - static const Color commonColor = MyTheme.accent; - // kMinInteractiveDimension - static const double height = 25.0; - static const double dividerHeight = 12.0; -} - class DesktopHomePage extends StatefulWidget { const DesktopHomePage({Key? key}) : super(key: key); @@ -66,19 +51,19 @@ class _DesktopHomePageState extends State super.build(context); return Row( children: [ - buildServerInfo(context), + buildLeftPane(context), const VerticalDivider( width: 1, thickness: 1, ), Expanded( - child: buildServerBoard(context), + child: buildRightPane(context), ), ], ); } - buildServerInfo(BuildContext context) { + buildLeftPane(BuildContext context) { return ChangeNotifierProvider.value( value: gFFI.serverModel, child: Container( @@ -95,7 +80,7 @@ class _DesktopHomePageState extends State ); } - buildServerBoard(BuildContext context) { + buildRightPane(BuildContext context) { return Container( color: MyTheme.color(context).grayBg, child: ConnectionPage(), @@ -167,93 +152,9 @@ class _DesktopHomePageState extends State } Widget buildPopupMenu(BuildContext context) { - var position; RxBool hover = false.obs; return InkWell( - onTapDown: (detail) { - final x = detail.globalPosition.dx; - final y = detail.globalPosition.dy; - position = RelativeRect.fromLTRB(x, y, x, y); - }, - onTap: () async { - final userName = await gFFI.userModel.getUserName(); - final enabledInput = await bind.mainGetOption(key: 'enable-audio'); - final defaultInput = await gFFI.getDefaultAudioInput(); - var menu = [ - await genEnablePopupMenuItem( - translate("Enable Keyboard/Mouse"), - 'enable-keyboard', - ), - await genEnablePopupMenuItem( - translate("Enable Clipboard"), - 'enable-clipboard', - ), - await genEnablePopupMenuItem( - translate("Enable File Transfer"), - 'enable-file-transfer', - ), - await genEnablePopupMenuItem( - translate("Enable TCP Tunneling"), - 'enable-tunnel', - ), - genAudioInputPopupMenuItem(enabledInput != "N", defaultInput), - PopupMenuDivider(), - PopupMenuItem( - child: Text(translate("ID/Relay Server")), - value: 'custom-server', - ), - PopupMenuItem( - child: Text(translate("IP Whitelisting")), - value: 'whitelist', - ), - PopupMenuItem( - child: Text(translate("Socks5 Proxy")), - value: 'socks5-proxy', - ), - PopupMenuDivider(), - await genEnablePopupMenuItem( - translate("Enable Service"), - 'stop-service', - ), - // TODO: direct server - await genEnablePopupMenuItem( - translate("Always connected via relay"), - 'allow-always-relay', - ), - await genEnablePopupMenuItem( - translate("Start ID/relay service"), - 'stop-rendezvous-service', - ), - PopupMenuDivider(), - userName.isEmpty - ? PopupMenuItem( - child: Text(translate("Login")), - value: 'login', - ) - : PopupMenuItem( - child: Text("${translate("Logout")} $userName"), - value: 'logout', - ), - PopupMenuItem( - child: Text(translate("Change ID")), - value: 'change-id', - ), - PopupMenuDivider(), - await genEnablePopupMenuItem( - translate("Dark Theme"), - 'allow-darktheme', - ), - PopupMenuItem( - child: Text(translate("About")), - value: 'about', - ), - ]; - final v = - await showMenu(context: context, position: position, items: menu); - if (v != null) { - onSelectMenu(v); - } - }, + onTap: () async {}, child: Obx( () => CircleAvatar( radius: 15, @@ -276,6 +177,7 @@ class _DesktopHomePageState extends State buildPasswordBoard(BuildContext context) { final model = gFFI.serverModel; RxBool refreshHover = false.obs; + RxBool editHover = false.obs; return Container( margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13), child: Row( @@ -334,7 +236,19 @@ class _DesktopHomePageState extends State onTap: () => bind.mainUpdateTemporaryPassword(), onHover: (value) => refreshHover.value = value, ), - const _PasswordPopupMenu(), + InkWell( + child: Obx( + () => Icon( + Icons.edit, + color: editHover.value + ? MyTheme.color(context).text + : Color(0xFFDDDDDD), + size: 22, + ).marginOnly(right: 8, bottom: 2), + ), + onTap: () => {}, + onHover: (value) => editHover.value = value, + ), ], ), ], @@ -408,236 +322,6 @@ class _DesktopHomePageState extends State windowManager.removeListener(this); super.dispose(); } - - void changeTheme(String choice) async { - if (choice == "Y") { - Get.changeTheme(MyTheme.darkTheme); - } else { - Get.changeTheme(MyTheme.lightTheme); - } - Get.find().setString("darkTheme", choice); - Get.forceAppUpdate(); - } - - void onSelectMenu(String key) async { - if (key.startsWith('enable-')) { - final option = await bind.mainGetOption(key: key); - bind.mainSetOption(key: key, value: option == "N" ? "" : "N"); - } else if (key.startsWith('allow-')) { - final option = await bind.mainGetOption(key: key); - final choice = option == "Y" ? "" : "Y"; - bind.mainSetOption(key: key, value: choice); - if (key == "allow-darktheme") changeTheme(choice); - } else if (key == "stop-service") { - final option = await bind.mainGetOption(key: key); - bind.mainSetOption(key: key, value: option == "Y" ? "" : "Y"); - } else if (key == "change-id") { - changeIdDialog(); - } else if (key == "custom-server") { - changeServer(); - } else if (key == "whitelist") { - changeWhiteList(); - } else if (key == "socks5-proxy") { - changeSocks5Proxy(); - } else if (key == "about") { - about(); - } else if (key == "logout") { - logOut(); - } else if (key == "login") { - login(); - } - } - - Future> genEnablePopupMenuItem( - String label, String key) async { - final v = await bind.mainGetOption(key: key); - bool enable; - if (key == "stop-service") { - enable = v != "Y"; - } else if (key.startsWith("allow-")) { - enable = v == "Y"; - } else { - enable = v != "N"; - } - - return PopupMenuItem( - child: Row( - children: [ - Icon(Icons.check, - color: enable ? null : MyTheme.accent.withAlpha(00)), - Text( - label, - style: genTextStyle(enable), - ), - ], - ), - value: key, - ); - } - - TextStyle genTextStyle(bool isPositive) { - return isPositive - ? TextStyle() - : TextStyle( - color: Colors.redAccent, decoration: TextDecoration.lineThrough); - } - - PopupMenuItem genAudioInputPopupMenuItem( - bool enableInput, String defaultAudioInput) { - final defaultInput = defaultAudioInput.obs; - final enabled = enableInput.obs; - - return PopupMenuItem( - child: FutureBuilder>( - future: gFFI.getAudioInputs(), - builder: (context, snapshot) { - if (snapshot.hasData) { - final inputs = snapshot.data!.toList(); - if (Platform.isWindows) { - inputs.insert(0, translate("System Sound")); - } - var inputList = inputs - .map((e) => PopupMenuItem( - child: Row( - children: [ - Obx(() => Offstage( - offstage: defaultInput.value != e, - child: Icon(Icons.check))), - Expanded( - child: Tooltip( - message: e, - child: Text( - "$e", - maxLines: 1, - overflow: TextOverflow.ellipsis, - ))), - ], - ), - value: e, - )) - .toList(); - inputList.insert( - 0, - PopupMenuItem( - child: Row( - children: [ - Obx(() => Offstage( - offstage: enabled.value, child: Icon(Icons.check))), - Expanded(child: Text(translate("Mute"))), - ], - ), - value: "Mute", - )); - return PopupMenuButton( - padding: EdgeInsets.zero, - child: Container( - alignment: Alignment.centerLeft, - child: Text(translate("Audio Input"))), - itemBuilder: (context) => inputList, - onSelected: (dev) async { - if (dev == "Mute") { - await bind.mainSetOption( - key: 'enable-audio', value: enabled.value ? '' : 'N'); - enabled.value = - await bind.mainGetOption(key: 'enable-audio') != 'N'; - } else if (dev != await gFFI.getDefaultAudioInput()) { - gFFI.setDefaultAudioInput(dev); - defaultInput.value = dev; - } - }, - ); - } else { - return Text("..."); - } - }, - ), - value: 'audio-input', - ); - } - - void about() async { - final appName = await bind.mainGetAppName(); - final license = await bind.mainGetLicense(); - final version = await bind.mainGetVersion(); - const linkStyle = TextStyle(decoration: TextDecoration.underline); - gFFI.dialogManager.show((setState, close) { - return CustomAlertDialog( - title: Text("About $appName"), - content: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 500), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 8.0, - ), - Text("Version: $version").marginSymmetric(vertical: 4.0), - InkWell( - onTap: () { - launchUrlString("https://rustdesk.com/privacy"); - }, - child: const Text( - "Privacy Statement", - style: linkStyle, - ).marginSymmetric(vertical: 4.0)), - InkWell( - onTap: () { - launchUrlString("https://rustdesk.com"); - }, - child: const Text( - "Website", - style: linkStyle, - ).marginSymmetric(vertical: 4.0)), - Container( - decoration: const BoxDecoration(color: Color(0xFF2c8cff)), - padding: - const EdgeInsets.symmetric(vertical: 24, horizontal: 8), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Copyright © 2022 Purslane Ltd.\n$license", - style: const TextStyle(color: Colors.white), - ), - const Text( - "Made with heart in this chaotic world!", - style: TextStyle( - fontWeight: FontWeight.w800, - color: Colors.white), - ) - ], - ), - ), - ], - ), - ).marginSymmetric(vertical: 4.0) - ], - ), - ), - actions: [ - TextButton(onPressed: close, child: Text(translate("OK"))), - ], - onSubmit: close, - onCancel: close, - ); - }); - } - - void login() { - loginDialog().then((success) { - if (success) { - // refresh frame - setState(() {}); - } - }); - } - - void logOut() { - gFFI.userModel.logOut().then((_) => {setState(() {})}); - } } /// common login dialog for desktop @@ -874,120 +558,3 @@ void setPasswordDialog() async { ); }); } - -class _PasswordPopupMenu extends StatefulWidget { - const _PasswordPopupMenu({Key? key}) : super(key: key); - - @override - State<_PasswordPopupMenu> createState() => _PasswordPopupMenuState(); -} - -class _PasswordPopupMenuState extends State<_PasswordPopupMenu> { - final RxBool _tempEnabled = true.obs; - final RxBool _permEnabled = true.obs; - - List> _buildMenus() { - return >[ - MenuEntryRadios( - text: translate('Password type'), - optionsGetter: () => [ - MenuEntryRadioOption( - text: translate('Use temporary password'), - value: kUseTemporaryPassword), - MenuEntryRadioOption( - text: translate('Use permanent password'), - value: kUsePermanentPassword), - MenuEntryRadioOption( - text: translate('Use both passwords'), - value: kUseBothPasswords), - ], - curOptionGetter: () async { - return gFFI.serverModel.verificationMethod; - }, - optionSetter: (String oldValue, String newValue) async { - await bind.mainSetOption( - key: "verification-method", value: newValue); - await gFFI.serverModel.updatePasswordModel(); - setState(() { - _tempEnabled.value = - gFFI.serverModel.verificationMethod != kUsePermanentPassword; - _permEnabled.value = - gFFI.serverModel.verificationMethod != kUseTemporaryPassword; - }); - }), - MenuEntryDivider(), - MenuEntryButton( - enabled: _permEnabled, - childBuilder: (TextStyle? style) => Text( - translate('Set permanent password'), - style: style, - ), - proc: () { - setPasswordDialog(); - }, - dismissOnClicked: true, - ), - MenuEntrySubMenu( - enabled: _tempEnabled, - text: translate('Set temporary password length'), - entries: [ - MenuEntryRadios( - enabled: _tempEnabled, - text: translate(''), - optionsGetter: () => [ - MenuEntryRadioOption( - text: translate('6'), - value: '6', - enabled: _tempEnabled, - ), - MenuEntryRadioOption( - text: translate('8'), - value: '8', - enabled: _tempEnabled, - ), - MenuEntryRadioOption( - text: translate('10'), - value: '10', - enabled: _tempEnabled, - ), - ], - curOptionGetter: () async { - return gFFI.serverModel.temporaryPasswordLength; - }, - optionSetter: (String oldValue, String newValue) async { - if (oldValue != newValue) { - await gFFI.serverModel.setTemporaryPasswordLength(newValue); - await gFFI.serverModel.updatePasswordModel(); - } - }), - ]) - ]; - } - - @override - Widget build(BuildContext context) { - final editHover = false.obs; - return mod_menu.PopupMenuButton( - padding: EdgeInsets.zero, - onHover: (v) => editHover.value = v, - tooltip: translate(''), - position: mod_menu.PopupMenuPosition.overSide, - itemBuilder: (BuildContext context) => _buildMenus() - .map((entry) => entry.build( - context, - const MenuConfig( - commonColor: _MenubarTheme.commonColor, - height: _MenubarTheme.height, - dividerHeight: _MenubarTheme.dividerHeight, - ))) - .expand((i) => i) - .toList(), - child: Obx(() => Icon(Icons.edit, - size: 22, - color: editHover.value - ? MyTheme.color(context).text - : const Color(0xFFDDDDDD)) - .marginOnly(bottom: 2)), - ); - } -} diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index 7c0b90e54..c6caebc77 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -140,7 +140,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.0" charcode: dependency: transitive description: @@ -161,7 +161,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.1.0" code_builder: dependency: transitive description: @@ -367,6 +367,13 @@ packages: url: "https://github.com/Kingtous/rustdesk_flutter_custom_cursor" source: git version: "0.0.1" + flutter_improved_scrolling: + dependency: "direct main" + description: + name: flutter_improved_scrolling + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.3" flutter_lints: dependency: "direct dev" description: @@ -374,6 +381,11 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_parsed_text: dependency: transitive description: @@ -478,7 +490,7 @@ packages: name: icons_launcher url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.0.4" image: dependency: "direct main" description: @@ -576,7 +588,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.5" + version: "0.1.4" menu_base: dependency: transitive description: @@ -590,7 +602,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.7.0" mime: dependency: transitive description: @@ -667,7 +679,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.8.1" path_provider: dependency: "direct main" description: diff --git a/flutter/test/widget_test.dart b/flutter/test/widget_test.dart deleted file mode 100644 index 3e7534740..000000000 --- a/flutter/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:flutter_hbb/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(App()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}