Merge branch 'master' into mobile_feat_update_rebase

This commit is contained in:
csf 2022-09-21 14:12:22 +08:00
commit 9284850dff
23 changed files with 404 additions and 729 deletions

View File

@ -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

View File

@ -244,6 +244,32 @@ final ButtonStyle flatButtonStyle = TextButton.styleFrom(
),
);
List<Locale> supportedLocales = const [
// specify CN/TW to fix CJK issue in flutter
Locale('zh', 'CN'),
Locale('zh', 'TW'),
Locale('zh', 'SG'),
Locale('fr'),
Locale('de'),
Locale('it'),
Locale('ja'),
Locale('cs'),
Locale('pl'),
Locale('ko'),
Locale('hu'),
Locale('pt'),
Locale('ru'),
Locale('sk'),
Locale('id'),
Locale('da'),
Locale('eo'),
Locale('tr'),
Locale('vi'),
Locale('pl'),
Locale('kz'),
Locale('en', 'US'),
];
String formatDurationToTime(Duration duration) {
var totalTime = duration.inSeconds;
final secs = totalTime % 60;
@ -734,8 +760,9 @@ class PermissionManager {
if (isDesktop) {
return Future.value(true);
}
if (!permissions.contains(type))
if (!permissions.contains(type)) {
return Future.error("Wrong permission!$type");
}
return gFFI.invokeMethod("check_permission", type);
}
@ -743,8 +770,9 @@ class PermissionManager {
if (isDesktop) {
return Future.value(true);
}
if (!permissions.contains(type))
if (!permissions.contains(type)) {
return Future.error("Wrong permission!$type");
}
gFFI.invokeMethod("request_permission", type);
if (type == "ignore_battery_optimizations") {

View File

@ -33,6 +33,7 @@ class IDTextInputFormatter extends TextInputFormatter {
String formatID(String id) {
String id2 = id.replaceAll(' ', '');
if (int.tryParse(id2) == null) return id;
String newID = '';
if (id2.length <= 3) {
newID = id2;

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:visibility_detector/visibility_detector.dart';
@ -41,6 +42,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
static const int _maxQueryCount = 3;
final space = isDesktop ? 12.0 : 8.0;
final _curPeers = <String>{};
final _scrollController = ScrollController();
var _lastChangeTime = DateTime.now();
var _lastQueryPeers = <String>{};
var _lastQueryTime = DateTime.now().subtract(const Duration(hours: 1));
@ -94,57 +96,59 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
? Center(
child: Text(translate("Empty")),
)
: SingleChildScrollView(
controller: ScrollController(),
child: ObxValue<RxString>((searchText) {
return FutureBuilder<List<Peer>>(
builder: (context, snapshot) {
if (snapshot.hasData) {
final peers = snapshot.data!;
final cards = <Widget>[];
for (final peer in peers) {
final visibilityChild = VisibilityDetector(
key: ValueKey(peer.id),
onVisibilityChanged: (info) {
final peerId = (info.key as ValueKey).value;
if (info.visibleFraction > 0.00001) {
_curPeers.add(peerId);
} else {
_curPeers.remove(peerId);
}
_lastChangeTime = DateTime.now();
},
child: widget.peerCardWidgetFunc(peer),
);
cards.add(Offstage(
key: ValueKey("off${peer.id}"),
offstage: widget.offstageFunc(peer),
child: isDesktop
? Obx(
() => SizedBox(
width: 220,
height: peerCardUiType.value ==
PeerUiType.grid
: DesktopScrollWrapper(
scrollController: _scrollController,
child: SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
controller: _scrollController,
child: ObxValue<RxString>((searchText) {
return FutureBuilder<List<Peer>>(
builder: (context, snapshot) {
if (snapshot.hasData) {
final peers = snapshot.data!;
final cards = <Widget>[];
for (final peer in peers) {
cards.add(Offstage(
key: ValueKey("off${peer.id}"),
offstage: widget.offstageFunc(peer),
child: Obx(
() => SizedBox(
width: 220,
height:
peerCardUiType.value == PeerUiType.grid
? 140
: 42,
child: visibilityChild,
),
)
: SizedBox(
width: mobileWidth,
child: visibilityChild)));
child: VisibilityDetector(
key: ValueKey(peer.id),
onVisibilityChanged: (info) {
final peerId =
(info.key as ValueKey).value;
if (info.visibleFraction > 0.00001) {
_curPeers.add(peerId);
} else {
_curPeers.remove(peerId);
}
_lastChangeTime = DateTime.now();
},
child: widget.peerCardWidgetFunc(peer),
),
),
)));
}
return Wrap(
spacing: space,
runSpacing: space,
children: cards);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
return Wrap(
spacing: space, runSpacing: space, children: cards);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
future: matchPeers(searchText.value, peers.peers),
);
}, peerSearchText),
},
future: matchPeers(searchText.value, peers.peers),
);
}, peerSearchText),
),
),
),
);

View File

@ -17,6 +17,13 @@ const int kMobileDefaultDisplayHeight = 1280;
const int kDesktopDefaultDisplayWidth = 1080;
const int kDesktopDefaultDisplayHeight = 720;
/// [kDefaultScrollAmountMultiplier] indicates how many rows can be scrolled after a minimum scroll action of mouse
const kDefaultScrollAmountMultiplier = 5.0;
const kDefaultScrollDuration = Duration(milliseconds: 50);
const kDefaultMouseWhellThrottleDuration = Duration(milliseconds: 50);
const kFullScreenEdgeSize = 0.0;
const kWindowEdgeSize = 1.0;
const kInvalidValueStr = "InvalidValueStr";
const kMobilePageConstraints = BoxConstraints(maxWidth: 600);

View File

@ -1,3 +1,5 @@
// main window right pane
import 'dart:async';
import 'dart:convert';
@ -82,8 +84,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
).marginSymmetric(horizontal: 22),
),
const Divider(),
SizedBox(height: 50, child: Obx(() => buildStatus()))
.paddingSymmetric(horizontal: 12.0)
SizedBox(child: Obx(() => buildStatus()))
.paddingOnly(bottom: 12, top: 6),
]),
);
}
@ -187,7 +189,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
onConnect(isFileTransfer: true);
},
child: Container(
height: 24,
height: 27,
alignment: Alignment.center,
decoration: BoxDecoration(
color: ftPressed.value
@ -224,31 +226,36 @@ class _ConnectionPageState extends State<ConnectionPage> {
onTapCancel: () => connPressed.value = false,
onHover: (value) => connHover.value = value,
onTap: onConnect,
child: Container(
height: 24,
decoration: BoxDecoration(
color: connPressed.value
? MyTheme.accent
: MyTheme.button,
border: Border.all(
color: connPressed.value
? MyTheme.accent
: connHover.value
? MyTheme.hoverBorder
: MyTheme.button,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 80.0,
),
borderRadius: BorderRadius.circular(5),
),
child: Center(
child: Text(
translate(
"Connect",
child: Container(
height: 27,
decoration: BoxDecoration(
color: connPressed.value
? MyTheme.accent
: MyTheme.button,
border: Border.all(
color: connPressed.value
? MyTheme.accent
: connHover.value
? MyTheme.hoverBorder
: MyTheme.button,
),
borderRadius: BorderRadius.circular(5),
),
style: TextStyle(
fontSize: 12, color: MyTheme.color(context).bg),
),
).marginSymmetric(horizontal: 12),
),
child: Center(
child: Text(
translate(
"Connect",
),
style: TextStyle(
fontSize: 12,
color: MyTheme.color(context).bg),
),
).marginSymmetric(horizontal: 12),
)),
),
),
],
@ -275,6 +282,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
var svcIsUsingPublicServer = true.obs;
Widget buildStatus() {
final fontSize = 14.0;
final textStyle = TextStyle(fontSize: fontSize);
final light = Container(
height: 8,
width: 8,
@ -282,13 +291,13 @@ class _ConnectionPageState extends State<ConnectionPage> {
borderRadius: BorderRadius.circular(20),
color: svcStopped.value ? Colors.redAccent : Colors.green,
),
).paddingSymmetric(horizontal: 10.0);
).paddingSymmetric(horizontal: 12.0);
if (svcStopped.value) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
light,
Text(translate("Service is not running")),
Text(translate("Service is not running"), style: textStyle),
TextButton(
onPressed: () async {
bool checked = await bind.mainCheckSuperUserPermission();
@ -296,19 +305,25 @@ class _ConnectionPageState extends State<ConnectionPage> {
bind.mainSetOption(key: "stop-service", value: "");
}
},
child: Text(translate("Start Service")))
child: Text(translate("Start Service"), style: textStyle))
],
);
} else {
if (svcStatusCode.value == 0) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [light, Text(translate("connecting_status"))],
children: [
light,
Text(translate("connecting_status"), style: textStyle)
],
);
} else if (svcStatusCode.value == -1) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [light, Text(translate("not_ready_status"))],
children: [
light,
Text(translate("not_ready_status"), style: textStyle)
],
);
}
}
@ -316,13 +331,15 @@ class _ConnectionPageState extends State<ConnectionPage> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
light,
Text(translate('Ready')),
Text(translate('Ready'), style: textStyle),
Text(', ', style: textStyle),
svcIsUsingPublicServer.value
? InkWell(
onTap: onUsePublicServerGuide,
child: Text(
', ${translate('setup_server_tip')}',
style: TextStyle(decoration: TextDecoration.underline),
translate('setup_server_tip'),
style: TextStyle(
decoration: TextDecoration.underline, fontSize: fontSize),
),
)
: Offstage()

View File

@ -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<DesktopHomePage>
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<DesktopHomePage>
);
}
buildServerBoard(BuildContext context) {
buildRightPane(BuildContext context) {
return Container(
color: MyTheme.color(context).grayBg,
child: ConnectionPage(),
@ -167,93 +152,9 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}
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 = <PopupMenuEntry>[
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<DesktopHomePage>
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<DesktopHomePage>
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,
),
],
),
],
@ -376,7 +290,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
@override
void onTrayMenuItemClick(MenuItem menuItem) {
print('click ${menuItem.key}');
debugPrint('click ${menuItem.key}');
switch (menuItem.key) {
case "quit":
exit(0);
@ -394,8 +308,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
trayManager.addListener(this);
windowManager.addListener(this);
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
print(
"call ${call.method} with args ${call.arguments} from window ${fromWindowId}");
debugPrint(
"call ${call.method} with args ${call.arguments} from window $fromWindowId");
if (call.method == "main_window_on_top") {
window_on_top(null);
}
@ -408,236 +322,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
windowManager.removeListener(this);
super.dispose();
}
void changeTheme(String choice) async {
if (choice == "Y") {
Get.changeTheme(MyTheme.darkTheme);
} else {
Get.changeTheme(MyTheme.lightTheme);
}
Get.find<SharedPreferences>().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<PopupMenuItem<String>> 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<String> genAudioInputPopupMenuItem(
bool enableInput, String defaultAudioInput) {
final defaultInput = defaultAudioInput.obs;
final enabled = enableInput.obs;
return PopupMenuItem(
child: FutureBuilder<List<String>>(
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<String>(
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 &copy; 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
@ -689,8 +373,7 @@ Future<bool> loginDialog() async {
debugPrint("$resp");
completer.complete(true);
} catch (err) {
// ignore: avoid_print
print(err.toString());
debugPrint(err.toString());
cancel();
return;
}
@ -874,120 +557,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<MenuEntryBase<String>> _buildMenus() {
return <MenuEntryBase<String>>[
MenuEntryRadios<String>(
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<String>(
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<String>(
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)),
);
}
}

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
@ -35,28 +37,30 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
Widget build(BuildContext context) {
RxBool fullscreen = false.obs;
Get.put(fullscreen, tag: 'fullscreen');
return Obx(() => DragToResizeArea(
resizeEdgeSize: fullscreen.value ? 1.0 : 8.0,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Overlay(initialEntries: [
OverlayEntry(builder: (context) {
gFFI.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: DesktopTab(
controller: tabController,
tail: ActionIcon(
message: 'Settings',
icon: IconFont.menu,
onTap: onAddSetting,
isClose: false,
),
));
})
]),
)));
final tabWidget = Container(
child: Overlay(initialEntries: [
OverlayEntry(builder: (context) {
gFFI.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: DesktopTab(
controller: tabController,
tail: ActionIcon(
message: 'Settings',
icon: IconFont.menu,
onTap: onAddSetting,
isClose: false,
),
));
})
]),
);
return Platform.isMacOS
? tabWidget
: Obx(() => DragToResizeArea(
resizeEdgeSize:
fullscreen.value ? kFullScreenEdgeSize : kWindowEdgeSize,
child: tabWidget));
}
void onAddSetting() {

View File

@ -1,8 +1,10 @@
import 'dart:convert';
import 'dart:io';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/file_manager_page.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
@ -66,20 +68,24 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
@override
Widget build(BuildContext context) {
return SubWindowDragToResizeArea(
windowId: windowId(),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: DesktopTab(
controller: tabController,
onWindowCloseButton: handleWindowCloseButton,
tail: const AddButton().paddingOnly(left: 10),
)),
),
final tabWidget = Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: DesktopTab(
controller: tabController,
onWindowCloseButton: handleWindowCloseButton,
tail: const AddButton().paddingOnly(left: 10),
)),
);
return Platform.isMacOS
? tabWidget
: SubWindowDragToResizeArea(
resizeEdgeSize: kWindowEdgeSize,
windowId: windowId(),
child: tabWidget,
);
}
void onRemoveId(String id) {

View File

@ -1,8 +1,10 @@
import 'dart:convert';
import 'dart:io';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/port_forward_page.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
@ -74,23 +76,27 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
@override
Widget build(BuildContext context) {
return SubWindowDragToResizeArea(
windowId: windowId(),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: DesktopTab(
controller: tabController,
onWindowCloseButton: () async {
tabController.clear();
return true;
},
tail: AddButton().paddingOnly(left: 10),
)),
),
final tabWidget = Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: DesktopTab(
controller: tabController,
onWindowCloseButton: () async {
tabController.clear();
return true;
},
tail: AddButton().paddingOnly(left: 10),
)),
);
return Platform.isMacOS
? tabWidget
: SubWindowDragToResizeArea(
resizeEdgeSize: kWindowEdgeSize,
windowId: windowId(),
child: tabWidget,
);
}
void onRemoveId(String id) {

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
@ -86,63 +87,66 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
@override
Widget build(BuildContext context) {
final RxBool fullscreen = Get.find(tag: 'fullscreen');
return Obx(() => SubWindowDragToResizeArea(
resizeEdgeSize: fullscreen.value ? 1.0 : 8.0,
windowId: windowId(),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: DesktopTab(
controller: tabController,
showTabBar: fullscreen.isFalse,
onWindowCloseButton: handleWindowCloseButton,
tail: const AddButton().paddingOnly(left: 10),
pageViewBuilder: (pageView) {
WindowController.fromWindowId(windowId())
.setFullscreen(fullscreen.isTrue);
return pageView;
},
tabBuilder: (key, icon, label, themeConf) => Obx(() {
final connectionType = ConnectionTypeState.find(key);
if (!connectionType.isValid()) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
label,
],
);
} else {
final msgDirect = translate(connectionType.direct.value ==
ConnectionType.strDirect
? 'Direct Connection'
: 'Relay Connection');
final msgSecure = translate(connectionType.secure.value ==
ConnectionType.strSecure
? 'Secure Connection'
: 'Insecure Connection');
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
Tooltip(
message: '$msgDirect\n$msgSecure',
child: Image.asset(
'assets/${connectionType.secure.value}${connectionType.direct.value}.png',
width: themeConf.iconSize,
height: themeConf.iconSize,
).paddingOnly(right: 5),
),
label,
],
);
}
}),
)),
),
));
final tabWidget = Container(
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
body: DesktopTab(
controller: tabController,
showTabBar: fullscreen.isFalse,
onWindowCloseButton: handleWindowCloseButton,
tail: const AddButton().paddingOnly(left: 10),
pageViewBuilder: (pageView) {
WindowController.fromWindowId(windowId())
.setFullscreen(fullscreen.isTrue);
return pageView;
},
tabBuilder: (key, icon, label, themeConf) => Obx(() {
final connectionType = ConnectionTypeState.find(key);
if (!connectionType.isValid()) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
label,
],
);
} else {
final msgDirect = translate(
connectionType.direct.value == ConnectionType.strDirect
? 'Direct Connection'
: 'Relay Connection');
final msgSecure = translate(
connectionType.secure.value == ConnectionType.strSecure
? 'Secure Connection'
: 'Insecure Connection');
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
Tooltip(
message: '$msgDirect\n$msgSecure',
child: Image.asset(
'assets/${connectionType.secure.value}${connectionType.direct.value}.png',
width: themeConf.iconSize,
height: themeConf.iconSize,
).paddingOnly(right: 5),
),
label,
],
);
}
}),
)),
);
return Platform.isMacOS
? tabWidget
: Obx(() => SubWindowDragToResizeArea(
resizeEdgeSize:
fullscreen.value ? kFullScreenEdgeSize : kWindowEdgeSize,
windowId: windowId(),
child: tabWidget));
}
void onRemoveId(String id) {

View File

@ -0,0 +1,26 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_improved_scrolling/flutter_improved_scrolling.dart';
class DesktopScrollWrapper extends StatelessWidget {
final ScrollController scrollController;
final Widget child;
const DesktopScrollWrapper(
{Key? key, required this.scrollController, required this.child})
: super(key: key);
@override
Widget build(BuildContext context) {
return ImprovedScrolling(
scrollController: scrollController,
enableCustomMouseWheelScrolling: true,
customMouseWheelScrollConfig: CustomMouseWheelScrollConfig(
scrollDuration: kDefaultScrollDuration,
scrollCurve: Curves.linearToEaseOut,
mouseWheelTurnsThrottleTimeMs:
kDefaultMouseWhellThrottleDuration.inMilliseconds,
scrollAmountMultiplier: kDefaultScrollAmountMultiplier),
child: child,
);
}
}

View File

@ -8,6 +8,7 @@ import 'package:flutter_hbb/desktop/screen/desktop_file_transfer_screen.dart';
import 'package:flutter_hbb/desktop/screen/desktop_port_forward_screen.dart';
import 'package:flutter_hbb/desktop/screen/desktop_remote_screen.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -123,6 +124,12 @@ void runRemoteScreen(Map<String, dynamic> argument) async {
home: DesktopRemoteScreen(
params: argument,
),
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: supportedLocales,
navigatorObservers: const [
// FirebaseAnalyticsObserver(analytics: analytics),
],
@ -141,6 +148,12 @@ void runFileTransferScreen(Map<String, dynamic> argument) async {
darkTheme: MyTheme.darkTheme,
themeMode: MyTheme.initialThemeMode(),
home: DesktopFileTransferScreen(params: argument),
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: supportedLocales,
navigatorObservers: const [
// FirebaseAnalyticsObserver(analytics: analytics),
],
@ -160,6 +173,12 @@ void runPortForwardScreen(Map<String, dynamic> argument) async {
darkTheme: MyTheme.darkTheme,
themeMode: MyTheme.initialThemeMode(),
home: DesktopPortForwardScreen(params: argument),
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: supportedLocales,
navigatorObservers: const [
// FirebaseAnalyticsObserver(analytics: analytics),
],
@ -178,14 +197,20 @@ void runConnectionManagerScreen() async {
theme: MyTheme.lightTheme,
darkTheme: MyTheme.darkTheme,
themeMode: MyTheme.initialThemeMode(),
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: supportedLocales,
home: const DesktopServerPage(),
builder: _keepScaleBuilder()));
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.setAlignment(Alignment.topRight);
await windowManager.show();
await windowManager.focus();
await windowManager.setAlignment(Alignment.topRight); // ensure
});
await windowManager.setAlignment(Alignment.topRight);
await windowManager.show();
await windowManager.focus();
await windowManager.setAlignment(Alignment.topRight); // ensure
});
}
WindowOptions getHiddenTitleBarWindowOptions({Size? size}) {
@ -247,6 +272,12 @@ class _AppState extends State<App> {
navigatorObservers: const [
// FirebaseAnalyticsObserver(analytics: analytics),
],
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: supportedLocales,
builder: isAndroid
? (context, child) => AccessibilityListener(
child: MediaQuery(

View File

@ -45,8 +45,8 @@ class AbModel with ChangeNotifier {
} catch (err) {
abError = err.toString();
} finally {
notifyListeners();
abLoading = false;
notifyListeners();
}
return null;
}
@ -98,12 +98,18 @@ class AbModel with ChangeNotifier {
final body = jsonEncode({
"data": jsonEncode({"tags": tags, "peers": peers})
});
final resp =
await http.post(Uri.parse(api), headers: authHeaders, body: body);
abLoading = false;
// await getAb(); // TODO
try {
final resp =
await http.post(Uri.parse(api), headers: authHeaders, body: body);
abError = "";
await getAb();
debugPrint("resp: ${resp.body}");
} catch (e) {
abError = e.toString();
} finally {
abLoading = false;
}
notifyListeners();
debugPrint("resp: ${resp.body}");
}
bool idContainBy(String id) {

View File

@ -4,7 +4,7 @@ project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "flutter_hbb")
set(BINARY_NAME "rustdesk")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.carriez.flutter_hbb")

View File

@ -51,7 +51,7 @@ static void my_application_activate(GApplication* application) {
// auto bdw = bitsdojo_window_from(window); // <--- add this line
// bdw->setCustomFrame(true); // <-- add this line
gtk_window_set_default_size(window, 1280, 720); // <-- comment this line
gtk_window_set_default_size(window, 800, 600); // <-- comment this line
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();

View File

@ -5,7 +5,7 @@
// 'flutter create' template.
// The application's name. By default this is also the title of the Flutter window.
PRODUCT_NAME = flutter_hbb
PRODUCT_NAME = rustdesk
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb

View File

@ -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:

View File

@ -24,6 +24,8 @@ environment:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
@ -78,6 +80,14 @@ dependencies:
desktop_drop: ^0.3.3
scroll_pos: ^0.3.0
rxdart: ^0.27.5
flutter_improved_scrolling: ^0.0.3
# currently, we use flutter 3.0.5 for windows build, latest for other builds.
#
# for flutter 3.0.5, please use official version(just comment code below).
# if build rustdesk by flutter >=3.3, please use our custom pub below (uncomment code below).
# git:
# url: https://github.com/Kingtous/flutter_improved_scrolling
# ref: 62f09545149f320616467c306c8c5f71714a18e6
dev_dependencies:
icons_launcher: ^2.0.4

View File

@ -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);
});
}

View File

@ -4,7 +4,7 @@ project(flutter_hbb LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "flutter_hbb")
set(BINARY_NAME "rustdesk")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.

View File

@ -52,7 +52,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
Win32Window::Size size(800, 600);
if (!window.CreateAndShow(L"flutter_hbb", origin, size))
{
return EXIT_FAILURE;

View File

@ -189,7 +189,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("x11 expected", "请切换到 x11"),
("Port", "端口"),
("Settings", "设置"),
("Username", " 用户名"),
("Username", "用户名"),
("Invalid port", "无效端口"),
("Closed manually by the peer", "被对方手动关闭"),
("Enable remote configuration modification", "允许远程修改配置"),
@ -272,7 +272,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Overwrite", "覆盖"),
("This file exists, skip or overwrite this file?", "这个文件/文件夹已存在,跳过/覆盖?"),
("Quit", "退出"),
("doc_mac_permission", "https://rustdesk.com/docs/zh-cn/manual/mac/#启用权限"),
("doc_mac_permission", "https://rustdesk.com/docs/zh-cn/manual/mac#%E5%90%AF%E7%94%A8%E6%9D%83%E9%99%90"),
("Help", "帮助"),
("Failed", "失败"),
("Succeeded", "成功"),