Merge branch 'master' into record

This commit is contained in:
RustDesk 2022-09-27 15:32:27 +08:00 committed by GitHub
commit 2481f338b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 1734 additions and 1072 deletions

4
.gitignore vendored
View File

@ -35,4 +35,6 @@ flatpak/ccache/**
flatpak/.flatpak-builder/build/**
flatpak/.flatpak-builder/shared-modules/**
flatpak/.flatpak-builder/shared-modules/*.tar.xz
flatpak/.flatpak-builder/debian-binary
flatpak/.flatpak-builder/debian-binary
# bridge file
lib/generated_bridge.dart

View File

@ -17,6 +17,8 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart';
import 'common/widgets/overlay.dart';
import 'mobile/pages/file_manager_page.dart';
import 'mobile/pages/remote_page.dart';
import 'models/model.dart';
import 'models/platform_model.dart';
@ -76,59 +78,22 @@ class IconFont {
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
const ColorThemeExtension({
required this.bg,
required this.grayBg,
required this.text,
required this.lightText,
required this.lighterText,
required this.placeholder,
required this.border,
});
final Color? bg;
final Color? grayBg;
final Color? text;
final Color? lightText;
final Color? lighterText;
final Color? placeholder;
final Color? border;
static const light = ColorThemeExtension(
bg: Color(0xFFFFFFFF),
grayBg: Color(0xFFEEEEEE),
text: Color(0xFF222222),
lightText: Color(0xFF666666),
lighterText: Color(0xFF888888),
placeholder: Color(0xFFAAAAAA),
border: Color(0xFFCCCCCC),
);
static const dark = ColorThemeExtension(
bg: Color(0xFF252525),
grayBg: Color(0xFF141414),
text: Color(0xFFFFFFFF),
lightText: Color(0xFF999999),
lighterText: Color(0xFF777777),
placeholder: Color(0xFF555555),
border: Color(0xFF555555),
);
@override
ThemeExtension<ColorThemeExtension> copyWith(
{Color? bg,
Color? grayBg,
Color? text,
Color? lightText,
Color? lighterText,
Color? placeholder,
Color? border}) {
ThemeExtension<ColorThemeExtension> copyWith({Color? border}) {
return ColorThemeExtension(
bg: bg ?? this.bg,
grayBg: grayBg ?? this.grayBg,
text: text ?? this.text,
lightText: lightText ?? this.lightText,
lighterText: lighterText ?? this.lighterText,
placeholder: placeholder ?? this.placeholder,
border: border ?? this.border,
);
}
@ -140,12 +105,6 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
return this;
}
return ColorThemeExtension(
bg: Color.lerp(bg, other.bg, t),
grayBg: Color.lerp(grayBg, other.grayBg, t),
text: Color.lerp(text, other.text, t),
lightText: Color.lerp(lightText, other.lightText, t),
lighterText: Color.lerp(lighterText, other.lighterText, t),
placeholder: Color.lerp(placeholder, other.placeholder, t),
border: Color.lerp(border, other.border, t),
);
}
@ -170,6 +129,14 @@ class MyTheme {
static ThemeData lightTheme = ThemeData(
brightness: Brightness.light,
backgroundColor: Color(0xFFFFFFFF),
scaffoldBackgroundColor: Color(0xFFEEEEEE),
textTheme: const TextTheme(
titleLarge: TextStyle(fontSize: 19, color: Colors.black87),
bodySmall: TextStyle(fontSize: 12, color: Colors.black54, height: 1.25),
bodyMedium: TextStyle(fontSize: 14, color: Colors.black54, height: 1.25),
),
hintColor: Color(0xFFAAAAAA),
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: const TabBarTheme(
@ -177,6 +144,12 @@ class MyTheme {
),
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
splashFactory: isDesktop ? NoSplash.splashFactory : null,
textButtonTheme: isDesktop
? TextButtonThemeData(
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
)
: null,
).copyWith(
extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.light,
@ -185,6 +158,13 @@ class MyTheme {
);
static ThemeData darkTheme = ThemeData(
brightness: Brightness.dark,
backgroundColor: Color(0xFF252525),
scaffoldBackgroundColor: Color(0xFF141414),
textTheme: const TextTheme(
titleLarge: TextStyle(fontSize: 19),
bodySmall: TextStyle(fontSize: 12, height: 1.25),
bodyMedium: TextStyle(fontSize: 14, height: 1.25)),
cardColor: Color(0xFF252525),
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: const TabBarTheme(
@ -192,6 +172,12 @@ class MyTheme {
),
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
splashFactory: isDesktop ? NoSplash.splashFactory : null,
textButtonTheme: isDesktop
? TextButtonThemeData(
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
)
: null,
).copyWith(
extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.dark,
@ -1073,14 +1059,38 @@ void connect(BuildContext context, String id,
assert(!(isFileTransfer && isTcpTunneling && isRDP),
"more than one connect type");
FocusScopeNode currentFocus = FocusScope.of(context);
if (isFileTransfer) {
await rustDeskWinManager.newFileTransfer(id);
} else if (isTcpTunneling || isRDP) {
await rustDeskWinManager.newPortForward(id, isRDP);
if (isDesktop) {
if (isFileTransfer) {
await rustDeskWinManager.newFileTransfer(id);
} else if (isTcpTunneling || isRDP) {
await rustDeskWinManager.newPortForward(id, isRDP);
} else {
await rustDeskWinManager.newRemoteDesktop(id);
}
} else {
await rustDeskWinManager.newRemoteDesktop(id);
if (isFileTransfer) {
if (!await PermissionManager.check("file")) {
if (!await PermissionManager.request("file")) {
return;
}
}
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => FileManagerPage(id: id),
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => RemotePage(id: id),
),
);
}
}
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}

View File

@ -1,16 +1,18 @@
import 'package:contextmenu/contextmenu.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/peer_widget.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/models/ab_model.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import '../../common.dart';
import '../../desktop/pages/desktop_home_page.dart';
import '../../mobile/pages/settings_page.dart';
import '../../models/platform_model.dart';
class AddressBook extends StatefulWidget {
const AddressBook({Key? key}) : super(key: key);
final EdgeInsets? menuPadding;
const AddressBook({Key? key, this.menuPadding}) : super(key: key);
@override
State<StatefulWidget> createState() {
@ -37,11 +39,16 @@ class _AddressBookState extends State<AddressBook> {
});
handleLogin() {
loginDialog().then((success) {
if (success) {
setState(() {});
}
});
// TODO refactor login dialog for desktop and mobile
if (isDesktop) {
loginDialog().then((success) {
if (success) {
setState(() {});
}
});
} else {
showLogin(gFFI.dialogManager);
}
}
Future<Widget> buildAddressBook(BuildContext context) async {
@ -108,7 +115,8 @@ class _AddressBookState extends State<AddressBook> {
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: const BorderSide(color: MyTheme.grayBg)),
side: BorderSide(
color: Theme.of(context).scaffoldBackgroundColor)),
child: Container(
width: 200,
height: double.infinity,
@ -174,7 +182,9 @@ class _AddressBookState extends State<AddressBook> {
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: AddressBookPeerWidget()),
child: AddressBookPeersView(
menuPadding: widget.menuPadding,
)),
)
],
));
@ -206,7 +216,8 @@ class _AddressBookState extends State<AddressBook> {
child: Text(
tagName,
style: TextStyle(
color: rxTags.contains(tagName) ? MyTheme.white : null),
color:
rxTags.contains(tagName) ? Colors.white : null), // TODO
),
),
),

View File

@ -72,3 +72,86 @@ void changeIdDialog() {
);
});
}
void changeWhiteList({Function()? callback}) async {
var newWhiteList = (await bind.mainGetOption(key: 'whitelist')).split(',');
var newWhiteListField = newWhiteList.join('\n');
var controller = TextEditingController(text: newWhiteListField);
var msg = "";
var isInProgress = false;
gFFI.dialogManager.show((setState, close) {
return CustomAlertDialog(
title: Text(translate("IP Whitelisting")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("whitelist_sep")),
const SizedBox(
height: 8.0,
),
Row(
children: [
Expanded(
child: TextField(
maxLines: null,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
),
controller: controller,
focusNode: FocusNode()..requestFocus()),
),
],
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
await bind.mainSetOption(key: 'whitelist', value: '');
callback?.call();
close();
},
child: Text(translate("Clear"))),
TextButton(
onPressed: () async {
setState(() {
msg = "";
isInProgress = true;
});
newWhiteListField = controller.text.trim();
var newWhiteList = "";
if (newWhiteListField.isEmpty) {
// pass
} else {
final ips =
newWhiteListField.trim().split(RegExp(r"[\s,;\n]+"));
// test ip
final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$");
for (final ip in ips) {
if (!ipMatch.hasMatch(ip)) {
msg = "${translate("Invalid IP")} $ip";
setState(() {
isInProgress = false;
});
return;
}
}
newWhiteList = ips.join(',');
}
await bind.mainSetOption(key: 'whitelist', value: newWhiteList);
callback?.call();
close();
},
child: Text(translate("OK"))),
],
onCancel: close,
);
});
}

View File

@ -26,7 +26,7 @@ class DraggableChatWindow extends StatelessWidget {
position: position,
width: width,
height: height,
builder: (_, onPanUpdate) {
builder: (context, onPanUpdate) {
return isIOS
? ChatPage(chatModel: chatModel)
: Scaffold(
@ -35,16 +35,16 @@ class DraggableChatWindow extends StatelessWidget {
onPanUpdate: onPanUpdate,
appBar: isDesktop
? _buildDesktopAppBar()
: _buildMobileAppBar(),
: _buildMobileAppBar(context),
),
body: ChatPage(chatModel: chatModel),
);
});
}
Widget _buildMobileAppBar() {
Widget _buildMobileAppBar(BuildContext context) {
return Container(
color: MyTheme.accent50,
color: Theme.of(context).colorScheme.primary,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -169,17 +169,17 @@ class DraggableMobileActions extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
color: MyTheme.white,
color: Colors.white,
onPressed: onBackPressed,
splashRadius: 20,
icon: const Icon(Icons.arrow_back)),
IconButton(
color: MyTheme.white,
color: Colors.white,
onPressed: onHomePressed,
splashRadius: 20,
icon: const Icon(Icons.home)),
IconButton(
color: MyTheme.white,
color: Colors.white,
onPressed: onRecentPressed,
splashRadius: 20,
icon: const Icon(Icons.more_horiz)),
@ -190,7 +190,7 @@ class DraggableMobileActions extends StatelessWidget {
endIndent: 10,
),
IconButton(
color: MyTheme.white,
color: Colors.white,
onPressed: onHidePressed,
splashRadius: 20,
icon: const Icon(Icons.keyboard_arrow_down)),

View File

@ -1,6 +1,7 @@
import 'package:contextmenu/contextmenu.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:get/get.dart';
import '../../common.dart';
@ -14,7 +15,7 @@ import '../../desktop/widgets/popup_menu.dart';
class _PopupMenuTheme {
static const Color commonColor = MyTheme.accent;
// kMinInteractiveDimension
static const double height = 25.0;
static const double height = 20.0;
static const double dividerHeight = 3.0;
}
@ -73,12 +74,18 @@ class _PeerCardState extends State<_PeerCard>
_showPeerMenu(peer.id);
},
child: ListTile(
contentPadding: const EdgeInsets.only(left: 12),
contentPadding: const EdgeInsets.only(left: 12), //
subtitle: Text('${peer.username}@${peer.hostname}'),
title: Text(peer.alias.isEmpty ? formatID(peer.id) : peer.alias),
title: Row(children: [
getOnline(4, peer.online),
Text(peer.alias.isEmpty ? formatID(peer.id) : peer.alias)
]),
leading: Container(
decoration: BoxDecoration(
color: str2color('${peer.id}${peer.platform}', 0x7f),
borderRadius: BorderRadius.circular(4),
),
padding: const EdgeInsets.all(6),
color: str2color('${peer.id}${peer.platform}', 0x7f),
child: getPlatformImage(peer.platform)),
trailing: InkWell(
child: const Padding(
@ -105,7 +112,9 @@ class _PeerCardState extends State<_PeerCard>
return MouseRegion(
onEnter: (evt) {
deco.value = BoxDecoration(
border: Border.all(color: MyTheme.button, width: _borderWidth),
border: Border.all(
color: Theme.of(context).colorScheme.secondary,
width: _borderWidth),
borderRadius: peerCardUiType.value == PeerUiType.grid
? BorderRadius.circular(_cardRadis)
: null);
@ -127,8 +136,10 @@ class _PeerCardState extends State<_PeerCard>
Widget _buildPeerTile(
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
final greyStyle =
TextStyle(fontSize: 12, color: MyTheme.color(context).lighterText);
final greyStyle = TextStyle(
fontSize: 11,
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
final alias = bind.mainGetPeerOptionSync(id: peer.id, key: 'alias');
return Obx(
() => Container(
foregroundDecoration: deco.value,
@ -144,66 +155,36 @@ class _PeerCardState extends State<_PeerCard>
),
Expanded(
child: Container(
decoration: BoxDecoration(color: MyTheme.color(context).bg),
decoration:
BoxDecoration(color: Theme.of(context).backgroundColor),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(children: [
Padding(
padding: const EdgeInsets.fromLTRB(0, 4, 4, 4),
child: CircleAvatar(
radius: 5,
backgroundColor: peer.online
? Colors.green
: Colors.yellow)),
Text(
formatID(peer.id),
style:
const TextStyle(fontWeight: FontWeight.w400),
),
]),
getOnline(8, peer.online),
Expanded(
child: Text(
alias.isEmpty ? formatID(peer.id) : alias,
overflow: TextOverflow.ellipsis,
)),
]).marginOnly(bottom: 2),
Align(
alignment: Alignment.centerLeft,
child: FutureBuilder<String>(
future: bind.mainGetPeerOption(
id: peer.id, key: 'alias'),
builder: (_, snapshot) {
if (snapshot.hasData) {
final name = snapshot.data!.isEmpty
? '${peer.username}@${peer.hostname}'
: snapshot.data!;
return Tooltip(
message: name,
waitDuration: const Duration(seconds: 1),
child: Text(
name,
style: greyStyle,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
),
);
} else {
// alias has not arrived
return Text(
'${peer.username}@${peer.hostname}',
style: greyStyle,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
);
}
},
child: Text(
'${peer.username}@${peer.hostname}',
style: greyStyle,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
),
),
],
),
).marginOnly(top: 2),
),
_actionMore(peer),
],
).paddingSymmetric(horizontal: 4.0),
).paddingOnly(left: 10.0, top: 3.0),
),
)
],
@ -268,21 +249,19 @@ class _PeerCardState extends State<_PeerCard>
),
),
Container(
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(children: [
Padding(
padding: const EdgeInsets.fromLTRB(0, 4, 8, 4),
child: CircleAvatar(
radius: 5,
backgroundColor: peer.online
? Colors.green
: Colors.yellow)),
Text(
peer.alias.isEmpty ? formatID(peer.id) : peer.alias)
]).paddingSymmetric(vertical: 8),
Expanded(
child: Row(children: [
getOnline(8, peer.online),
Expanded(
child: Text(
peer.alias.isEmpty ? formatID(peer.id) : peer.alias,
overflow: TextOverflow.ellipsis,
)),
]).paddingSymmetric(vertical: 8)),
_actionMore(peer),
],
).paddingSymmetric(horizontal: 12.0),
@ -308,13 +287,21 @@ class _PeerCardState extends State<_PeerCard>
child: CircleAvatar(
radius: 14,
backgroundColor: _iconMoreHover.value
? MyTheme.color(context).grayBg!
: MyTheme.color(context).bg!,
? Theme.of(context).scaffoldBackgroundColor
: Theme.of(context).backgroundColor,
// ? Theme.of(context).scaffoldBackgroundColor!
// : Theme.of(context).backgroundColor!,
child: Icon(Icons.more_vert,
size: 18,
color: _iconMoreHover.value
? MyTheme.color(context).text
: MyTheme.color(context).lightText))));
? Theme.of(context).textTheme.titleLarge?.color
: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5)))));
// ? MyTheme.color(context).text
// : MyTheme.color(context).lightText))));
/// Show the peer menu and handle user's choice.
/// User might remove the peer or send a file to the peer.
@ -333,8 +320,10 @@ class _PeerCardState extends State<_PeerCard>
abstract class BasePeerCard extends StatelessWidget {
final Peer peer;
final EdgeInsets? menuPadding;
BasePeerCard({required this.peer, Key? key}) : super(key: key);
BasePeerCard({required this.peer, this.menuPadding, Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
@ -379,6 +368,7 @@ abstract class BasePeerCard extends StatelessWidget {
isRDP: isRDP,
);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@ -428,17 +418,25 @@ abstract class BasePeerCard extends StatelessWidget {
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.edit),
onPressed: () => _rdpDialog(id),
),
child: Transform.scale(
scale: 0.8,
child: IconButton(
icon: const Icon(Icons.edit),
padding: EdgeInsets.zero,
onPressed: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
_rdpDialog(id);
},
)),
))
],
)),
proc: () {
connect(context, id, isRDP: true);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@ -453,6 +451,7 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () {
bind.mainWol(id: id);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@ -461,6 +460,7 @@ abstract class BasePeerCard extends StatelessWidget {
Future<MenuEntryBase<String>> _forceAlwaysRelayAction(String id) async {
const option = 'force-always-relay';
return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox,
text: translate('Always connect via relay'),
getter: () async {
return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty;
@ -475,6 +475,7 @@ abstract class BasePeerCard extends StatelessWidget {
}
await bind.mainSetPeerOption(id: id, key: option, value: value);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@ -489,13 +490,15 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () {
_rename(id, isAddressBook);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@protected
MenuEntryBase<String> _removeAction(
String id, Future<void> Function() reloadFunc) {
String id, Future<void> Function() reloadFunc,
{bool isLan = false}) {
return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Remove'),
@ -503,12 +506,16 @@ abstract class BasePeerCard extends StatelessWidget {
),
proc: () {
() async {
await bind.mainRemovePeer(id: id);
if (isLan) {
// TODO
} else {
await bind.mainRemovePeer(id: id);
}
removePreference(id);
await reloadFunc();
// Get.forceAppUpdate(); // TODO use inner model / state
}();
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@ -523,6 +530,7 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () {
bind.mainForgetPassword(id: id);
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@ -543,6 +551,7 @@ abstract class BasePeerCard extends StatelessWidget {
}
}();
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@ -561,10 +570,10 @@ abstract class BasePeerCard extends StatelessWidget {
if (favs.remove(id)) {
await bind.mainStoreFav(favs: favs);
await reloadFunc();
// Get.forceAppUpdate(); // TODO use inner model / state
}
}();
},
padding: menuPadding,
dismissOnClicked: true,
);
}
@ -606,8 +615,6 @@ abstract class BasePeerCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Form(
child: TextFormField(
controller: controller,
@ -634,7 +641,8 @@ abstract class BasePeerCard extends StatelessWidget {
}
class RecentPeerCard extends BasePeerCard {
RecentPeerCard({required Peer peer, Key? key}) : super(peer: peer, key: key);
RecentPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key);
@override
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -642,15 +650,13 @@ class RecentPeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer),
_transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
];
MenuEntryBase<String>? rdpAction;
if (peer.platform == 'Windows') {
rdpAction = _rdpAction(context, peer.id);
if (isDesktop) {
menuItems.add(_tcpTunnelingAction(context, peer.id));
}
menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) {
menuItems.add(rdpAction);
if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider());
@ -658,15 +664,17 @@ class RecentPeerCard extends BasePeerCard {
menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadRecentPeers();
}));
menuItems.add(_unrememberPasswordAction(peer.id));
if (await bind.mainPeerHasPassword(id: peer.id)) {
menuItems.add(_unrememberPasswordAction(peer.id));
}
menuItems.add(_addFavAction(peer.id));
return menuItems;
}
}
class FavoritePeerCard extends BasePeerCard {
FavoritePeerCard({required Peer peer, Key? key})
: super(peer: peer, key: key);
FavoritePeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key);
@override
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -674,15 +682,13 @@ class FavoritePeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer),
_transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
];
MenuEntryBase<String>? rdpAction;
if (peer.platform == 'Windows') {
rdpAction = _rdpAction(context, peer.id);
if (isDesktop) {
menuItems.add(_tcpTunnelingAction(context, peer.id));
}
menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) {
menuItems.add(rdpAction);
if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider());
@ -690,7 +696,9 @@ class FavoritePeerCard extends BasePeerCard {
menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadFavPeers();
}));
menuItems.add(_unrememberPasswordAction(peer.id));
if (await bind.mainPeerHasPassword(id: peer.id)) {
menuItems.add(_unrememberPasswordAction(peer.id));
}
menuItems.add(_rmFavAction(peer.id, () async {
await bind.mainLoadFavPeers();
}));
@ -699,8 +707,8 @@ class FavoritePeerCard extends BasePeerCard {
}
class DiscoveredPeerCard extends BasePeerCard {
DiscoveredPeerCard({required Peer peer, Key? key})
: super(peer: peer, key: key);
DiscoveredPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key);
@override
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -708,30 +716,24 @@ class DiscoveredPeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer),
_transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
];
MenuEntryBase<String>? rdpAction;
if (peer.platform == 'Windows') {
rdpAction = _rdpAction(context, peer.id);
if (isDesktop) {
menuItems.add(_tcpTunnelingAction(context, peer.id));
}
menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) {
menuItems.add(rdpAction);
if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false));
menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadLanPeers();
}));
menuItems.add(_unrememberPasswordAction(peer.id));
menuItems.add(_removeAction(peer.id, () async {}));
return menuItems;
}
}
class AddressBookPeerCard extends BasePeerCard {
AddressBookPeerCard({required Peer peer, Key? key})
: super(peer: peer, key: key);
AddressBookPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key);
@override
Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -739,22 +741,21 @@ class AddressBookPeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer),
_transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
];
MenuEntryBase<String>? rdpAction;
if (peer.platform == 'Windows') {
rdpAction = _rdpAction(context, peer.id);
if (isDesktop) {
menuItems.add(_tcpTunnelingAction(context, peer.id));
}
menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) {
menuItems.add(rdpAction);
if (peer.platform == 'Windows') {
menuItems.add(_rdpAction(context, peer.id));
}
menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false));
menuItems.add(_removeAction(peer.id, () async {}));
menuItems.add(_unrememberPasswordAction(peer.id));
menuItems.add(_addFavAction(peer.id));
if (await bind.mainPeerHasPassword(id: peer.id)) {
menuItems.add(_unrememberPasswordAction(peer.id));
}
menuItems.add(_editTagAction(peer.id));
return menuItems;
}
@ -762,7 +763,8 @@ class AddressBookPeerCard extends BasePeerCard {
@protected
@override
MenuEntryBase<String> _removeAction(
String id, Future<void> Function() reloadFunc) {
String id, Future<void> Function() reloadFunc,
{bool isLan = false}) {
return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text(
translate('Remove'),
@ -774,6 +776,7 @@ class AddressBookPeerCard extends BasePeerCard {
await gFFI.abModel.updateAb();
}();
},
padding: super.menuPadding,
dismissOnClicked: true,
);
}
@ -788,6 +791,7 @@ class AddressBookPeerCard extends BasePeerCard {
proc: () {
_abEditTag(id);
},
padding: super.menuPadding,
dismissOnClicked: true,
);
}
@ -869,7 +873,7 @@ class AddressBookPeerCard extends BasePeerCard {
child: Text(
tagName,
style: TextStyle(
color: rxTags.contains(tagName) ? MyTheme.white : null),
color: rxTags.contains(tagName) ? Colors.white : null),
),
),
),
@ -995,3 +999,13 @@ void _rdpDialog(String id) async {
);
});
}
Widget getOnline(double rightPadding, bool online) {
return Tooltip(
message: translate(online ? 'Online' : 'Offline'),
waitDuration: const Duration(seconds: 1),
child: Padding(
padding: EdgeInsets.fromLTRB(0, 4, rightPadding, 4),
child: CircleAvatar(
radius: 3, backgroundColor: online ? Colors.green : kColorWarn)));
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/peer_widget.dart';
import 'package:flutter_hbb/common/widgets/peercard_widget.dart';
import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/common/widgets/peer_card.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:get/get.dart';
@ -42,9 +42,6 @@ class _PeerTabPageState extends State<PeerTabPage>
// hard code for now
Future<void> _handleTabSelection(int index) async {
// reset search text
peerSearchText.value = "";
peerSearchTextController.clear();
_tabIndex.value = index;
await bind.mainSetLocalOption(
key: 'peer-tab-index', value: index.toString());
@ -101,6 +98,7 @@ class _PeerTabPageState extends State<PeerTabPage>
}
Widget _createTabBar(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return ListView(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
@ -111,9 +109,9 @@ class _PeerTabPageState extends State<PeerTabPage>
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: _tabIndex.value == t.key
? MyTheme.color(context).bg
? Theme.of(context).backgroundColor
: null,
borderRadius: BorderRadius.circular(2),
borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
),
child: Align(
alignment: Alignment.center,
@ -123,9 +121,9 @@ class _PeerTabPageState extends State<PeerTabPage>
style: TextStyle(
height: 1,
fontSize: 14,
color: _tabIndex.value == t.key
? MyTheme.color(context).text
: MyTheme.color(context).lightText),
color:
_tabIndex.value == t.key ? textColor : textColor
?..withOpacity(0.5)),
),
)),
onTap: () async => await _handleTabSelection(t.key),
@ -147,7 +145,8 @@ class _PeerTabPageState extends State<PeerTabPage>
}
Widget _createPeerViewTypeSwitch(BuildContext context) {
final activeDeco = BoxDecoration(color: MyTheme.color(context).bg);
final textColor = Theme.of(context).textTheme.titleLarge?.color;
final activeDeco = BoxDecoration(color: Theme.of(context).backgroundColor);
return Row(
children: [PeerUiType.grid, PeerUiType.list]
.map((type) => Obx(
@ -166,9 +165,9 @@ class _PeerTabPageState extends State<PeerTabPage>
? Icons.grid_view_rounded
: Icons.list,
size: 18,
color: peerCardUiType.value == type
? MyTheme.color(context).text
: MyTheme.color(context).lightText,
color:
peerCardUiType.value == type ? textColor : textColor
?..withOpacity(0.5),
)),
),
))
@ -199,9 +198,9 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
drawer = true;
});
},
icon: const Icon(
icon: Icon(
Icons.search_rounded,
color: MyTheme.dark,
color: Theme.of(context).hintColor,
));
}
@ -212,7 +211,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
return Container(
width: 120,
decoration: BoxDecoration(
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(6),
),
child: Obx(() => Row(
@ -222,7 +221,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
children: [
Icon(
Icons.search_rounded,
color: MyTheme.color(context).placeholder,
color: Theme.of(context).hintColor,
).marginSymmetric(horizontal: 4),
Expanded(
child: TextField(
@ -234,7 +233,11 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
focusNode: focusNode,
textAlign: TextAlign.start,
maxLines: 1,
cursorColor: MyTheme.color(context).lightText,
cursorColor: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5),
cursorHeight: 18,
cursorWidth: 1,
style: const TextStyle(fontSize: 14),
@ -244,8 +247,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
hintText:
focused.value ? null : translate("Search ID"),
hintStyle: TextStyle(
fontSize: 14,
color: MyTheme.color(context).placeholder),
fontSize: 14, color: Theme.of(context).hintColor),
border: InputBorder.none,
isDense: true,
),
@ -262,9 +264,9 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
drawer = false;
});
},
icon: const Icon(
icon: Icon(
Icons.close,
color: MyTheme.dark,
color: Theme.of(context).hintColor,
)),
],
),

View File

@ -11,34 +11,34 @@ import 'package:window_manager/window_manager.dart';
import '../../common.dart';
import '../../models/peer_model.dart';
import '../../models/platform_model.dart';
import 'peercard_widget.dart';
import 'peer_card.dart';
typedef OffstageFunc = bool Function(Peer peer);
typedef PeerCardWidgetFunc = Widget Function(Peer peer);
typedef PeerCardBuilder = BasePeerCard Function(Peer peer);
/// for peer search text, global obs value
final peerSearchText = "".obs;
final peerSearchTextController =
TextEditingController(text: peerSearchText.value);
class _PeerWidget extends StatefulWidget {
class _PeersView extends StatefulWidget {
final Peers peers;
final OffstageFunc offstageFunc;
final PeerCardWidgetFunc peerCardWidgetFunc;
final PeerCardBuilder peerCardBuilder;
const _PeerWidget(
const _PeersView(
{required this.peers,
required this.offstageFunc,
required this.peerCardWidgetFunc,
required this.peerCardBuilder,
Key? key})
: super(key: key);
@override
_PeerWidgetState createState() => _PeerWidgetState();
_PeersViewState createState() => _PeersViewState();
}
/// State for the peer widget.
class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
class _PeersViewState extends State<_PeersView> with WindowListener {
static const int _maxQueryCount = 3;
final space = isDesktop ? 12.0 : 8.0;
final _curPeers = <String>{};
@ -60,7 +60,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
return width;
}();
_PeerWidgetState() {
_PeersViewState() {
_startCheckOnlines();
}
@ -119,7 +119,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
}
_lastChangeTime = DateTime.now();
},
child: widget.peerCardWidgetFunc(peer),
child: widget.peerCardBuilder(peer),
);
cards.add(Offstage(
key: ValueKey("off${peer.id}"),
@ -198,40 +198,41 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
}
}
abstract class BasePeerWidget extends StatelessWidget {
abstract class BasePeersView extends StatelessWidget {
final String name;
final String loadEvent;
final OffstageFunc offstageFunc;
final PeerCardWidgetFunc peerCardWidgetFunc;
final PeerCardBuilder peerCardBuilder;
final List<Peer> initPeers;
const BasePeerWidget({
const BasePeersView({
Key? key,
required this.name,
required this.loadEvent,
required this.offstageFunc,
required this.peerCardWidgetFunc,
required this.peerCardBuilder,
required this.initPeers,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return _PeerWidget(
return _PeersView(
peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers),
offstageFunc: offstageFunc,
peerCardWidgetFunc: peerCardWidgetFunc);
peerCardBuilder: peerCardBuilder);
}
}
class RecentPeerWidget extends BasePeerWidget {
RecentPeerWidget({Key? key})
class RecentPeersView extends BasePeersView {
RecentPeersView({Key? key, EdgeInsets? menuPadding})
: super(
key: key,
name: 'recent peer',
loadEvent: 'load_recent_peers',
offstageFunc: (Peer peer) => false,
peerCardWidgetFunc: (Peer peer) => RecentPeerCard(
peerCardBuilder: (Peer peer) => RecentPeerCard(
peer: peer,
menuPadding: menuPadding,
),
initPeers: [],
);
@ -244,15 +245,16 @@ class RecentPeerWidget extends BasePeerWidget {
}
}
class FavoritePeerWidget extends BasePeerWidget {
FavoritePeerWidget({Key? key})
class FavoritePeersView extends BasePeersView {
FavoritePeersView({Key? key, EdgeInsets? menuPadding})
: super(
key: key,
name: 'favorite peer',
loadEvent: 'load_fav_peers',
offstageFunc: (Peer peer) => false,
peerCardWidgetFunc: (Peer peer) => FavoritePeerCard(
peerCardBuilder: (Peer peer) => FavoritePeerCard(
peer: peer,
menuPadding: menuPadding,
),
initPeers: [],
);
@ -265,15 +267,16 @@ class FavoritePeerWidget extends BasePeerWidget {
}
}
class DiscoveredPeerWidget extends BasePeerWidget {
DiscoveredPeerWidget({Key? key})
class DiscoveredPeersView extends BasePeersView {
DiscoveredPeersView({Key? key, EdgeInsets? menuPadding})
: super(
key: key,
name: 'discovered peer',
loadEvent: 'load_lan_peers',
offstageFunc: (Peer peer) => false,
peerCardWidgetFunc: (Peer peer) => DiscoveredPeerCard(
peerCardBuilder: (Peer peer) => DiscoveredPeerCard(
peer: peer,
menuPadding: menuPadding,
),
initPeers: [],
);
@ -286,16 +289,17 @@ class DiscoveredPeerWidget extends BasePeerWidget {
}
}
class AddressBookPeerWidget extends BasePeerWidget {
AddressBookPeerWidget({Key? key})
class AddressBookPeersView extends BasePeersView {
AddressBookPeersView({Key? key, EdgeInsets? menuPadding})
: super(
key: key,
name: 'address book peer',
loadEvent: 'load_address_book_peers',
offstageFunc: (Peer peer) =>
!_hitTag(gFFI.abModel.selectedTags, peer.tags),
peerCardWidgetFunc: (Peer peer) => AddressBookPeerCard(
peerCardBuilder: (Peer peer) => AddressBookPeerCard(
peer: peer,
menuPadding: menuPadding,
),
initPeers: _loadPeers(),
);

View File

@ -11,12 +11,16 @@ const String kAppTypeDesktopPortForward = "port forward";
const String kTabLabelHomePage = "Home";
const String kTabLabelSettingPage = "Settings";
const Color kColorWarn = Color.fromARGB(255, 245, 133, 59);
const int kMobileDefaultDisplayWidth = 720;
const int kMobileDefaultDisplayHeight = 1280;
const int kDesktopDefaultDisplayWidth = 1080;
const int kDesktopDefaultDisplayHeight = 720;
const Size kConnectionManagerWindowSize = Size(300, 400);
/// [kDefaultScrollAmountMultiplier] indicates how many rows can be scrolled after a minimum scroll action of mouse
const kDefaultScrollAmountMultiplier = 5.0;
const kDefaultScrollDuration = Duration(milliseconds: 50);

View File

@ -5,14 +5,16 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/widgets/address_book.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:get/get.dart';
import 'package:url_launcher/url_launcher_string.dart';
import '../../common.dart';
import '../../common/formatter/id_formatter.dart';
import '../../common/widgets/peer_tab_page.dart';
import '../../common/widgets/peer_widget.dart';
import '../../common/widgets/peers_view.dart';
import '../../models/platform_model.dart';
import '../widgets/button.dart';
/// Connection page for connecting to a remote peer.
class ConnectionPage extends StatefulWidget {
@ -74,10 +76,18 @@ class _ConnectionPageState extends State<ConnectionPage> {
translate('Address Book')
],
children: [
RecentPeerWidget(),
FavoritePeerWidget(),
DiscoveredPeerWidget(),
const AddressBook(),
RecentPeersView(
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
),
FavoritePeersView(
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
),
DiscoveredPeersView(
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
),
const AddressBook(
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
),
],
)),
],
@ -100,10 +110,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
/// UI for the remote ID TextField.
/// Search for a peer and connect to it if the id exists.
Widget _buildRemoteIDTextField(BuildContext context) {
RxBool ftHover = false.obs;
RxBool ftPressed = false.obs;
RxBool connHover = false.obs;
RxBool connPressed = false.obs;
RxBool inputFocused = false.obs;
FocusNode focusNode = FocusNode();
focusNode.addListener(() {
@ -113,7 +119,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
width: 320 + 20 * 2,
padding: const EdgeInsets.fromLTRB(20, 24, 20, 22),
decoration: BoxDecoration(
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
borderRadius: const BorderRadius.all(Radius.circular(13)),
),
child: Ink(
@ -123,7 +129,10 @@ class _ConnectionPageState extends State<ConnectionPage> {
children: [
Text(
translate('Control Remote Desktop'),
style: const TextStyle(fontSize: 19, height: 1),
style: Theme.of(context)
.textTheme
.titleLarge
?.merge(TextStyle(height: 1)),
),
],
).marginOnly(bottom: 15),
@ -142,13 +151,12 @@ class _ConnectionPageState extends State<ConnectionPage> {
height: 1,
),
maxLines: 1,
cursorColor: MyTheme.color(context).text!,
cursorColor:
Theme.of(context).textTheme.titleLarge?.color,
decoration: InputDecoration(
hintText: inputFocused.value
? null
: translate('Enter Remote ID'),
hintStyle: TextStyle(
color: MyTheme.color(context).placeholder),
border: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderSide: BorderSide(
@ -180,84 +188,17 @@ class _ConnectionPageState extends State<ConnectionPage> {
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Obx(() => InkWell(
onTapDown: (_) => ftPressed.value = true,
onTapUp: (_) => ftPressed.value = false,
onTapCancel: () => ftPressed.value = false,
onHover: (value) => ftHover.value = value,
onTap: () {
onConnect(isFileTransfer: true);
},
child: Container(
height: 27,
alignment: Alignment.center,
decoration: BoxDecoration(
color: ftPressed.value
? MyTheme.accent
: Colors.transparent,
border: Border.all(
color: ftPressed.value
? MyTheme.accent
: ftHover.value
? MyTheme.hoverBorder
: MyTheme.border,
),
borderRadius: BorderRadius.circular(5),
),
child: Text(
translate(
"Transfer File",
),
style: TextStyle(
fontSize: 12,
color: ftPressed.value
? MyTheme.color(context).bg
: MyTheme.color(context).text),
).marginSymmetric(horizontal: 12),
),
)),
Button(
isOutline: true,
onTap: () {
onConnect(isFileTransfer: true);
},
text: "Transfer File",
),
const SizedBox(
width: 17,
),
Obx(
() => InkWell(
onTapDown: (_) => connPressed.value = true,
onTapUp: (_) => connPressed.value = false,
onTapCancel: () => connPressed.value = false,
onHover: (value) => connHover.value = value,
onTap: onConnect,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 80.0,
),
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),
),
child: Center(
child: Text(
translate(
"Connect",
),
style: TextStyle(
fontSize: 12,
color: MyTheme.color(context).bg),
),
).marginSymmetric(horizontal: 12),
)),
),
),
Button(onTap: onConnect, text: "Connect"),
],
),
)
@ -289,7 +230,11 @@ class _ConnectionPageState extends State<ConnectionPage> {
width: 8,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: svcStopped.value ? Colors.redAccent : Colors.green,
color: svcStopped.value || svcStatusCode.value == 0
? kColorWarn
: (svcStatusCode.value == 1
? Color.fromARGB(255, 50, 190, 166)
: Color.fromARGB(255, 224, 79, 95)),
),
).paddingSymmetric(horizontal: 12.0);
if (svcStopped.value) {

View File

@ -5,6 +5,8 @@ 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/pages/desktop_tab_page.dart';
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';
@ -12,6 +14,9 @@ import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart';
import 'package:url_launcher/url_launcher.dart';
import '../widgets/button.dart';
class DesktopHomePage extends StatefulWidget {
const DesktopHomePage({Key? key}) : super(key: key);
@ -26,6 +31,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
with TrayListener, WindowListener, AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
var updateUrl = '';
@override
void onWindowClose() async {
@ -68,12 +74,13 @@ class _DesktopHomePageState extends State<DesktopHomePage>
value: gFFI.serverModel,
child: Container(
width: 200,
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
child: Column(
children: [
buildTip(context),
buildIDBoard(context),
buildPasswordBoard(context),
buildHelpCards(),
],
),
),
@ -82,7 +89,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
buildRightPane(BuildContext context) {
return Container(
color: MyTheme.color(context).grayBg,
color: Theme.of(context).scaffoldBackgroundColor,
child: ConnectionPage(),
);
}
@ -116,7 +123,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
translate("ID"),
style: TextStyle(
fontSize: 14,
color: MyTheme.color(context).lightText),
color: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5)),
).marginOnly(top: 5),
buildPopupMenu(context)
],
@ -152,21 +163,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}
Widget buildPopupMenu(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
RxBool hover = false.obs;
return InkWell(
onTap: () async {},
onTap: DesktopTabPage.onAddSetting,
child: Obx(
() => CircleAvatar(
radius: 15,
backgroundColor: hover.value
? MyTheme.color(context).grayBg!
: MyTheme.color(context).bg!,
? Theme.of(context).scaffoldBackgroundColor
: Theme.of(context).backgroundColor,
child: Icon(
Icons.more_vert_outlined,
size: 20,
color: hover.value
? MyTheme.color(context).text
: MyTheme.color(context).lightText,
color: hover.value ? textColor : textColor?.withOpacity(0.5),
),
),
),
@ -178,6 +188,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
final model = gFFI.serverModel;
RxBool refreshHover = false.obs;
RxBool editHover = false.obs;
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return Container(
margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13),
child: Row(
@ -198,7 +209,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Text(
translate("Password"),
style: TextStyle(
fontSize: 14, color: MyTheme.color(context).lightText),
fontSize: 14, color: textColor?.withOpacity(0.5)),
),
Row(
children: [
@ -228,8 +239,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
() => Icon(
Icons.refresh,
color: refreshHover.value
? MyTheme.color(context).text
: Color(0xFFDDDDDD),
? textColor
: Color(0xFFDDDDDD), // TODO
size: 22,
).marginOnly(right: 8, bottom: 2),
),
@ -241,12 +252,12 @@ class _DesktopHomePageState extends State<DesktopHomePage>
() => Icon(
Icons.edit,
color: editHover.value
? MyTheme.color(context).text
: Color(0xFFDDDDDD),
? textColor
: Color(0xFFDDDDDD), // TODO
size: 22,
).marginOnly(right: 8, bottom: 2),
),
onTap: () => {},
onTap: () => DesktopSettingPage.switch2page(1),
onHover: (value) => editHover.value = value,
),
],
@ -270,7 +281,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
children: [
Text(
translate("Your Desktop"),
style: TextStyle(fontWeight: FontWeight.normal, fontSize: 19),
style: Theme.of(context).textTheme.titleLarge,
// style: TextStyle(
// // color: MyTheme.color(context).text,
// fontWeight: FontWeight.normal,
// fontSize: 19),
),
SizedBox(
height: 10.0,
@ -278,16 +293,93 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Text(
translate("desk_tip"),
overflow: TextOverflow.clip,
style: TextStyle(
fontSize: 12,
color: MyTheme.color(context).lighterText,
height: 1.25),
style: Theme.of(context).textTheme.bodySmall,
)
],
),
);
}
Widget buildHelpCards() {
if (Platform.isWindows) {
if (!bind.mainIsInstalled()) {
return buildInstallCard(
"", "install_tip", "Install", bind.mainGotoInstall);
} else if (bind.mainIsInstalledLowerVersion()) {
return buildInstallCard("Status", "Your installation is lower version.",
"Click to upgrade", bind.mainUpdateMe);
}
}
if (updateUrl.isNotEmpty) {
return buildInstallCard(
"Status",
"There is a newer version of ${bind.mainGetAppNameSync()} ${bind.mainGetNewVersion()} available.",
"Click to download", () async {
final Uri url = Uri.parse('https://rustdesk.com');
await launchUrl(url);
});
}
if (Platform.isMacOS) {}
if (bind.mainIsInstalledLowerVersion()) {}
return Container();
}
Widget buildInstallCard(String title, String content, String btnText,
GestureTapCallback onPressed) {
return Container(
margin: EdgeInsets.only(top: 20),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Color.fromARGB(255, 226, 66, 188),
Color.fromARGB(255, 244, 114, 124),
],
)),
padding: EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: (title.isNotEmpty
? <Widget>[
Center(
child: Text(
translate(title),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15),
).marginOnly(bottom: 6)),
]
: <Widget>[]) +
<Widget>[
Text(
translate(content),
style: TextStyle(
height: 1.5,
color: Colors.white,
fontWeight: FontWeight.normal,
fontSize: 13),
).marginOnly(bottom: 20),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Button(
padding: 8,
isOutline: true,
text: translate(btnText),
textColor: Colors.white,
borderColor: Colors.white,
textSize: 20,
radius: 10,
onTap: onPressed,
)
]),
],
)),
);
}
@override
void onTrayMenuItemClick(MenuItem menuItem) {
debugPrint('click ${menuItem.key}');
@ -305,6 +397,10 @@ class _DesktopHomePageState extends State<DesktopHomePage>
@override
void initState() {
super.initState();
Timer(const Duration(seconds: 5), () async {
updateUrl = await bind.mainGetSoftwareUpdateUrl();
if (updateUrl.isNotEmpty) setState(() {});
});
trayManager.addListener(this);
windowManager.addListener(this);
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
@ -331,7 +427,7 @@ Future<bool> loginDialog() async {
var userNameMsg = "";
String pass = "";
var passMsg = "";
var userContontroller = TextEditingController(text: userName);
var userController = TextEditingController(text: userName);
var pwdController = TextEditingController(text: pass);
var isInProgress = false;
@ -349,7 +445,7 @@ Future<bool> loginDialog() async {
});
}
userName = userContontroller.text;
userName = userController.text;
pass = pwdController.text;
if (userName.isEmpty) {
userNameMsg = translate("Username missed");
@ -385,6 +481,7 @@ Future<bool> loginDialog() async {
close();
}
// dialog
return CustomAlertDialog(
title: Text(translate("Login")),
content: ConstrainedBox(
@ -411,7 +508,7 @@ Future<bool> loginDialog() async {
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: userNameMsg.isNotEmpty ? userNameMsg : null),
controller: userContontroller,
controller: userController,
focusNode: FocusNode()..requestFocus(),
),
),

View File

@ -5,7 +5,9 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:get/get.dart';
@ -18,7 +20,7 @@ import '../../common/widgets/dialog.dart';
const double _kTabWidth = 235;
const double _kTabHeight = 42;
const double _kCardFixedWidth = 560;
const double _kCardFixedWidth = 540;
const double _kCardLeftMargin = 15;
const double _kContentHMargin = 15;
const double _kContentHSubMargin = _kContentHMargin + 33;
@ -28,6 +30,8 @@ const double _kListViewBottomMargin = 15;
const double _kTitleFontSize = 20;
const double _kContentFontSize = 15;
const Color _accentColor = MyTheme.accent;
const String _kSettingPageControllerTag = "settingPageController";
const String _kSettingPageIndexTag = "settingPageIndex";
class _TabInfo {
late final String label;
@ -37,10 +41,30 @@ class _TabInfo {
}
class DesktopSettingPage extends StatefulWidget {
const DesktopSettingPage({Key? key}) : super(key: key);
final int initialPage;
const DesktopSettingPage({Key? key, required this.initialPage})
: super(key: key);
@override
State<DesktopSettingPage> createState() => _DesktopSettingPageState();
static void switch2page(int page) {
if (page >= 5) return;
try {
if (Get.isRegistered<PageController>(tag: _kSettingPageControllerTag)) {
DesktopTabPage.onAddSetting(initialPage: page);
PageController controller = Get.find(tag: _kSettingPageControllerTag);
RxInt selectedIndex = Get.find(tag: _kSettingPageIndexTag);
selectedIndex.value = page;
controller.jumpToPage(page);
} else {
DesktopTabPage.onAddSetting(initialPage: page);
}
} catch (e) {
debugPrint('$e');
}
}
}
class _DesktopSettingPageState extends State<DesktopSettingPage>
@ -50,12 +74,12 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
_TabInfo('Security', Icons.enhanced_encryption_outlined,
Icons.enhanced_encryption),
_TabInfo('Network', Icons.link_outlined, Icons.link),
_TabInfo('Acount', Icons.person_outline, Icons.person),
_TabInfo('Account', Icons.person_outline, Icons.person),
_TabInfo('About', Icons.info_outline, Icons.info)
];
late PageController controller;
RxInt selectedIndex = 0.obs;
late RxInt selectedIndex;
@override
bool get wantKeepAlive => true;
@ -63,14 +87,24 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
@override
void initState() {
super.initState();
controller = PageController();
selectedIndex = (widget.initialPage < 5 ? widget.initialPage : 0).obs;
Get.put<RxInt>(selectedIndex, tag: _kSettingPageIndexTag);
controller = PageController(initialPage: widget.initialPage);
Get.put<PageController>(controller, tag: _kSettingPageControllerTag);
}
@override
void dispose() {
super.dispose();
Get.delete<PageController>(tag: _kSettingPageControllerTag);
Get.delete<RxInt>(tag: _kSettingPageIndexTag);
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: Row(
children: <Widget>[
SizedBox(
@ -85,7 +119,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
const VerticalDivider(thickness: 1, width: 1),
Expanded(
child: Container(
color: MyTheme.color(context).grayBg,
color: Theme.of(context).scaffoldBackgroundColor,
child: DesktopScrollWrapper(
scrollController: controller,
child: PageView(
@ -94,7 +128,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
_General(),
_Safety(),
_Network(),
_Acount(),
_Account(),
_About(),
],
)),
@ -387,7 +421,7 @@ class _Safety extends StatefulWidget {
class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
bool locked = true;
bool locked = bind.mainIsInstalled();
final scrollController = ScrollController();
@override
@ -533,7 +567,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
_OptionCheckBox(context, 'Deny remote access', 'stop-service',
checkedIcon: const Icon(
Icons.warning_amber_rounded,
color: Color.fromARGB(255, 255, 204, 0),
color: kColorWarn,
),
enabled: enabled),
Offstage(
@ -541,6 +575,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
child: _OptionCheckBox(context, 'Enable RDP', 'enable-rdp',
enabled: enabled),
),
_OptionCheckBox(context, 'Deny LAN Discovery', 'enable-lan-discovery',
reverse: true, enabled: enabled),
...directIp(context),
whitelist(),
]);
@ -700,14 +736,14 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
}
}
class _Acount extends StatefulWidget {
const _Acount({Key? key}) : super(key: key);
class _Account extends StatefulWidget {
const _Account({Key? key}) : super(key: key);
@override
State<_Acount> createState() => _AcountState();
State<_Account> createState() => _AccountState();
}
class _AcountState extends State<_Acount> {
class _AccountState extends State<_Account> {
@override
Widget build(BuildContext context) {
final scrollController = ScrollController();
@ -717,12 +753,12 @@ class _AcountState extends State<_Acount> {
physics: NeverScrollableScrollPhysics(),
controller: scrollController,
children: [
_Card(title: 'Acount', children: [login()]),
_Card(title: 'Account', children: [accountAction()]),
],
).marginOnly(bottom: _kListViewBottomMargin));
}
Widget login() {
Widget accountAction() {
return _futureBuilder(future: () async {
return await gFFI.userModel.getUserName();
}(), hasData: (data) {
@ -730,12 +766,14 @@ class _AcountState extends State<_Acount> {
return _Button(
username.isEmpty ? 'Login' : 'Logout',
() => {
loginDialog().then((success) {
if (success) {
// refresh frame
setState(() {});
}
})
username.isEmpty
? loginDialog().then((success) {
if (success) {
// refresh frame
setState(() {});
}
})
: gFFI.userModel.logOut()
});
});
}
@ -859,7 +897,9 @@ Widget _Card({required String title, required List<Widget> children}) {
}
Color? _disabledTextColor(BuildContext context, bool enabled) {
return enabled ? null : MyTheme.color(context).lighterText;
return enabled
? null
: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6);
}
// ignore: non_constant_identifier_names
@ -1339,91 +1379,6 @@ void changeServer() async {
});
}
void changeWhiteList({Function()? callback}) async {
Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
var newWhiteList = ((oldOptions['whitelist'] ?? "") as String).split(',');
var newWhiteListField = newWhiteList.join('\n');
var controller = TextEditingController(text: newWhiteListField);
var msg = "";
var isInProgress = false;
gFFI.dialogManager.show((setState, close) {
return CustomAlertDialog(
title: Text(translate("IP Whitelisting")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("whitelist_sep")),
const SizedBox(
height: 8.0,
),
Row(
children: [
Expanded(
child: TextField(
maxLines: null,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
),
controller: controller,
focusNode: FocusNode()..requestFocus()),
),
],
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
await bind.mainSetOption(key: 'whitelist', value: '');
callback?.call();
close();
},
child: Text(translate("Clear"))),
TextButton(
onPressed: () async {
setState(() {
msg = "";
isInProgress = true;
});
newWhiteListField = controller.text.trim();
var newWhiteList = "";
if (newWhiteListField.isEmpty) {
// pass
} else {
final ips =
newWhiteListField.trim().split(RegExp(r"[\s,;\n]+"));
// test ip
final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$");
for (final ip in ips) {
if (!ipMatch.hasMatch(ip)) {
msg = "${translate("Invalid IP")} $ip";
setState(() {
isInProgress = false;
});
return;
}
}
newWhiteList = ips.join(',');
}
oldOptions['whitelist'] = newWhiteList;
await bind.mainSetOptions(json: jsonEncode(oldOptions));
callback?.call();
close();
},
child: Text(translate("OK"))),
],
onCancel: close,
);
});
}
void changeSocks5Proxy() async {
var socks = await bind.mainGetSocks();

View File

@ -14,6 +14,23 @@ class DesktopTabPage extends StatefulWidget {
@override
State<DesktopTabPage> createState() => _DesktopTabPageState();
static void onAddSetting({int initialPage = 0}) {
try {
DesktopTabController tabController = Get.find();
tabController.add(TabInfo(
key: kTabLabelSettingPage,
label: kTabLabelSettingPage,
selectedIcon: Icons.build_sharp,
unselectedIcon: Icons.build_outlined,
page: DesktopSettingPage(
key: const ValueKey(kTabLabelSettingPage),
initialPage: initialPage,
)));
} catch (e) {
debugPrint('$e');
}
}
}
class _DesktopTabPageState extends State<DesktopTabPage> {
@ -22,6 +39,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
@override
void initState() {
super.initState();
Get.put<DesktopTabController>(tabController);
tabController.add(TabInfo(
key: kTabLabelHomePage,
label: kTabLabelHomePage,
@ -33,6 +51,12 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
)));
}
@override
void dispose() {
super.dispose();
Get.delete<DesktopTabController>();
}
@override
Widget build(BuildContext context) {
RxBool fullscreen = false.obs;
@ -42,13 +66,13 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
OverlayEntry(builder: (context) {
gFFI.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab(
controller: tabController,
tail: ActionIcon(
message: 'Settings',
icon: IconFont.menu,
onTap: onAddSetting,
onTap: DesktopTabPage.onAddSetting,
isClose: false,
),
));
@ -62,13 +86,4 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
fullscreen.value ? kFullScreenEdgeSize : kWindowEdgeSize,
child: tabWidget));
}
void onAddSetting() {
tabController.add(TabInfo(
key: kTabLabelSettingPage,
label: kTabLabelSettingPage,
selectedIcon: Icons.build_sharp,
unselectedIcon: Icons.build_outlined,
page: DesktopSettingPage(key: const ValueKey(kTabLabelSettingPage))));
}
}

View File

@ -104,7 +104,7 @@ class _FileManagerPageState extends State<FileManagerPage>
return false;
},
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: Row(
children: [
Flexible(flex: 3, child: body(isLocal: true)),

View File

@ -72,7 +72,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab(
controller: tabController,
onWindowCloseButton: handleWindowCloseButton,

View File

@ -70,7 +70,7 @@ class _PortForwardPageState extends State<PortForwardPage>
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
backgroundColor: MyTheme.color(context).grayBg,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: FutureBuilder(future: () async {
if (!widget.isRDP) {
refreshTunnelConfig();
@ -80,7 +80,8 @@ class _PortForwardPageState extends State<PortForwardPage>
return Container(
decoration: BoxDecoration(
border: Border.all(
width: 20, color: MyTheme.color(context).grayBg!)),
width: 20,
color: Theme.of(context).scaffoldBackgroundColor)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -88,7 +89,7 @@ class _PortForwardPageState extends State<PortForwardPage>
Flexible(
child: Container(
decoration: BoxDecoration(
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
border: Border.all(width: 1, color: MyTheme.border)),
child:
widget.isRDP ? buildRdp(context) : buildTunnel(context),
@ -131,7 +132,7 @@ class _PortForwardPageState extends State<PortForwardPage>
return Theme(
data: Theme.of(context)
.copyWith(backgroundColor: MyTheme.color(context).bg),
.copyWith(backgroundColor: Theme.of(context).backgroundColor),
child: Obx(() => ListView.builder(
controller: ScrollController(),
itemCount: pfs.length + 2,
@ -139,7 +140,7 @@ class _PortForwardPageState extends State<PortForwardPage>
if (index == 0) {
return Container(
height: 25,
color: MyTheme.color(context).grayBg,
color: Theme.of(context).scaffoldBackgroundColor,
child: Row(children: [
text('Local Port'),
const SizedBox(width: _kColumn1Width),
@ -166,7 +167,7 @@ class _PortForwardPageState extends State<PortForwardPage>
return Container(
height: _kRowHeight,
decoration: BoxDecoration(color: MyTheme.color(context).bg),
decoration: BoxDecoration(color: Theme.of(context).backgroundColor),
child: Row(children: [
buildTunnelInputCell(context,
controller: localPortController,
@ -216,11 +217,12 @@ class _PortForwardPageState extends State<PortForwardPage>
{required TextEditingController controller,
List<TextInputFormatter>? inputFormatters,
String? hint}) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return Expanded(
child: TextField(
controller: controller,
inputFormatters: inputFormatters,
cursorColor: MyTheme.color(context).text,
cursorColor: textColor,
cursorHeight: 20,
cursorWidth: 1,
decoration: InputDecoration(
@ -228,12 +230,12 @@ class _PortForwardPageState extends State<PortForwardPage>
borderSide: BorderSide(color: MyTheme.color(context).border!)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: MyTheme.color(context).border!)),
fillColor: MyTheme.color(context).bg,
fillColor: Theme.of(context).backgroundColor,
contentPadding: const EdgeInsets.all(10),
hintText: hint,
hintStyle: TextStyle(
color: MyTheme.color(context).placeholder, fontSize: 16)),
style: TextStyle(color: MyTheme.color(context).text, fontSize: 16),
hintStyle:
TextStyle(color: Theme.of(context).hintColor, fontSize: 16)),
style: TextStyle(color: textColor, fontSize: 16),
).marginAll(10),
);
}
@ -250,7 +252,7 @@ class _PortForwardPageState extends State<PortForwardPage>
? MyTheme.currentThemeMode() == ThemeMode.dark
? const Color(0xFF202020)
: const Color(0xFFF4F5F6)
: MyTheme.color(context).bg),
: Theme.of(context).backgroundColor),
child: Row(children: [
text(pf.localPort.toString()),
const SizedBox(width: _kColumn1Width),
@ -292,7 +294,7 @@ class _PortForwardPageState extends State<PortForwardPage>
).marginOnly(left: _kTextLeftMargin));
return Theme(
data: Theme.of(context)
.copyWith(backgroundColor: MyTheme.color(context).bg),
.copyWith(backgroundColor: Theme.of(context).backgroundColor),
child: ListView.builder(
controller: ScrollController(),
itemCount: 2,
@ -300,7 +302,7 @@ class _PortForwardPageState extends State<PortForwardPage>
if (index == 0) {
return Container(
height: 25,
color: MyTheme.color(context).grayBg,
color: Theme.of(context).scaffoldBackgroundColor,
child: Row(children: [
text1('Local Port'),
const SizedBox(width: _kColumn1Width),
@ -311,7 +313,8 @@ class _PortForwardPageState extends State<PortForwardPage>
} else {
return Container(
height: _kRowHeight,
decoration: BoxDecoration(color: MyTheme.color(context).bg),
decoration:
BoxDecoration(color: Theme.of(context).backgroundColor),
child: Row(children: [
Expanded(
child: Align(

View File

@ -80,7 +80,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab(
controller: tabController,
onWindowCloseButton: () async {

View File

@ -11,7 +11,6 @@ import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart';
import 'package:flutter_custom_cursor/flutter_custom_cursor.dart';
import '../../consts.dart';
import '../widgets/remote_menubar.dart';
import '../../common.dart';
import '../../mobile/widgets/dialog.dart';
@ -45,7 +44,6 @@ class _RemotePageState extends State<RemotePage>
late RxBool _keyboardEnabled;
final FocusNode _rawKeyFocusNode = FocusNode();
var _isPhysicalMouse = false;
var _imageFocused = false;
Function(bool)? _onEnterOrLeaveImage4Menubar;
@ -139,7 +137,7 @@ class _RemotePageState extends State<RemotePage>
Widget buildBody(BuildContext context) {
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: Overlay(
initialEntries: [
OverlayEntry(builder: (context) {
@ -445,6 +443,7 @@ class ImagePainter extends CustomPainter {
}
class QualityMonitor extends StatelessWidget {
static const textStyle = TextStyle(color: MyTheme.grayBg);
final QualityMonitorModel qualityMonitorModel;
QualityMonitor(this.qualityMonitorModel);
@ -464,23 +463,23 @@ class QualityMonitor extends StatelessWidget {
children: [
Text(
"Speed: ${qualityMonitorModel.data.speed ?? ''}",
style: const TextStyle(color: MyTheme.grayBg),
style: textStyle,
),
Text(
"FPS: ${qualityMonitorModel.data.fps ?? ''}",
style: const TextStyle(color: MyTheme.grayBg),
style: textStyle,
),
Text(
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
style: const TextStyle(color: MyTheme.grayBg),
style: textStyle,
),
Text(
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
style: const TextStyle(color: MyTheme.grayBg),
style: textStyle,
),
Text(
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
style: const TextStyle(color: MyTheme.grayBg),
style: textStyle,
),
],
),

View File

@ -91,7 +91,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab(
controller: tabController,
showTabBar: fullscreen.isFalse,

View File

@ -69,7 +69,7 @@ class _DesktopServerPageState extends State<DesktopServerPage>
OverlayEntry(builder: (context) {
gFFI.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold(
backgroundColor: MyTheme.color(context).bg,
backgroundColor: Theme.of(context).backgroundColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
@ -145,7 +145,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
windowManager.startDragging();
},
child: Container(
color: MyTheme.color(context).bg,
color: Theme.of(context).backgroundColor,
),
),
),
@ -310,14 +310,15 @@ class _CmHeaderState extends State<_CmHeader>
],
),
),
Offstage(
offstage: client.isFileTransfer,
child: IconButton(
onPressed: () => checkClickTime(
client.id, () => gFFI.chatModel.toggleCMChatPage(client.id)),
icon: Icon(Icons.message_outlined),
),
)
Consumer<ServerModel>(
builder: (_, model, child) => Offstage(
offstage: !client.authorized || client.isFileTransfer,
child: IconButton(
onPressed: () => checkClickTime(client.id,
() => gFFI.chatModel.toggleCMChatPage(client.id)),
icon: Icon(Icons.message_outlined),
),
))
],
);
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../common.dart';
class Button extends StatefulWidget {
GestureTapCallback onTap;
String text;
double? textSize;
double? minWidth;
bool isOutline;
double? padding;
Color? textColor;
double? radius;
Color? borderColor;
Button({
Key? key,
this.minWidth,
this.isOutline = false,
this.textSize,
this.padding,
this.textColor,
this.radius,
this.borderColor,
required this.onTap,
required this.text,
}) : super(key: key);
@override
State<Button> createState() => _ButtonState();
}
class _ButtonState extends State<Button> {
RxBool hover = false.obs;
RxBool pressed = false.obs;
@override
Widget build(BuildContext context) {
return Obx(() => InkWell(
onTapDown: (_) => pressed.value = true,
onTapUp: (_) => pressed.value = false,
onTapCancel: () => pressed.value = false,
onHover: (value) => hover.value = value,
onTap: widget.onTap,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: widget.minWidth ?? 70.0,
),
child: Container(
padding: EdgeInsets.all(widget.padding ?? 4.5),
alignment: Alignment.center,
decoration: BoxDecoration(
color: pressed.value
? MyTheme.accent
: (widget.isOutline
? Colors.transparent
: MyTheme.button),
border: Border.all(
color: pressed.value
? MyTheme.accent
: hover.value
? MyTheme.hoverBorder
: (widget.isOutline
? widget.borderColor ?? MyTheme.border
: MyTheme.button),
),
borderRadius: BorderRadius.circular(widget.radius ?? 5),
),
child: Text(
translate(
widget.text,
),
style: TextStyle(
fontSize: widget.textSize ?? 12.0,
color: pressed.value || !widget.isOutline
? Theme.of(context).backgroundColor
: widget.textColor ??
Theme.of(context).textTheme.titleLarge?.color),
).marginSymmetric(horizontal: 12),
)),
));
}
}

View File

@ -14,7 +14,8 @@ import 'package:flutter/material.dart';
// void setState(VoidCallback fn) { }
// enum Menu { itemOne, itemTwo, itemThree, itemFour }
const Duration _kMenuDuration = Duration(milliseconds: 300);
// const Duration _kMenuDuration = Duration(milliseconds: 300);
const Duration _kMenuDuration = Duration(milliseconds: 0);
const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
const double _kMenuHorizontalPadding = 16.0;
const double _kMenuDividerHeight = 16.0;
@ -22,7 +23,7 @@ const double _kMenuDividerHeight = 16.0;
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
const double _kMenuMaxWidth = double.infinity;
// const double _kMenuVerticalPadding = 8.0;
const double _kMenuVerticalPadding = 0.0;
const double _kMenuVerticalPadding = 8.0;
const double _kMenuWidthStep = 0.0;
//const double _kMenuScreenPadding = 8.0;
const double _kMenuScreenPadding = 0.0;

View File

@ -1,7 +1,6 @@
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:get/get.dart';
import './material_mod_popup_menu.dart' as mod_menu;
@ -78,7 +77,8 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
duration: kThemeChangeDuration,
child: Container(
alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: widget.height),
constraints: BoxConstraints(
minHeight: widget.height, maxHeight: widget.height),
padding:
widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
child: widget.child,
@ -156,12 +156,14 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
final RadioCurOptionGetter curOptionGetter;
final RadioOptionSetter optionSetter;
final RxString _curOption = "".obs;
final EdgeInsets? padding;
MenuEntryRadios({
required this.text,
required this.optionsGetter,
required this.curOptionGetter,
required this.optionSetter,
this.padding,
dismissOnClicked = false,
RxBool? enabled,
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled) {
@ -189,30 +191,37 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
height: conf.height,
child: TextButton(
child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height),
constraints:
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
child: Row(
children: [
Text(
opt.text,
style: TextStyle(
color: MyTheme.color(context).text,
color: Theme.of(context).textTheme.titleLarge?.color,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: SizedBox(
width: 20.0,
height: 20.0,
child: Obx(() => opt.value == curOption.value
? Icon(
Icons.check,
color: conf.commonColor,
)
: const SizedBox.shrink())),
)),
alignment: Alignment.centerRight,
child: Transform.scale(
scale: MenuConfig.iconScale,
child: Obx(() => opt.value == curOption.value
? IconButton(
padding: const EdgeInsets.fromLTRB(
8.0, 0.0, 8.0, 0.0),
hoverColor: Colors.transparent,
focusColor: Colors.transparent,
onPressed: () {},
icon: Icon(
Icons.check,
color: conf.commonColor,
))
: const SizedBox.shrink()),
))),
],
),
),
@ -239,12 +248,14 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
final RadioCurOptionGetter curOptionGetter;
final RadioOptionSetter optionSetter;
final RxString _curOption = "".obs;
final EdgeInsets? padding;
MenuEntrySubRadios({
required this.text,
required this.optionsGetter,
required this.curOptionGetter,
required this.optionSetter,
this.padding,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
@ -275,28 +286,34 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
height: conf.height,
child: TextButton(
child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height),
constraints:
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
child: Row(
children: [
Text(
opt.text,
style: TextStyle(
color: MyTheme.color(context).text,
color: Theme.of(context).textTheme.titleLarge?.color,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: SizedBox(
width: 20.0,
height: 20.0,
child: Transform.scale(
scale: MenuConfig.iconScale,
child: Obx(() => opt.value == curOption.value
? Icon(
Icons.check,
color: conf.commonColor,
)
? IconButton(
padding: EdgeInsets.zero,
hoverColor: Colors.transparent,
focusColor: Colors.transparent,
onPressed: () {},
icon: Icon(
Icons.check,
color: conf.commonColor,
))
: const SizedBox.shrink())),
)),
],
@ -318,7 +335,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
return [
PopupMenuChildrenItem(
enabled: super.enabled,
padding: EdgeInsets.zero,
padding: padding,
height: conf.height,
itemBuilder: (BuildContext context) =>
options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(),
@ -327,7 +344,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
Text(
text,
style: TextStyle(
color: MyTheme.color(context).text,
color: Theme.of(context).textTheme.titleLarge?.color,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
),
@ -345,28 +362,37 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
}
}
enum SwitchType {
sswitch,
scheckbox,
}
typedef SwitchGetter = Future<bool> Function();
typedef SwitchSetter = Future<void> Function(bool);
abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
final SwitchType switchType;
final String text;
final EdgeInsets? padding;
Rx<TextStyle>? textStyle;
MenuEntrySwitchBase({
required this.switchType,
required this.text,
required dismissOnClicked,
this.textStyle,
this.padding,
RxBool? enabled,
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled);
RxBool get curOption;
Future<void> setOption(bool option);
Future<void> setOption(bool? option);
@override
List<mod_menu.PopupMenuEntry<T>> build(
BuildContext context, MenuConfig conf) {
textStyle ??= const TextStyle(
color: Colors.black,
textStyle ??= TextStyle(
color: Theme.of(context).textTheme.titleLarge?.color,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal)
.obs;
@ -376,6 +402,7 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
height: conf.height,
child: TextButton(
child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart,
height: conf.height,
child: Row(children: [
@ -386,16 +413,33 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Obx(() => Switch(
value: curOption.value,
onChanged: (v) {
if (super.dismissOnClicked &&
Navigator.canPop(context)) {
Navigator.pop(context);
}
setOption(v);
},
)),
child: Transform.scale(
scale: MenuConfig.iconScale,
child: Obx(() {
if (switchType == SwitchType.sswitch) {
return Switch(
value: curOption.value,
onChanged: (v) {
if (super.dismissOnClicked &&
Navigator.canPop(context)) {
Navigator.pop(context);
}
setOption(v);
},
);
} else {
return Checkbox(
value: curOption.value,
onChanged: (v) {
if (super.dismissOnClicked &&
Navigator.canPop(context)) {
Navigator.pop(context);
}
setOption(v);
},
);
}
})),
))
])),
onPressed: () {
@ -416,15 +460,19 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
final RxBool _curOption = false.obs;
MenuEntrySwitch({
required SwitchType switchType,
required String text,
required this.getter,
required this.setter,
Rx<TextStyle>? textStyle,
EdgeInsets? padding,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
switchType: switchType,
text: text,
textStyle: textStyle,
padding: padding,
dismissOnClicked: dismissOnClicked,
enabled: enabled,
) {
@ -436,11 +484,13 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
@override
RxBool get curOption => _curOption;
@override
setOption(bool option) async {
await setter(option);
final opt = await getter();
if (_curOption.value != opt) {
_curOption.value = opt;
setOption(bool? option) async {
if (option != null) {
await setter(option);
final opt = await getter();
if (_curOption.value != opt) {
_curOption.value = opt;
}
}
}
}
@ -453,32 +503,40 @@ class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
final SwitchSetter setter;
MenuEntrySwitch2({
required SwitchType switchType,
required String text,
required this.getter,
required this.setter,
Rx<TextStyle>? textStyle,
EdgeInsets? padding,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
switchType: switchType,
text: text,
textStyle: textStyle,
padding: padding,
dismissOnClicked: dismissOnClicked);
@override
RxBool get curOption => getter();
@override
setOption(bool option) async {
await setter(option);
setOption(bool? option) async {
if (option != null) {
await setter(option);
}
}
}
class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
final String text;
final List<MenuEntryBase<T>> entries;
final EdgeInsets? padding;
MenuEntrySubMenu({
required this.text,
required this.entries,
this.padding,
RxBool? enabled,
}) : super(enabled: enabled);
@ -490,7 +548,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
PopupMenuChildrenItem(
enabled: super.enabled,
height: conf.height,
padding: EdgeInsets.zero,
padding: padding,
position: mod_menu.PopupMenuPosition.overSide,
itemBuilder: (BuildContext context) => entries
.map((entry) => entry.build(context, conf))
@ -501,7 +559,9 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
Obx(() => Text(
text,
style: TextStyle(
color: super.enabled!.value ? Colors.black : Colors.grey,
color: super.enabled!.value
? Theme.of(context).textTheme.titleLarge?.color
: Colors.grey,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
)),
@ -522,10 +582,12 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
class MenuEntryButton<T> extends MenuEntryBase<T> {
final Widget Function(TextStyle? style) childBuilder;
Function() proc;
final EdgeInsets? padding;
MenuEntryButton({
required this.childBuilder,
required this.proc,
this.padding,
dismissOnClicked = false,
RxBool? enabled,
}) : super(
@ -534,8 +596,8 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
);
Widget _buildChild(BuildContext context, MenuConfig conf) {
const enabledStyle = TextStyle(
color: Colors.black,
final enabledStyle = TextStyle(
color: Theme.of(context).textTheme.titleLarge?.color,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal);
const disabledStyle = TextStyle(
@ -553,8 +615,10 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
}
: null,
child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height),
constraints:
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
child: childBuilder(
super.enabled!.value ? enabledStyle : disabledStyle),
),

View File

@ -20,7 +20,7 @@ import './material_mod_popup_menu.dart' as mod_menu;
class _MenubarTheme {
static const Color commonColor = MyTheme.accent;
// kMinInteractiveDimension
static const double height = 25.0;
static const double height = 20.0;
static const double dividerHeight = 12.0;
}
@ -391,31 +391,41 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
List<MenuEntryBase<String>> _getControlMenu(BuildContext context) {
final pi = widget.ffi.ffiModel.pi;
final perms = widget.ffi.ffiModel.permissions;
const EdgeInsets padding = EdgeInsets.only(left: 14.0, right: 5.0);
final List<MenuEntryBase<String>> displayMenu = [];
displayMenu.addAll([
MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Row(
children: [
Text(
translate('OS Password'),
style: style,
),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.edit),
onPressed: () => showSetOSPassword(
widget.id, false, widget.ffi.dialogManager),
),
))
],
),
childBuilder: (TextStyle? style) => Container(
alignment: AlignmentDirectional.center,
height: _MenubarTheme.height,
child: Row(
children: [
Text(
translate('OS Password'),
style: style,
),
Expanded(
child: Align(
alignment: Alignment.centerRight,
child: Transform.scale(
scale: 0.8,
child: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.edit),
onPressed: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
showSetOSPassword(
widget.id, false, widget.ffi.dialogManager);
})),
))
],
)),
proc: () {
showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
},
padding: padding,
dismissOnClicked: true,
),
MenuEntryButton<String>(
@ -426,6 +436,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
connect(context, widget.id, isFileTransfer: true);
},
padding: padding,
dismissOnClicked: true,
),
MenuEntryButton<String>(
@ -433,6 +444,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
translate('TCP Tunneling'),
style: style,
),
padding: padding,
proc: () {
connect(context, widget.id, isTcpTunneling: true);
},
@ -451,6 +463,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
showAuditDialog(widget.id, widget.ffi.dialogManager);
},
padding: padding,
dismissOnClicked: true,
),
);
@ -467,6 +480,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
bind.sessionCtrlAltDel(id: widget.id);
},
padding: padding,
dismissOnClicked: true,
));
}
@ -483,6 +497,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
},
padding: padding,
dismissOnClicked: true,
));
}
@ -496,6 +511,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
bind.sessionLockScreen(id: widget.id);
},
padding: padding,
dismissOnClicked: true,
));
@ -513,6 +529,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
value: '${blockInput.value ? "un" : ""}block-input');
blockInput.value = !blockInput.value;
},
padding: padding,
dismissOnClicked: true,
));
}
@ -527,6 +544,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
bind.sessionRefresh(id: widget.id);
},
padding: padding,
dismissOnClicked: true,
));
}
@ -547,6 +565,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
// }
// }();
// },
// padding: padding,
// dismissOnClicked: true,
// ));
// }
@ -559,6 +578,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () {
widget.ffi.cursorModel.reset();
},
padding: padding,
dismissOnClicked: true,
));
}
@ -567,125 +587,155 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
}
List<MenuEntryBase<String>> _getDisplayMenu(dynamic futureData) {
const EdgeInsets padding = EdgeInsets.only(left: 18.0, right: 8.0);
final displayMenu = [
MenuEntryRadios<String>(
text: translate('Ratio'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Scale original'), value: 'original'),
MenuEntryRadioOption(
text: translate('Scale adaptive'), value: 'adaptive'),
],
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'view-style') ??
'adaptive';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "view-style", value: newValue);
widget.ffi.canvasModel.updateViewStyle();
}),
text: translate('Ratio'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Scale original'),
value: 'original',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: translate('Scale adaptive'),
value: 'adaptive',
dismissOnClicked: true,
),
],
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'view-style') ??
'adaptive';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "view-style", value: newValue);
widget.ffi.canvasModel.updateViewStyle();
},
padding: padding,
dismissOnClicked: true,
),
MenuEntryDivider<String>(),
MenuEntryRadios<String>(
text: translate('Scroll Style'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('ScrollAuto'), value: 'scrollauto'),
MenuEntryRadioOption(
text: translate('Scrollbar'), value: 'scrollbar'),
],
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'scroll-style') ??
'';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "scroll-style", value: newValue);
widget.ffi.canvasModel.updateScrollStyle();
}),
text: translate('Scroll Style'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('ScrollAuto'),
value: 'scrollauto',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: translate('Scrollbar'),
value: 'scrollbar',
dismissOnClicked: true,
),
],
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'scroll-style') ??
'';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "scroll-style", value: newValue);
widget.ffi.canvasModel.updateScrollStyle();
},
padding: padding,
dismissOnClicked: true,
),
MenuEntryDivider<String>(),
MenuEntryRadios<String>(
text: translate('Image Quality'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Good image quality'), value: 'best'),
MenuEntryRadioOption(
text: translate('Balanced'), value: 'balanced'),
MenuEntryRadioOption(
text: translate('Optimize reaction time'), value: 'low'),
MenuEntryRadioOption(
text: translate('Custom'),
value: 'custom',
dismissOnClicked: true),
],
curOptionGetter: () async {
String quality =
await bind.sessionGetImageQuality(id: widget.id) ?? 'balanced';
if (quality == '') quality = 'balanced';
return quality;
},
optionSetter: (String oldValue, String newValue) async {
if (oldValue != newValue) {
await bind.sessionSetImageQuality(id: widget.id, value: newValue);
}
text: translate('Image Quality'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Good image quality'),
value: 'best',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: translate('Balanced'),
value: 'balanced',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: translate('Optimize reaction time'),
value: 'low',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: translate('Custom'),
value: 'custom',
dismissOnClicked: true),
],
curOptionGetter: () async {
String quality =
await bind.sessionGetImageQuality(id: widget.id) ?? 'balanced';
if (quality == '') quality = 'balanced';
return quality;
},
optionSetter: (String oldValue, String newValue) async {
if (oldValue != newValue) {
await bind.sessionSetImageQuality(id: widget.id, value: newValue);
}
if (newValue == 'custom') {
final btnCancel = msgBoxButton(translate('Close'), () {
widget.ffi.dialogManager.dismissAll();
});
final quality =
await bind.sessionGetCustomImageQuality(id: widget.id);
double initValue = quality != null && quality.isNotEmpty
? quality[0].toDouble()
: 50.0;
const minValue = 10.0;
const maxValue = 100.0;
if (initValue < minValue) {
initValue = minValue;
}
if (initValue > maxValue) {
initValue = maxValue;
}
final RxDouble sliderValue = RxDouble(initValue);
final rxReplay = rxdart.ReplaySubject<double>();
rxReplay
.throttleTime(const Duration(milliseconds: 1000),
trailing: true, leading: false)
.listen((double v) {
() async {
await bind.sessionSetCustomImageQuality(
id: widget.id, value: v.toInt());
}();
});
final slider = Obx(() {
return Slider(
value: sliderValue.value,
min: minValue,
max: maxValue,
divisions: 90,
onChanged: (double value) {
sliderValue.value = value;
rxReplay.add(value);
},
);
});
final content = Row(
children: [
slider,
SizedBox(
width: 90,
child: Obx(() => Text(
'${sliderValue.value.round()}% Bitrate',
style: const TextStyle(fontSize: 15),
)))
],
);
msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
content, [btnCancel]);
if (newValue == 'custom') {
final btnCancel = msgBoxButton(translate('Close'), () {
widget.ffi.dialogManager.dismissAll();
});
final quality =
await bind.sessionGetCustomImageQuality(id: widget.id);
double initValue = quality != null && quality.isNotEmpty
? quality[0].toDouble()
: 50.0;
const minValue = 10.0;
const maxValue = 100.0;
if (initValue < minValue) {
initValue = minValue;
}
}),
if (initValue > maxValue) {
initValue = maxValue;
}
final RxDouble sliderValue = RxDouble(initValue);
final rxReplay = rxdart.ReplaySubject<double>();
rxReplay
.throttleTime(const Duration(milliseconds: 1000),
trailing: true, leading: false)
.listen((double v) {
() async {
await bind.sessionSetCustomImageQuality(
id: widget.id, value: v.toInt());
}();
});
final slider = Obx(() {
return Slider(
value: sliderValue.value,
min: minValue,
max: maxValue,
divisions: 90,
onChanged: (double value) {
sliderValue.value = value;
rxReplay.add(value);
},
);
});
final content = Row(
children: [
slider,
SizedBox(
width: 90,
child: Obx(() => Text(
'${sliderValue.value.round()}% Bitrate',
style: const TextStyle(fontSize: 15),
)))
],
);
msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
content, [btnCancel]);
}
},
padding: padding,
),
MenuEntryDivider<String>(),
];
@ -701,30 +751,49 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
} finally {}
if (codecs.length == 2 && (codecs[0] || codecs[1])) {
displayMenu.add(MenuEntryRadios<String>(
text: translate('Codec Preference'),
optionsGetter: () {
final list = [
MenuEntryRadioOption(text: translate('Auto'), value: 'auto'),
MenuEntryRadioOption(text: 'VP9', value: 'vp9'),
];
if (codecs[0]) {
list.add(MenuEntryRadioOption(text: 'H264', value: 'h264'));
}
if (codecs[1]) {
list.add(MenuEntryRadioOption(text: 'H265', value: 'h265'));
}
return list;
},
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'codec-preference') ??
'auto';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "codec-preference", value: newValue);
bind.sessionChangePreferCodec(id: widget.id);
}));
text: translate('Codec Preference'),
optionsGetter: () {
final list = [
MenuEntryRadioOption(
text: translate('Auto'),
value: 'auto',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: 'VP9',
value: 'vp9',
dismissOnClicked: true,
),
];
if (codecs[0]) {
list.add(MenuEntryRadioOption(
text: 'H264',
value: 'h264',
dismissOnClicked: true,
));
}
if (codecs[1]) {
list.add(MenuEntryRadioOption(
text: 'H265',
value: 'h265',
dismissOnClicked: true,
));
}
return list;
},
curOptionGetter: () async {
return await bind.sessionGetOption(
id: widget.id, arg: 'codec-preference') ??
'auto';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption(
id: widget.id, name: "codec-preference", value: newValue);
bind.sessionChangePreferCodec(id: widget.id);
},
padding: padding,
dismissOnClicked: true,
));
}
}
@ -732,62 +801,74 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
displayMenu.add(() {
final state = ShowRemoteCursorState.find(widget.id);
return MenuEntrySwitch2<String>(
text: translate('Show remote cursor'),
getter: () {
return state;
},
setter: (bool v) async {
state.value = v;
await bind.sessionToggleOption(
id: widget.id, value: 'show-remote-cursor');
});
switchType: SwitchType.scheckbox,
text: translate('Show remote cursor'),
getter: () {
return state;
},
setter: (bool v) async {
state.value = v;
await bind.sessionToggleOption(
id: widget.id, value: 'show-remote-cursor');
},
padding: padding,
dismissOnClicked: true,
);
}());
/// Show quality monitor
displayMenu.add(MenuEntrySwitch<String>(
text: translate('Show quality monitor'),
getter: () async {
return bind.sessionGetToggleOptionSync(
id: widget.id, arg: 'show-quality-monitor');
},
setter: (bool v) async {
await bind.sessionToggleOption(
id: widget.id, value: 'show-quality-monitor');
widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
}));
switchType: SwitchType.scheckbox,
text: translate('Show quality monitor'),
getter: () async {
return bind.sessionGetToggleOptionSync(
id: widget.id, arg: 'show-quality-monitor');
},
setter: (bool v) async {
await bind.sessionToggleOption(
id: widget.id, value: 'show-quality-monitor');
widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
},
padding: padding,
dismissOnClicked: true,
));
final perms = widget.ffi.ffiModel.permissions;
final pi = widget.ffi.ffiModel.pi;
if (perms['audio'] != false) {
displayMenu.add(_createSwitchMenuEntry('Mute', 'disable-audio'));
displayMenu
.add(_createSwitchMenuEntry('Mute', 'disable-audio', padding, true));
}
if (Platform.isWindows &&
pi.platform == 'Windows' &&
perms['file'] != false) {
displayMenu.add(_createSwitchMenuEntry(
'Allow file copy and paste', 'enable-file-transfer'));
'Allow file copy and paste', 'enable-file-transfer', padding, true));
}
if (perms['keyboard'] != false) {
if (perms['clipboard'] != false) {
displayMenu.add(
_createSwitchMenuEntry('Disable clipboard', 'disable-clipboard'));
displayMenu.add(_createSwitchMenuEntry(
'Disable clipboard', 'disable-clipboard', padding, true));
}
displayMenu.add(_createSwitchMenuEntry(
'Lock after session end', 'lock-after-session-end'));
'Lock after session end', 'lock-after-session-end', padding, true));
if (pi.platform == 'Windows') {
displayMenu.add(MenuEntrySwitch2<String>(
dismissOnClicked: true,
text: translate('Privacy mode'),
getter: () {
return PrivacyModeState.find(widget.id);
},
setter: (bool v) async {
await bind.sessionToggleOption(
id: widget.id, value: 'privacy-mode');
}));
switchType: SwitchType.scheckbox,
text: translate('Privacy mode'),
getter: () {
return PrivacyModeState.find(widget.id);
},
setter: (bool v) async {
await bind.sessionToggleOption(
id: widget.id, value: 'privacy-mode');
},
padding: padding,
dismissOnClicked: true,
));
}
}
return displayMenu;
@ -796,34 +877,39 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
List<MenuEntryBase<String>> _getKeyboardMenu() {
final keyboardMenu = [
MenuEntryRadios<String>(
text: translate('Ratio'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Legacy mode'), value: 'legacy'),
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
],
curOptionGetter: () async {
return await bind.sessionGetKeyboardName(id: widget.id);
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionSetKeyboardMode(
id: widget.id, keyboardMode: newValue);
widget.ffi.canvasModel.updateViewStyle();
})
text: translate('Ratio'),
optionsGetter: () => [
MenuEntryRadioOption(text: translate('Legacy mode'), value: 'legacy'),
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
],
curOptionGetter: () async {
return await bind.sessionGetKeyboardName(id: widget.id);
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionSetKeyboardMode(
id: widget.id, keyboardMode: newValue);
widget.ffi.canvasModel.updateViewStyle();
},
)
];
return keyboardMenu;
}
MenuEntrySwitch<String> _createSwitchMenuEntry(String text, String option) {
MenuEntrySwitch<String> _createSwitchMenuEntry(
String text, String option, EdgeInsets? padding, bool dismissOnClicked) {
return MenuEntrySwitch<String>(
text: translate(text),
getter: () async {
return bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
},
setter: (bool v) async {
await bind.sessionToggleOption(id: widget.id, value: option);
});
switchType: SwitchType.scheckbox,
text: translate(text),
getter: () async {
return bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
},
setter: (bool v) async {
await bind.sessionToggleOption(id: widget.id, value: option);
},
padding: padding,
dismissOnClicked: dismissOnClicked,
);
}
}

View File

@ -490,6 +490,15 @@ class _ListView extends StatelessWidget {
this.tabBuilder,
this.labelGetter});
/// Check whether to show ListView
///
/// Conditions:
/// - hide single item when only has one item (home) on [DesktopTabPage].
bool isHideSingleItem() {
return state.value.tabs.length == 1 &&
controller.tabType == DesktopTabType.main;
}
@override
Widget build(BuildContext context) {
return Obx(() => ListView(
@ -497,38 +506,41 @@ class _ListView extends StatelessWidget {
scrollDirection: Axis.horizontal,
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
children: state.value.tabs.asMap().entries.map((e) {
final index = e.key;
final tab = e.value;
return _Tab(
index: index,
label: labelGetter == null
? Rx<String>(tab.label)
: labelGetter!(tab.label),
selectedIcon: tab.selectedIcon,
unselectedIcon: tab.unselectedIcon,
closable: tab.closable,
selected: state.value.selected,
onClose: () {
if (tab.onTabCloseButton != null) {
tab.onTabCloseButton!();
} else {
controller.remove(index);
}
},
onSelected: () => controller.jumpTo(index),
tabBuilder: tabBuilder == null
? null
: (Widget icon, Widget labelWidget, TabThemeConf themeConf) {
return tabBuilder!(
tab.label,
icon,
labelWidget,
themeConf,
);
children: isHideSingleItem()
? List.empty()
: state.value.tabs.asMap().entries.map((e) {
final index = e.key;
final tab = e.value;
return _Tab(
index: index,
label: labelGetter == null
? Rx<String>(tab.label)
: labelGetter!(tab.label),
selectedIcon: tab.selectedIcon,
unselectedIcon: tab.unselectedIcon,
closable: tab.closable,
selected: state.value.selected,
onClose: () {
if (tab.onTabCloseButton != null) {
tab.onTabCloseButton!();
} else {
controller.remove(index);
}
},
);
}).toList()));
onSelected: () => controller.jumpTo(index),
tabBuilder: tabBuilder == null
? null
: (Widget icon, Widget labelWidget,
TabThemeConf themeConf) {
return tabBuilder!(
tab.label,
icon,
labelWidget,
themeConf,
);
},
);
}).toList()));
}
}

View File

@ -103,6 +103,7 @@ void runMainApp(bool startService) async {
restoreWindowPosition(WindowType.Main);
await windowManager.show();
await windowManager.focus();
await windowManager.setOpacity(1);
});
}
@ -190,8 +191,13 @@ void runPortForwardScreen(Map<String, dynamic> argument) async {
void runConnectionManagerScreen() async {
// initialize window
WindowOptions windowOptions =
getHiddenTitleBarWindowOptions(size: const Size(300, 400));
await initEnv(kAppTypeMain);
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
// ensure initial window size to be changed
await windowManager.setSize(kConnectionManagerWindowSize);
await Future.wait([
windowManager.setAlignment(Alignment.topRight),
initEnv(kAppTypeMain)
]);
runApp(GetMaterialApp(
debugShowCheckedModeBanner: false,
theme: MyTheme.lightTheme,
@ -206,17 +212,17 @@ void runConnectionManagerScreen() async {
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
windowManager.show();
windowManager.focus();
windowManager.setOpacity(1);
windowManager.setAlignment(Alignment.topRight); // ensure
});
}
WindowOptions getHiddenTitleBarWindowOptions({Size? size}) {
return WindowOptions(
size: size,
center: true,
center: false,
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: TitleBarStyle.hidden,

View File

@ -45,7 +45,7 @@ class ChatPage extends StatelessWidget implements PageShape {
return ChangeNotifierProvider.value(
value: chatModel,
child: Container(
color: MyTheme.color(context).grayBg,
color: Theme.of(context).scaffoldBackgroundColor,
child: Consumer<ChatModel>(builder: (context, chatModel, child) {
final currentUser = chatModel.currentUser;
return Stack(
@ -62,11 +62,18 @@ class ChatPage extends StatelessWidget implements PageShape {
inputOptions: InputOptions(
sendOnEnter: true,
inputDecoration: defaultInputDecoration(
fillColor: MyTheme.color(context).bg),
hintText: "${translate('Write a message')}...",
fillColor: Theme.of(context).backgroundColor),
sendButtonBuilder: defaultSendButton(
color: MyTheme.color(context).text!),
inputTextStyle:
TextStyle(color: MyTheme.color(context).text)),
color: Theme.of(context)
.textTheme
.titleLarge!
.color!),
inputTextStyle: TextStyle(
color: Theme.of(context)
.textTheme
.titleLarge
?.color)),
messageOptions: MessageOptions(
showOtherUsersAvatar: false,
showTime: true,
@ -81,7 +88,8 @@ class ChatPage extends StatelessWidget implements PageShape {
)),
);
}),
chatModel.currentID == ChatModel.clientModeID
desktopType == DesktopType.cm ||
chatModel.currentID == ChatModel.clientModeID
? SizedBox.shrink()
: Padding(
padding: EdgeInsets.all(12),

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
@ -10,12 +9,11 @@ import 'package:url_launcher/url_launcher.dart';
import '../../common.dart';
import '../../common/widgets/address_book.dart';
import '../../common/widgets/peer_tab_page.dart';
import '../../common/widgets/peer_widget.dart';
import '../../common/widgets/peers_view.dart';
import '../../consts.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
import 'home_page.dart';
import 'remote_page.dart';
import 'scan_page.dart';
import 'settings_page.dart';
@ -84,9 +82,9 @@ class _ConnectionPageState extends State<ConnectionPage> {
translate('Address Book')
],
children: [
RecentPeerWidget(),
FavoritePeerWidget(),
DiscoveredPeerWidget(),
RecentPeersView(),
FavoritePeersView(),
DiscoveredPeersView(),
const AddressBook(),
],
)),
@ -97,38 +95,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
/// Connects to the selected peer.
void onConnect() {
var id = _idController.id;
connect(id);
}
/// Connect to a peer with [id].
/// If [isFileTransfer], starts a session only for file transfer.
void connect(String id, {bool isFileTransfer = false}) async {
if (id == '') return;
id = id.replaceAll(' ', '');
if (isFileTransfer) {
if (!await PermissionManager.check("file")) {
if (!await PermissionManager.request("file")) {
return;
}
}
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => FileManagerPage(id: id),
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => RemotePage(id: id),
),
);
}
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
connect(context, id);
}
/// UI for software update.
@ -161,8 +128,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 2),
child: Ink(
decoration: const BoxDecoration(
color: MyTheme.white,
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.all(Radius.circular(13)),
),
child: Row(

View File

@ -58,7 +58,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
return false;
},
child: Scaffold(
backgroundColor: MyTheme.grayBg,
// backgroundColor: MyTheme.grayBg,
appBar: AppBar(
leading: Row(children: [
IconButton(
@ -69,7 +69,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
title: ToggleSwitch(
initialLabelIndex: model.isLocal ? 0 : 1,
activeBgColor: [MyTheme.idColor],
inactiveBgColor: MyTheme.grayBg,
// inactiveBgColor: MyTheme.grayBg,
inactiveFgColor: Colors.black54,
totalSwitches: 2,
minWidth: 100,
@ -465,6 +465,9 @@ class _FileManagerPageState extends State<FileManagerPage> {
);
case JobState.none:
break;
case JobState.paused:
// TODO: Handle this case.
break;
}
return null;
}
@ -530,8 +533,7 @@ class BottomSheetBody extends StatelessWidget {
children: [
Text(title, style: TextStyle(fontSize: 18)),
Text(text,
style: TextStyle(
fontSize: 14, color: MyTheme.grayBg))
style: TextStyle(fontSize: 14)) // TODO color
],
)
],
@ -548,7 +550,7 @@ class BottomSheetBody extends StatelessWidget {
));
},
onClosing: () {},
backgroundColor: MyTheme.grayBg,
// backgroundColor: MyTheme.grayBg,
enableDrag: false,
);
}

View File

@ -59,7 +59,7 @@ class _HomePageState extends State<HomePage> {
return false;
},
child: Scaffold(
backgroundColor: MyTheme.grayBg,
// backgroundColor: MyTheme.grayBg,
appBar: AppBar(
centerTitle: true,
title: Text("RustDesk"),
@ -73,7 +73,7 @@ class _HomePageState extends State<HomePage> {
.toList(),
currentIndex: _selectedIndex,
type: BottomNavigationBarType.fixed,
selectedItemColor: MyTheme.accent,
selectedItemColor: MyTheme.accent, //
unselectedItemColor: MyTheme.darkGray,
onTap: (index) => setState(() {
// close chat overlay when go chat page
@ -95,7 +95,7 @@ class WebHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: MyTheme.grayBg,
// backgroundColor: MyTheme.grayBg,
appBar: AppBar(
centerTitle: true,
title: Text("RustDesk" + (isWeb ? " (Beta) " : "")),

View File

@ -325,17 +325,13 @@ class _RemotePageState extends State<RemotePage> {
},
onPointerSignal: (e) {
if (e is PointerScrollEvent) {
var dx = e.scrollDelta.dx;
var dy = e.scrollDelta.dy;
if (dx > 0)
dx = -1;
else if (dx < 0) dx = 1;
if (dy > 0)
var dy = 0;
if (e.scrollDelta.dy > 0) {
dy = -1;
else if (dy < 0) dy = 1;
bind.sessionSendMouse(
id: widget.id,
msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
} else if (e.scrollDelta.dy < 0) {
dy = 1;
}
gFFI.scroll(dy);
}
},
child: MouseRegion(
@ -752,7 +748,7 @@ class _RemotePageState extends State<RemotePage> {
void changeTouchMode() {
setState(() => _showEdit = false);
showModalBottomSheet(
backgroundColor: MyTheme.grayBg,
// backgroundColor: MyTheme.grayBg,
isScrollControlled: true,
context: context,
shape: const RoundedRectangleBorder(
@ -968,7 +964,9 @@ class ImagePainter extends CustomPainter {
}
}
// TODO global widget
class QualityMonitor extends StatelessWidget {
static final textColor = Colors.grey.shade200;
@override
Widget build(BuildContext context) => ChangeNotifierProvider.value(
value: gFFI.qualityMonitorModel,
@ -985,23 +983,23 @@ class QualityMonitor extends StatelessWidget {
children: [
Text(
"Speed: ${qualityMonitorModel.data.speed ?? ''}",
style: TextStyle(color: MyTheme.grayBg),
style: TextStyle(color: textColor),
),
Text(
"FPS: ${qualityMonitorModel.data.fps ?? ''}",
style: TextStyle(color: MyTheme.grayBg),
style: TextStyle(color: textColor),
),
Text(
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
style: TextStyle(color: MyTheme.grayBg),
style: TextStyle(color: textColor),
),
Text(
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
style: TextStyle(color: MyTheme.grayBg),
style: TextStyle(color: textColor),
),
Text(
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
style: TextStyle(color: MyTheme.grayBg),
style: TextStyle(color: textColor),
),
],
),

View File

@ -170,7 +170,7 @@ class ServerInfo extends StatelessWidget {
icon: const Icon(Icons.perm_identity),
labelText: translate("ID"),
labelStyle: const TextStyle(
fontWeight: FontWeight.bold, color: MyTheme.accent50),
fontWeight: FontWeight.bold, color: MyTheme.accent80),
),
onSaved: (String? value) {},
),
@ -185,7 +185,7 @@ class ServerInfo extends StatelessWidget {
icon: const Icon(Icons.lock),
labelText: translate("Password"),
labelStyle: const TextStyle(
fontWeight: FontWeight.bold, color: MyTheme.accent50),
fontWeight: FontWeight.bold, color: MyTheme.accent80),
suffix: isPermanent
? null
: IconButton(
@ -213,7 +213,7 @@ class ServerInfo extends StatelessWidget {
fontFamily: 'WorkSans',
fontWeight: FontWeight.bold,
fontSize: 18,
color: MyTheme.accent80,
color: MyTheme.accent,
),
))
],
@ -304,7 +304,8 @@ class _PermissionCheckerState extends State<PermissionChecker> {
softWrap: true,
style: const TextStyle(
fontSize: 14.0,
color: MyTheme.accent50)))
fontWeight: FontWeight.w500,
color: MyTheme.accent80)))
],
)
: const SizedBox.shrink())
@ -333,8 +334,13 @@ class PermissionRow extends StatelessWidget {
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(name,
style: const TextStyle(
fontSize: 16.0, color: MyTheme.accent50)))),
style: TextStyle(
fontSize: 16.0,
fontWeight:
Theme.of(context).brightness == Brightness.dark
? FontWeight.bold
: null,
color: MyTheme.accent80)))),
Expanded(
flex: 2,
child: FittedBox(
@ -398,7 +404,7 @@ class ConnectionManager extends StatelessWidget {
},
icon: const Icon(
Icons.chat,
color: MyTheme.accent80,
color: MyTheme.accent,
)))
],
),
@ -406,7 +412,9 @@ class ConnectionManager extends StatelessWidget {
? const SizedBox.shrink()
: Text(
translate("android_new_connection_tip"),
style: const TextStyle(color: Colors.black54),
style: Theme.of(globalKey.currentContext!)
.textTheme
.bodyMedium,
),
client.authorized
? ElevatedButton.icon(
@ -460,8 +468,8 @@ class PaddingCard extends StatelessWidget {
titleIcon != null
? Padding(
padding: const EdgeInsets.only(right: 10),
child: Icon(titleIcon,
color: MyTheme.accent80, size: 30))
child:
Icon(titleIcon, color: MyTheme.accent, size: 30))
: const SizedBox.shrink(),
Text(
title!,
@ -469,7 +477,7 @@ class PaddingCard extends StatelessWidget {
fontFamily: 'WorkSans',
fontWeight: FontWeight.bold,
fontSize: 20,
color: MyTheme.accent80,
color: MyTheme.accent,
),
)
],

View File

@ -2,12 +2,14 @@ import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';
import 'package:settings_ui/settings_ui.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../common.dart';
import '../../common/widgets/dialog.dart';
import '../../models/model.dart';
import '../../models/platform_model.dart';
import '../widgets/dialog.dart';
@ -25,14 +27,15 @@ class SettingsPage extends StatefulWidget implements PageShape {
final appBarActions = [ScanButton()];
@override
_SettingsState createState() => _SettingsState();
State<SettingsPage> createState() => _SettingsState();
}
const url = 'https://rustdesk.com/';
final _hasIgnoreBattery = androidVersion >= 26;
var _ignoreBatteryOpt = false;
var _enableAbr = false;
var _isDarkMode = false;
var _denyLANDiscovery = false;
var _onlyWhiteList = false;
class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
String? username;
@ -60,7 +63,19 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
_enableAbr = enableAbrRes;
}
// _isDarkMode = MyTheme.currentDarkMode(); // TODO
final denyLanDiscovery = !option2bool('enable-lan-discovery',
await bind.mainGetOption(key: 'enable-lan-discovery'));
if (denyLanDiscovery != _denyLANDiscovery) {
update = true;
_denyLANDiscovery = denyLanDiscovery;
}
final onlyWhiteList =
(await bind.mainGetOption(key: 'whitelist')).isNotEmpty;
if (onlyWhiteList != _onlyWhiteList) {
update = true;
_onlyWhiteList = onlyWhiteList;
}
if (update) {
setState(() {});
@ -100,16 +115,57 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
Provider.of<FfiModel>(context);
final enhancementsTiles = [
SettingsTile.switchTile(
title: Text(translate('Adaptive Bitrate') + ' (beta)'),
title: Text('${translate('Adaptive Bitrate')} (beta)'),
initialValue: _enableAbr,
onToggle: (v) {
bind.mainSetOption(key: "enable-abr", value: v ? "" : "N");
onToggle: (v) async {
await bind.mainSetOption(key: "enable-abr", value: v ? "" : "N");
final newValue = await bind.mainGetOption(key: "enable-abr") != "N";
setState(() {
_enableAbr = !_enableAbr;
_enableAbr = newValue;
});
},
)
];
final shareScreenTiles = [
SettingsTile.switchTile(
title: Text(translate('Deny LAN Discovery')),
initialValue: _denyLANDiscovery,
onToggle: (v) async {
await bind.mainSetOption(
key: "enable-lan-discovery",
value: bool2option("enable-lan-discovery", !v));
final newValue = !option2bool('enable-lan-discovery',
await bind.mainGetOption(key: 'enable-lan-discovery'));
setState(() {
_denyLANDiscovery = newValue;
});
},
),
SettingsTile.switchTile(
title: Row(children: [
Text(translate('Use IP Whitelisting')),
Offstage(
offstage: !_onlyWhiteList,
child: const Icon(Icons.warning_amber_rounded,
color: Color.fromARGB(255, 255, 204, 0)))
.marginOnly(left: 5)
]),
initialValue: _onlyWhiteList,
onToggle: (_) async {
update() async {
final onlyWhiteList =
(await bind.mainGetOption(key: 'whitelist')).isNotEmpty;
if (onlyWhiteList != _onlyWhiteList) {
setState(() {
_onlyWhiteList = onlyWhiteList;
});
}
}
changeWhiteList(callback: update);
},
)
];
if (_hasIgnoreBattery) {
enhancementsTiles.insert(
0,
@ -152,7 +208,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
SettingsTile.navigation(
title: Text(username == null
? translate("Login")
: translate("Logout") + ' ($username)'),
: '${translate("Logout")} ($username)'),
leading: Icon(Icons.person),
onPressed: (context) {
if (username == null) {
@ -177,18 +233,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
onPressed: (context) {
showLanguageSettings(gFFI.dialogManager);
}),
SettingsTile.switchTile(
SettingsTile.navigation(
title: Text(translate('Dark Theme')),
leading: Icon(Icons.dark_mode),
initialValue: _isDarkMode,
onToggle: (v) {
setState(() {
_isDarkMode = !_isDarkMode;
// MyTheme.changeDarkMode(_isDarkMode); // TODO
});
onPressed: (context) {
showThemeSettings(gFFI.dialogManager);
},
)
]),
SettingsSection(
title: Text(translate("Share Screen")),
tiles: shareScreenTiles,
),
SettingsSection(
title: Text(translate("Enhancements")),
tiles: enhancementsTiles,
@ -232,7 +288,7 @@ void showLanguageSettings(OverlayDialogManager dialogManager) async {
final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>;
var lang = await bind.mainGetLocalOption(key: "lang");
dialogManager.show((setState, close) {
final setLang = (v) {
setLang(v) {
if (lang != v) {
setState(() {
lang = v;
@ -241,7 +297,8 @@ void showLanguageSettings(OverlayDialogManager dialogManager) async {
HomePage.homeKey.currentState?.refreshPages();
Future.delayed(Duration(milliseconds: 200), close);
}
};
}
return CustomAlertDialog(
title: SizedBox.shrink(),
content: Column(
@ -257,13 +314,41 @@ void showLanguageSettings(OverlayDialogManager dialogManager) async {
),
actions: []);
}, backDismiss: true, clickMaskDismiss: true);
} catch (_e) {}
} catch (e) {
//
}
}
void showThemeSettings(OverlayDialogManager dialogManager) async {
var themeMode = MyTheme.getThemeModePreference();
dialogManager.show((setState, close) {
setTheme(v) {
if (themeMode != v) {
setState(() {
themeMode = v;
});
MyTheme.changeDarkMode(themeMode);
Future.delayed(Duration(milliseconds: 200), close);
}
}
return CustomAlertDialog(
title: SizedBox.shrink(),
contentPadding: 10,
content: Column(children: [
getRadio('Light', ThemeMode.light, themeMode, setTheme),
getRadio('Dark', ThemeMode.dark, themeMode, setTheme),
getRadio('Follow System', ThemeMode.system, themeMode, setTheme)
]),
actions: []);
}, backDismiss: true, clickMaskDismiss: true);
}
void showAbout(OverlayDialogManager dialogManager) {
dialogManager.show((setState, close) {
return CustomAlertDialog(
title: Text(translate('About') + ' RustDesk'),
title: Text('${translate('About')} RustDesk'),
content: Wrap(direction: Axis.vertical, spacing: 12, children: [
Text('Version: $version'),
InkWell(
@ -429,7 +514,7 @@ void showLogin(OverlayDialogManager dialogManager) {
),
controller: nameController,
),
PasswordWidget(controller: passwordController),
PasswordWidget(controller: passwordController, autoFocus: false),
]),
actions: (loading
? <Widget>[CircularProgressIndicator()]

View File

@ -6,8 +6,7 @@ import '../../models/model.dart';
import '../../models/platform_model.dart';
void clientClose(OverlayDialogManager dialogManager) {
msgBox('', 'Close', 'Are you sure to close the connection?',
dialogManager);
msgBox('', 'Close', 'Are you sure to close the connection?', dialogManager);
}
void showSuccess() {
@ -131,7 +130,7 @@ void setTemporaryPasswordLengthDialog(
if (index < 0) index = 0;
length = lengths[index];
dialogManager.show((setState, close) {
final setLength = (newValue) {
setLength(newValue) {
final oldValue = length;
if (oldValue == newValue) return;
setState(() {
@ -143,7 +142,8 @@ void setTemporaryPasswordLengthDialog(
close();
showSuccess();
});
};
}
return CustomAlertDialog(
title: Text(translate("Set temporary password length")),
content: Column(
@ -230,12 +230,14 @@ void wrongPasswordDialog(String id, OverlayDialogManager dialogManager) {
}
class PasswordWidget extends StatefulWidget {
PasswordWidget({Key? key, required this.controller}) : super(key: key);
PasswordWidget({Key? key, required this.controller, this.autoFocus = true})
: super(key: key);
final TextEditingController controller;
final bool autoFocus;
@override
_PasswordWidgetState createState() => _PasswordWidgetState();
State<PasswordWidget> createState() => _PasswordWidgetState();
}
class _PasswordWidgetState extends State<PasswordWidget> {
@ -245,7 +247,9 @@ class _PasswordWidgetState extends State<PasswordWidget> {
@override
void initState() {
super.initState();
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
if (widget.autoFocus) {
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
}
}
@override

View File

@ -166,10 +166,12 @@ class ChatModel with ChangeNotifier {
if (_isShowChatPage) {
_isShowChatPage = !_isShowChatPage;
notifyListeners();
await windowManager.setSize(Size(400, 600));
await windowManager.setSize(Size(300, 400));
await windowManager.setAlignment(Alignment.topRight);
} else {
await windowManager.setSize(Size(800, 600));
await windowManager.setSize(Size(600, 400));
await Future.delayed(Duration(milliseconds: 100));
await windowManager.setAlignment(Alignment.topRight);
_isShowChatPage = !_isShowChatPage;
notifyListeners();
}

View File

@ -436,7 +436,7 @@ class ServerModel with ChangeNotifier {
clientInfo(client),
Text(
translate("android_new_connection_tip"),
style: const TextStyle(color: Colors.black54),
style: Theme.of(globalKey.currentContext!).textTheme.bodyMedium,
),
],
),

View File

@ -53,13 +53,19 @@ static void my_application_activate(GApplication* application) {
// bdw->setCustomFrame(true); // <-- add this line
gtk_window_set_default_size(window, 800, 600); // <-- comment this line
gtk_widget_show(GTK_WIDGET(window));
gtk_widget_set_opacity(GTK_WIDGET(window), 0);
g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
auto border_frame = gtk_frame_new(nullptr);
gtk_frame_set_shadow_type(GTK_FRAME(border_frame), GTK_SHADOW_ETCHED_IN);
gtk_container_add(GTK_CONTAINER(border_frame), GTK_WIDGET(view));
gtk_widget_show(GTK_WIDGET(border_frame));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(border_frame));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1023 B

After

Width:  |  Height:  |  Size: 909 B

View File

@ -243,8 +243,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: fee851fa43116e0b91c39acd0ec37063dc6015f8
resolved-ref: fee851fa43116e0b91c39acd0ec37063dc6015f8
ref: "74c10aa49fecc088992a9edef1f6da39f83df505"
resolved-ref: "74c10aa49fecc088992a9edef1f6da39f83df505"
url: "https://github.com/Kingtous/rustdesk_desktop_multi_window"
source: git
version: "0.1.0"

View File

@ -64,7 +64,7 @@ dependencies:
desktop_multi_window:
git:
url: https://github.com/Kingtous/rustdesk_desktop_multi_window
ref: fee851fa43116e0b91c39acd0ec37063dc6015f8
ref: 74c10aa49fecc088992a9edef1f6da39f83df505
freezed_annotation: ^2.0.3
tray_manager:
git:

View File

@ -90,12 +90,12 @@ BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.carriez" "\0"
VALUE "FileDescription", "flutter_hbb" "\0"
VALUE "FileDescription", "rustdesk" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "flutter_hbb" "\0"
VALUE "InternalName", "rustdesk" "\0"
VALUE "LegalCopyright", "Copyright (C) 2022 com.carriez. All rights reserved." "\0"
VALUE "OriginalFilename", "flutter_hbb.exe" "\0"
VALUE "ProductName", "flutter_hbb" "\0"
VALUE "OriginalFilename", "rustdesk.exe" "\0"
VALUE "ProductName", "rustdesk" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END

View File

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

BIN
res/icon-margin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -36,7 +36,7 @@ def expand():
lang = os.path.basename(fn)[:-3]
if lang in ['en','cn']: continue
dict = get_lang(lang)
fw = open("%s.rs"%lang, "wt")
fw = open("./src/lang/%s.rs"%lang, "wt")
for line in open('./src/lang/cn.rs'):
line_strip = line.strip()
if line_strip.startswith('("'):

View File

@ -15,18 +15,7 @@ use hbb_common::{message_proto::Hash, ResultType};
use crate::flutter::{self, SESSIONS};
use crate::start_server;
use crate::ui_interface;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_interface::get_sound_inputs;
use crate::ui_interface::{
change_id, check_mouse_time, check_super_user_permission, default_video_save_directory,
discover, forget_password, get_api_server, get_app_name, get_async_job_status,
get_connect_status, get_fav, get_id, get_lan_peers, get_langs, get_license, get_local_option,
get_mouse_time, get_option, get_options, get_peer, get_peer_option, get_socks, get_uuid,
get_version, has_hwcodec, has_rendezvous_service, post_request, send_to_cm, set_local_option,
set_option, set_options, set_peer_option, set_permanent_password, set_socks, store_fav,
test_if_valid_server, update_temporary_password, using_public_server,
};
use crate::ui_interface::{self, *};
use crate::{
client::file_trait::FileManager,
flutter::{make_fd_to_json, session_add, session_start_},
@ -482,6 +471,9 @@ pub fn main_get_app_name() -> String {
get_app_name()
}
pub fn main_get_app_name_sync() -> SyncReturn<String> {
SyncReturn(get_app_name())
}
pub fn main_get_license() -> String {
get_license()
}
@ -563,14 +555,27 @@ pub fn main_get_peer_option(id: String, key: String) -> String {
get_peer_option(id, key)
}
pub fn main_get_peer_option_sync(id: String, key: String) -> SyncReturn<String> {
SyncReturn(get_peer_option(id, key))
}
pub fn main_set_peer_option(id: String, key: String, value: String) {
set_peer_option(id, key, value)
}
pub fn main_set_peer_option_sync(id: String, key: String, value: String) -> SyncReturn<bool> {
set_peer_option(id, key, value);
SyncReturn(true)
}
pub fn main_forget_password(id: String) {
forget_password(id)
}
pub fn main_peer_has_password(id: String) -> bool {
peer_has_password(id)
}
pub fn main_get_recent_peers() -> String {
if !config::APP_DIR.read().unwrap().is_empty() {
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers()
@ -699,10 +704,7 @@ fn main_broadcast_message(data: &HashMap<&str, &str>) {
}
pub fn main_change_theme(dark: String) {
main_broadcast_message(&HashMap::from([
("name", "theme"),
("dark", &dark),
]));
main_broadcast_message(&HashMap::from([("name", "theme"), ("dark", &dark)]));
send_to_cm(&crate::ipc::Data::Theme(dark));
}
@ -982,6 +984,48 @@ pub fn query_onlines(ids: Vec<String>) {
crate::rendezvous_mediator::query_online_states(ids, handle_query_onlines)
}
pub fn main_is_installed() -> SyncReturn<bool> {
SyncReturn(is_installed())
}
pub fn main_is_installed_lower_version() -> SyncReturn<bool> {
SyncReturn(is_installed_lower_version())
}
pub fn main_is_installed_daemon(prompt: bool) -> SyncReturn<bool> {
SyncReturn(is_installed_daemon(prompt))
}
pub fn main_is_process_trusted(prompt: bool) -> SyncReturn<bool> {
SyncReturn(is_process_trusted(prompt))
}
pub fn main_is_can_screen_recording(prompt: bool) -> SyncReturn<bool> {
SyncReturn(is_can_screen_recording(prompt))
}
pub fn main_is_share_rdp() -> SyncReturn<bool> {
SyncReturn(is_share_rdp())
}
pub fn main_is_rdp_service_open() -> SyncReturn<bool> {
SyncReturn(is_rdp_service_open())
}
pub fn main_goto_install() -> SyncReturn<bool> {
goto_install();
SyncReturn(true)
}
pub fn main_get_new_version() -> SyncReturn<String> {
SyncReturn(get_new_version())
}
pub fn main_update_me() -> SyncReturn<bool> {
update_me("".to_owned());
SyncReturn(true)
}
#[cfg(target_os = "android")]
pub mod server_side {
use jni::{

View File

@ -30,7 +30,8 @@ pub(super) fn start_listening() -> ResultType<()> {
if let Ok(msg_in) = Message::parse_from_bytes(&buf[0..len]) {
match msg_in.union {
Some(rendezvous_message::Union::PeerDiscovery(p)) => {
if p.cmd == "ping" {
if p.cmd == "ping" && Config::get_option("enable-lan-discovery").is_empty()
{
if let Some(self_addr) = get_ipaddr_by_peer(&addr) {
let mut msg_out = Message::new();
let peer = PeerDiscovery {

View File

@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "适应窗口"),
("General", "常规"),
("Security", "安全"),
("Acount", "账户"),
("Account", "账户"),
("Theme", "主题"),
("Dark Theme", "暗黑主题"),
("Dark", "黑暗"),
("Light", "明亮"),
("Follow System", "跟随系统"),
("Enable hardware codec", "使用硬件编解码"),
("Unlock Security Settings", "解锁安全设置"),
("Enable Audio", "允许传输音频"),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", "结束录屏"),
("Enable Recording Session", "允许录制会话"),
("Allow recording session", "允许录制会话"),
("Enable LAN Discovery", "允许局域网发现"),
("Deny LAN Discovery", "拒绝局域网发现"),
("Write a message", "输入聊天消息"),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Je třeba restartovat"),
("Unsupported display server ", "Nepodporovaný zobrazovací server"),
("x11 expected", "očekávány x11"),
("Port", "Číslo portu"),
("Port", ""),
("Settings", "Nastavení"),
("Username", "Uživatelské jméno"),
("Invalid port", "Neplatné číslo portu"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Zastavení služby automaticky ukončí veškerá navázaná spojení."),
("android_version_audio_tip", "Vámi nyní používaná verze systému Android nepodporuje zachytávání zvuku přejděte na Android 10 nebo novější."),
("android_start_service_tip", "Službu pro sdílení obrazovky spustíte klepnutím na [Spustit službu] nebo UDĚLTE pověření pro [Zachytávání obsahu obrazovky]."),
("Account", "Účet"),
("Account", ""),
("Overwrite", "Přepsat"),
("This file exists, skip or overwrite this file?", "Tento soubor existuje přeskočit ho nebo přepsat?"),
("Quit", "Ukončit"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Měřítko adaptivní"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Genstart krævet"),
("Unsupported display server ", "Ikke-understøttet displayserver"),
("x11 expected", "X11 Forventet"),
("Port", "Port"),
("Port", ""),
("Settings", "Indstillinger"),
("Username", " Brugernavn"),
("Invalid port", "Ugyldig port"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Ved at lukke tjenesten lukkes alle fremstillede forbindelser automatisk."),
("android_version_audio_tip", "Den aktuelle Android -version understøtter ikke lydoptagelse, skal du opdatere om Android 10 eller højere."),
("android_start_service_tip", "Tryk på [Start Service] eller åbn autorisationen [skærmoptagelse] for at starte skærmudgivelsen."),
("Account", "Konto"),
("Account", ""),
("Overwrite", "Overskriv"),
("This file exists, skip or overwrite this file?", "Denne fil findes, springer over denne fil eller overskriver?"),
("Quit", "Afslut"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Skala adaptiv"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Neustart erforderlich"),
("Unsupported display server ", "Nicht unterstützter Display-Server"),
("x11 expected", "X11 erwartet"),
("Port", "Port"),
("Port", ""),
("Settings", "Einstellungen"),
("Username", " Benutzername"),
("Invalid port", "Ungültiger Port"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Durch das Deaktivieren des Dienstes werden automatisch alle hergestellten Verbindungen getrennt."),
("android_version_audio_tip", "Ihre Android-Version unterstützt keine Audioaufnahme, bitte aktualisieren Sie auf Android 10 oder höher, falls möglich."),
("android_start_service_tip", "Tippen Sie auf [Dienst aktivieren] oder aktivieren Sie die Berechtigung [Bildschirmzugr.], um den Bildschirmfreigabedienst zu starten."),
("Account", "Konto"),
("Account", ""),
("Overwrite", "Überschreiben"),
("This file exists, skip or overwrite this file?", "Diese Datei existiert; überspringen oder überschreiben?"),
("Quit", "Beenden"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Adaptiv skalieren"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Restarto deviga"),
("Unsupported display server ", "La aktuala bilda servilo ne estas subtenita"),
("x11 expected", "Bonvolu uzi x11"),
("Port", "Pordo"),
("Port", ""),
("Settings", "Agordoj"),
("Username", " Uzanta nomo"),
("Invalid port", "Pordo nevalida"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Skalo adapta"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Reinicio requerido"),
("Unsupported display server ", "Servidor de visualización no compatible"),
("x11 expected", "x11 necesario"),
("Port", "Puerto"),
("Port", ""),
("Settings", "Ajustes"),
("Username", " Nombre de usuario"),
("Invalid port", "Puerto inválido"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Cerrar el servicio cerrará automáticamente todas las conexiones establecidas."),
("android_version_audio_tip", "La versión actual de Android no admite la captura de audio, actualice a Android 10 o posterior."),
("android_start_service_tip", "Toque el permiso [Iniciar servicio] o ABRIR [Captura de pantalla] para iniciar el servicio de uso compartido de pantalla."),
("Account", "Cuenta"),
("Account", ""),
("Overwrite", "Sobrescribir"),
("This file exists, skip or overwrite this file?", "Este archivo existe, ¿omitir o sobrescribir este archivo?"),
("Quit", "Salir"),
@ -289,23 +289,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
("Connection not allowed", "Conexión no disponible"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Connection not allowed", "Conexión no disponible"),
("Use temporary password", "Usar contraseña temporal"),
("Use permanent password", "Usar contraseña permamente"),
("Use both passwords", "Usar ambas comtraseñas"),
@ -337,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Adaptable a escala"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -367,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Redémarrage pour prendre effet"),
("Unsupported display server ", "Le serveur d'affichage actuel n'est pas pris en charge"),
("x11 expected", "Veuillez passer à x11"),
("Port", "Port"),
("Port", ""),
("Settings", "Paramètres"),
("Username", " Nom d'utilisateur"),
("Invalid port", "Port invalide"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "La fermeture du service fermera automatiquement toutes les connexions établies."),
("android_version_audio_tip", "La version actuelle d'Android ne prend pas en charge la capture audio, veuillez passer à Android 10 ou supérieur."),
("android_start_service_tip", "Appuyez sur [Démarrer le service] ou sur l'autorisation OUVRIR [Capture d'écran] pour démarrer le service de partage d'écran."),
("Account", "Compte"),
("Account", ""),
("Overwrite", "Écraser"),
("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"),
("Quit", "Quitter"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Échelle adaptative"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Újraindítás szükséges"),
("Unsupported display server ", "Nem támogatott kijelző szerver"),
("x11 expected", "x11-re számítottt"),
("Port", "Port"),
("Port", ""),
("Settings", "Beállítások"),
("Username", "Felhasználónév"),
("Invalid port", "Érvénytelen port"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "A szolgáltatás bezárása automatikusan szétkapcsol minden létező kapcsolatot."),
("android_version_audio_tip", "A jelenlegi Android verzió nem támogatja a hangrögzítést, kérlek frissíts legalább Android 10-re, vagy egy újabb verzióra."),
("android_start_service_tip", "Nyomj a [Szolgáltatás Indítása] opcióra, vagy adj [Képernyőrözítési] jogot az applikációnak hogy elindítsd a képernyőmegosztó szolgáltatást."),
("Account", "Fiók"),
("Account", ""),
("Overwrite", "Felülírás"),
("This file exists, skip or overwrite this file?", "Ez a fájl már létezik, skippeljünk, vagy felülírjuk ezt a fájlt?"),
("Quit", "Kilépés"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Skála adaptív"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Diperlukan boot ulang"),
("Unsupported display server ", "Server tampilan tidak didukung "),
("x11 expected", "x11 diharapkan"),
("Port", "Port"),
("Port", ""),
("Settings", "Pengaturan"),
("Username", "Username"),
("Invalid port", "Kesalahan port"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Menutup layanan akan secara otomatis menutup semua koneksi yang dibuat."),
("android_version_audio_tip", "Versi Android saat ini tidak mendukung pengambilan audio, harap tingkatkan ke Android 10 atau lebih tinggi."),
("android_start_service_tip", "Ketuk izin [Mulai Layanan] atau BUKA [Tangkapan Layar] untuk memulai layanan berbagi layar."),
("Account", "Akun"),
("Account", ""),
("Overwrite", "Timpa"),
("This file exists, skip or overwrite this file?", "File ini sudah ada, lewati atau timpa file ini?"),
("Quit", "Keluar"),
@ -289,23 +289,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"),
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
("Connection not allowed", "Koneksi tidak dijinkan"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Connection not allowed", "Koneksi tidak dijinkan"),
("Use temporary password", "Gunakan kata sandi sementara"),
("Use permanent password", "Gunakan kata sandi permanaen"),
("Use both passwords", "Gunakan kedua kata sandi "),
@ -337,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Skala adaptif"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -367,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Riavvio necessario"),
("Unsupported display server ", "Display server non supportato"),
("x11 expected", "x11 necessario"),
("Port", "Porta"),
("Port", ""),
("Settings", "Impostazioni"),
("Username", " Nome utente"),
("Invalid port", "Porta non valida"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "La chiusura del servizio chiuderà automaticamente tutte le connessioni stabilite."),
("android_version_audio_tip", "L'attuale versione di Android non supporta l'acquisizione audio, esegui l'upgrade ad Android 10 o versioni successive."),
("android_start_service_tip", "Toccare [Avvia servizio] o APRI l'autorizzazione [Cattura schermo] per avviare il servizio di condivisione dello schermo."),
("Account", "Account"),
("Account", ""),
("Overwrite", "Sovrascrivi"),
("This file exists, skip or overwrite this file?", "Questo file esiste, saltare o sovrascrivere questo file?"),
("Quit", "Esci"),
@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", "Connessione non consentita"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "Usa password temporanea"),
("Use permanent password", "Usa password permanente"),
("Use both passwords", "Usa entrambe le password"),
@ -301,6 +304,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Are you sure you want to restart", "Sei sicuro di voler riavviare?"),
("Restarting Remote Device", "Il dispositivo remoto si sta riavviando"),
("remote_restarting_tip", "Riavviare il dispositivo remoto"),
("Copied", ""),
("Exit Fullscreen", "Esci dalla modalità schermo intero"),
("Fullscreen", "A schermo intero"),
("Mobile Actions", "Azioni mobili"),
@ -318,14 +322,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Insecure Connection", "Connessione insicura"),
("Scale original", "Scala originale"),
("Scale adaptive", "Scala adattiva"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -353,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "オリジナル"),
("Shrink", "縮小"),
("Stretch", "伸縮"),
("Scrollbar", ""),
("ScrollAuto", ""),
("Good image quality", "画質優先"),
("Balanced", "バランス"),
("Optimize reaction time", "速度優先"),
@ -185,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "再起動が必要"),
("Unsupported display server ", "サポートされていないディスプレイサーバー"),
("x11 expected", "X11 が必要です"),
("Port", "ポート"),
("Port", ""),
("Settings", "設定"),
("Username", "ユーザー名"),
("Invalid port", "無効なポート"),
@ -266,11 +268,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "サービスを停止すると、現在確立されている接続が全て自動的に閉じられます。"),
("android_version_audio_tip", "現在のAndroidバージョンでは音声キャプチャはサポートされていません。Android 10以降にアップグレードしてください。"),
("android_start_service_tip", "「サービスを開始」をタップするか「画面キャプチャ」を開くと、画面共有サービスが開始されます。"),
("Account", "アカウント"),
("Account", ""),
("Overwrite", "上書き"),
("This file exists, skip or overwrite this file?", "このファイルは存在しています。スキップするか上書きしますか?"),
("Quit", "終了"),
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), // @TODO: Update url when someone translates the document
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), // @TODO: Update url when someone translates the docum"),
("Help", "ヘルプ"),
("Failed", "失敗"),
("Succeeded", "成功"),
@ -302,6 +304,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Are you sure you want to restart", "本当に再起動しますか"),
("Restarting Remote Device", "リモート端末を再起動中"),
("remote_restarting_tip", "リモート端末は再起動中です。このメッセージボックスを閉じて、しばらくした後に固定のパスワードを使用して再接続してください。"),
("Copied", ""),
("Exit Fullscreen", "全画面表示を終了"),
("Fullscreen", "全画面表示"),
("Mobile Actions", "モバイル アクション"),
@ -321,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "フィットウィンドウ"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -351,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "원본"),
("Shrink", "축소"),
("Stretch", "확대"),
("Scrollbar", ""),
("ScrollAuto", ""),
("Good image quality", "최적 이미지 품질"),
("Balanced", "균형"),
("Optimize reaction time", "반응 시간 최적화"),
@ -185,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "재부팅이 필요합니다"),
("Unsupported display server ", "지원하지 않는 디스플레이 서버"),
("x11 expected", "x11 예상됨"),
("Port", "포트"),
("Port", ""),
("Settings", "설정"),
("Username", "사용자명"),
("Invalid port", "유효하지 않은 포트"),
@ -266,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "서비스를 종료하면 모든 연결이 자동으로 닫힙니다."),
("android_version_audio_tip", "현재 Android 버전은 오디오 캡처를 지원하지 않습니다. Android 10 이상으로 업그레이드하십시오."),
("android_start_service_tip", "[서비스 시작] 또는 [화면 캡처] 권한을 눌러 화면 공유 서비스를 시작합니다."),
("Account", "계정"),
("Account", ""),
("Overwrite", "덮어쓰기"),
("This file exists, skip or overwrite this file?", "해당 파일이 이미 존재합니다, 넘어가거나 덮어쓰시겠습니까?"),
("Quit", "종료"),
@ -288,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "배터리 최적화 무시하기"),
("android_open_battery_optimizations_tip", "해당 기능을 비활성화하려면 RustDesk 응용 프로그램 설정 페이지로 이동하여 [배터리]에서 [제한 없음] 선택을 해제하십시오."),
("Connection not allowed", "연결이 허용되지 않음"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "임시 비밀번호 사용"),
("Use permanent password", "영구 비밀번호 사용"),
("Use both passwords", "두 비밀번호 (임시/영구) 사용"),
@ -299,6 +304,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Are you sure you want to restart", "정말로 재시작 하시겠습니까"),
("Restarting Remote Device", "원격 기기를 다시 시작하는중"),
("remote_restarting_tip", "원격 장치를 다시 시작하는 중입니다. 이 메시지 상자를 닫고 잠시 후 영구 비밀번호로 다시 연결하십시오."),
("Copied", ""),
("Exit Fullscreen", "전체 화면 종료"),
("Fullscreen", "전체화면"),
("Mobile Actions", "모바일 액션"),
@ -318,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "맞는 창"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -348,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -329,5 +329,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("General", ""),
("Security", ""),
("Account", "Есепкі"),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
("Temporary Password Length", ""),
("Unlock Network Settings", ""),
("Server", ""),
("Direct IP Access", ""),
("Proxy", ""),
("Port", "Порт"),
("Apply", ""),
("Disconnect all devices?", ""),
("Clear", ""),
("Audio Input Device", ""),
("Deny remote access", ""),
("Use IP Whitelisting", ""),
("Network", ""),
("Enable RDP", ""),
("Pin menubar", "Мәзір жолағын бекіту"),
("Unpin menubar", "Мәзір жолағын босату"),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Wymagany restart"),
("Unsupported display server ", "Nieobsługiwany serwer wyświetlania "),
("x11 expected", "oczekiwane X11"),
("Port", "Port"),
("Port", ""),
("Settings", "Ustawienia"),
("Username", "Nazwa użytkownika"),
("Invalid port", "Nieprawidłowy port"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "android_stop_service_tip"),
("android_version_audio_tip", "android_version_audio_tip"),
("android_start_service_tip", "android_start_service_tip"),
("Account", "Konto"),
("Account", ""),
("Overwrite", "Nadpisz"),
("This file exists, skip or overwrite this file?", "Ten plik istnieje, pominąć czy nadpisać ten plik?"),
("Quit", "Zrezygnuj"),
@ -289,19 +289,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Zachowaj usługę w tle RustDesk"),
("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"),
("android_open_battery_optimizations_tip", "android_open_battery_optimizations_tip"),
("Random Password After Session", "Losowe hasło po sesji"),
("Keep", "Zachowaj"),
("Update", "Aktualizacja"),
("Disable", "Wyłącz"),
("Onetime Password", "Hasło jednorazowe"),
("Verification Method", "Metoda weryfikacji"),
("Enable security password", "Włącz hasło zabezpieczające"),
("Enable random password", "Włącz losowe hasło"),
("Enable onetime password", "Włącz hasło jednorazowe"),
("Disable onetime password", "Wyłącz hasło jednorazowe"),
("Activate onetime password", "Aktywuj hasło jednorazowe"),
("Set security password", "Ustaw hasło zabezpieczające"),
("Connection not allowed", "Połączenie niedozwolone"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),
("Set permanent password", ""),
("Set temporary password length", ""),
("Enable Remote Restart", ""),
("Allow remote restart", ""),
("Restart Remote Device", ""),
("Are you sure you want to restart", ""),
("Restarting Remote Device", ""),
("remote_restarting_tip", ""),
("Copied", ""),
("Exit Fullscreen", "Wyłączyć tryb pełnoekranowy"),
("Fullscreen", "Pełny ekran"),
@ -322,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Skala adaptacyjna"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -352,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Original"),
("Shrink", "Reduzir"),
("Stretch", "Aumentar"),
("Scrollbar", ""),
("ScrollAuto", ""),
("Good image quality", "Qualidade visual boa"),
("Balanced", "Equilibrada"),
("Optimize reaction time", "Optimizar tempo de reacção"),
@ -127,7 +129,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Set Password", "Definir palavra-chave"),
("OS Password", "Senha do SO"),
("install_tip", "Devido ao UAC, o RustDesk não funciona correctamente em alguns casos. Para evitar o UAC, por favor clique no botão abaixo para instalar o RustDesk no sistema."),
("Click to update", "Clique para fazer a actualização"),
("Click to upgrade", ""),
("Click to download", "Clique para carregar"),
("Click to update", "Clique para fazer a actualização"),
("Configure", "Configurar"),
@ -185,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Reinicialização necessária"),
("Unsupported display server ", "Servidor de display não suportado"),
("x11 expected", "x11 em falha"),
("Port", "Porta"),
("Port", ""),
("Settings", "Configurações"),
("Username", "Nome de utilizador"),
("Invalid port", "Porta inválida"),
@ -266,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Fechar o serviço irá automaticamente fechar todas as ligações estabelecidas."),
("android_version_audio_tip", "A versão atual do Android não suporta captura de áudio, por favor actualize para o Android 10 ou maior."),
("android_start_service_tip", "Toque [Iniciar Serviço] ou abra a permissão [Captura de Ecran] para iniciar o serviço de partilha de ecran."),
("Account", "Conta"),
("Account", ""),
("Overwrite", "Substituir"),
("This file exists, skip or overwrite this file?", "Este ficheiro já existe, ignorar ou substituir este ficheiro?"),
("Quit", "Saída"),
@ -288,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", "Ligação não autorizada"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "Utilizar palavra-chave temporária"),
("Use permanent password", "Utilizar palavra-chave permanente"),
("Use both passwords", "Utilizar ambas as palavras-chave"),
@ -299,6 +304,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Are you sure you want to restart", "Tem a certeza que pretende reiniciar"),
("Restarting Remote Device", "A reiniciar sistema remoto"),
("remote_restarting_tip", ""),
("Copied", ""),
("Exit Fullscreen", "Sair da tela cheia"),
("Fullscreen", "Tela cheia"),
("Mobile Actions", "Ações para celular"),
@ -318,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Escala adaptável"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -348,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Reinicialização necessária"),
("Unsupported display server ", "Servidor de display não suportado"),
("x11 expected", "x11 esperado"),
("Port", "Porta"),
("Port", ""),
("Settings", "Configurações"),
("Username", "Nome de usuário"),
("Invalid port", "Porta inválida"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Fechar o serviço irá automaticamente fechar todas as conexões estabelecidas."),
("android_version_audio_tip", "A versão atual do Android não suporta captura de áudio, por favor atualize para o Android 10 ou maior."),
("android_start_service_tip", "Toque [Iniciar Serviço] ou abra a permissão [Captura de Tela] para iniciar o serviço de compartilhamento de tela."),
("Account", "Conta"),
("Account", ""),
("Overwrite", "Substituir"),
("This file exists, skip or overwrite this file?", "Este arquivo existe, pular ou substituir este arquivo?"),
("Quit", "Saída"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", ""),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Требуется перезагрузка"),
("Unsupported display server ", "Неподдерживаемый сервер дисплея"),
("x11 expected", "Ожидается X11"),
("Port", "Порт"),
("Port", ""),
("Settings", "Настройки"),
("Username", "Имя пользователя"),
("Invalid port", "Неверный порт"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Закрытие службы автоматически закроет все установленные соединения."),
("android_version_audio_tip", "Текущая версия Android не поддерживает захват звука, обновите ее до Android 10 или выше."),
("android_start_service_tip", "Нажмите [Запуск промежуточного сервера] или ОТКРЫТЬ разрешение [Захват экрана], чтобы запустить службу демонстрации экрана."),
("Account", "Аккаунт"),
("Account", ""),
("Overwrite", "Перезаписать"),
("This file exists, skip or overwrite this file?", "Этот файл существует, пропустить или перезаписать файл?"),
("Quit", "Выйти"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Масштаб адаптивный"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Vyžaduje sa reštart"),
("Unsupported display server ", "Nepodporovaný zobrazovací (display) server"),
("x11 expected", "očakáva sa x11"),
("Port", "Port"),
("Port", ""),
("Settings", "Nastavenia"),
("Username", "Uživateľské meno"),
("Invalid port", "Neplatný port"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Zastavenie služby automaticky ukončí všetky naviazané spojenia."),
("android_version_audio_tip", "Vaša verzia Androidu neumožňuje zaznamenávanie zvuku. Prejdite na verziu Android 10 alebo vyššiu."),
("android_start_service_tip", "Klepnite na [Spustiť službu] alebo OTVORTE oprávnenie [Zachytávanie obsahu obrazovky], aby sa aktivovala služba zdieľania obrazovky."),
("Account", "Účet"),
("Account", ""),
("Overwrite", "Prepísať"),
("This file exists, skip or overwrite this file?", "Preskočiť alebo prepísať existujúci súbor?"),
("Quit", "Ukončiť"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Prispôsobivá mierka"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", ""),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Yeniden başlatma gerekli"),
("Unsupported display server ", "Desteklenmeyen görüntü sunucusu"),
("x11 expected", "x11 bekleniyor"),
("Port", "Port"),
("Port", ""),
("Settings", "Ayarlar"),
("Username", "Kullanıcı Adı"),
("Invalid port", "Geçersiz port"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Hizmetin kapatılması, kurulan tüm bağlantıları otomatik olarak kapatacaktır."),
("android_version_audio_tip", "Mevcut Android sürümü ses yakalamayı desteklemiyor, lütfen Android 10 veya sonraki bir sürüme yükseltin."),
("android_start_service_tip", "Ekran paylaşım hizmetini başlatmak için [Hizmeti Başlat] veya AÇ [Ekran Yakalama] iznine dokunun."),
("Account", "Hesap"),
("Account", ""),
("Overwrite", "üzerine yaz"),
("This file exists, skip or overwrite this file?", "Bu dosya var, bu dosya atlansın veya üzerine yazılsın mı?"),
("Quit", "Çıkış"),
@ -289,23 +289,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
("Connection not allowed", "bağlantıya izin verilmedi"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Connection not allowed", "bağlantıya izin verilmedi"),
("Use temporary password", "Geçici şifre kullan"),
("Use permanent password", "Kalıcı şifre kullan"),
("Use both passwords", "İki şifreyide kullan"),
@ -337,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Ölçek uyarlanabilir"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -367,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "需要重新啟動"),
("Unsupported display server ", "不支援顯示伺服器"),
("x11 expected", "預期 x11"),
("Port", "連接埠"),
("Port", "端口"),
("Settings", "設定"),
("Username", "使用者名稱"),
("Invalid port", "連接埠無效"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "關閉服務將自動關閉所有已建立的連接。"),
("android_version_audio_tip", "目前的 Android 版本不支持音訊錄製,請升級至 Android 10 或以上版本。"),
("android_start_service_tip", "點擊 「啟動服務」 或啟用 「畫面錄製」 權限以開啟手機畫面共享服務。"),
("Account", ""),
("Account", ""),
("Overwrite", "覆寫"),
("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要跳過或是覆寫此檔案嗎?"),
("Quit", "退出"),
@ -324,14 +324,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "適應窗口"),
("General", "常規"),
("Security", "安全"),
("Acount", "賬戶"),
("Account", "賬戶"),
("Theme", "主題"),
("Dark Theme", "暗黑主題"),
("Dark", "黑暗"),
("Light", "明亮"),
("Follow System", "跟隨系統"),
("Enable hardware codec", "使用硬件編解碼"),
("Unlock Security Settings", "解鎖安全設置"),
("Enable Audio", "允許傳輸音頻"),
("Temporary Password Length", "临时密码长"),
("Unlock Network Settings", "臨時密碼長度"),
("Temporary Password Length", "臨時密碼長"),
("Unlock Network Settings", "解鎖網絡設置"),
("Server", "服務器"),
("Direct IP Access", "IP直接訪問"),
("Proxy", "代理"),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", "結束錄屏"),
("Enable Recording Session", "允許錄製會話"),
("Allow recording session", "允許錄製會話"),
("Enable LAN Discovery", "允許局域網發現"),
("Deny LAN Discovery", "拒絕局域網發現"),
("Write a message", "輸入聊天消息"),
].iter().cloned().collect();
}

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Yêu cầu khởi động lại"),
("Unsupported display server ", "Máy chủ hiển thị không đuợc hỗ trọ"),
("x11 expected", "Cần x11"),
("Port", "Cổng"),
("Port", ""),
("Settings", "Cài đặt"),
("Username", "Tên người dùng"),
("Invalid port", "Cổng không hợp lệ"),
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("android_stop_service_tip", "Đóng dịch vụ sẽ tự động đóng tất cả các kết nối đã thiết lập."),
("android_version_audio_tip", "Phiên bản Android hiện tại không hỗ trợ ghi âm, vui lòng nâng cấp lên Android 10 trở lên."),
("android_start_service_tip", "Nhấn vào [Bắt đầu dịch vụ] hoặc MỞ quyền [Ghi màn hình] để bắt đầu dịch vụ chia sẻ màn hình."),
("Account", "Tài khoản"),
("Account", ""),
("Overwrite", "Ghi đè"),
("This file exists, skip or overwrite this file?", "Tệp tin này đã tồn tại, bạn có muốn bỏ qua hay ghi đè lên tệp tin này?"),
("Quit", "Thoát"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Quy mô thích ứng"),
("General", ""),
("Security", ""),
("Acount", ""),
("Account", ""),
("Theme", ""),
("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""),
("Unlock Security Settings", ""),
("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""),
("Enable Recording Session", ""),
("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect();
}

View File

@ -297,6 +297,7 @@ class MyIdMenu: Reactor.Component {
<li #enable-file-transfer><span>{svg_checkmark}</span>{translate('Enable File Transfer')}</li>
<li #enable-remote-restart><span>{svg_checkmark}</span>{translate('Enable Remote Restart')}</li>
<li #enable-tunnel><span>{svg_checkmark}</span>{translate('Enable TCP Tunneling')}</li>
<li #enable-lan-discovery><span>{svg_checkmark}</span>{translate('Enable LAN Discovery')}</li>
<AudioInputs />
<Enhancements />
<li #allow-remote-config-modification><span>{svg_checkmark}</span>{translate('Enable remote configuration modification')}</li>

View File

@ -338,10 +338,13 @@ pub fn set_socks(proxy: String, username: String, password: String) {
.ok();
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[inline]
pub fn is_installed() -> bool {
crate::platform::is_installed()
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
return crate::platform::is_installed();
}
false
}
#[inline]