Merge branch 'master' into mobile_feat_update_rebase
This commit is contained in:
commit
9284850dff
@ -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
|
||||
|
@ -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") {
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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 © 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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
26
flutter/lib/desktop/widgets/scroll_wrapper.dart
Normal file
26
flutter/lib/desktop/widgets/scroll_wrapper.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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", "成功"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user