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/build/**
flatpak/.flatpak-builder/shared-modules/** flatpak/.flatpak-builder/shared-modules/**
flatpak/.flatpak-builder/shared-modules/*.tar.xz 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 'package:window_manager/window_manager.dart';
import 'common/widgets/overlay.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/model.dart';
import 'models/platform_model.dart'; import 'models/platform_model.dart';
@ -76,59 +78,22 @@ class IconFont {
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> { class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
const ColorThemeExtension({ const ColorThemeExtension({
required this.bg,
required this.grayBg,
required this.text,
required this.lightText,
required this.lighterText,
required this.placeholder,
required this.border, required this.border,
}); });
final Color? bg;
final Color? grayBg;
final Color? text;
final Color? lightText;
final Color? lighterText;
final Color? placeholder;
final Color? border; final Color? border;
static const light = ColorThemeExtension( 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), border: Color(0xFFCCCCCC),
); );
static const dark = ColorThemeExtension( 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), border: Color(0xFF555555),
); );
@override @override
ThemeExtension<ColorThemeExtension> copyWith( ThemeExtension<ColorThemeExtension> copyWith({Color? border}) {
{Color? bg,
Color? grayBg,
Color? text,
Color? lightText,
Color? lighterText,
Color? placeholder,
Color? border}) {
return ColorThemeExtension( 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, border: border ?? this.border,
); );
} }
@ -140,12 +105,6 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
return this; return this;
} }
return ColorThemeExtension( 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), border: Color.lerp(border, other.border, t),
); );
} }
@ -170,6 +129,14 @@ class MyTheme {
static ThemeData lightTheme = ThemeData( static ThemeData lightTheme = ThemeData(
brightness: Brightness.light, 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, primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: const TabBarTheme( tabBarTheme: const TabBarTheme(
@ -177,6 +144,12 @@ class MyTheme {
), ),
splashColor: Colors.transparent, splashColor: Colors.transparent,
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
splashFactory: isDesktop ? NoSplash.splashFactory : null,
textButtonTheme: isDesktop
? TextButtonThemeData(
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
)
: null,
).copyWith( ).copyWith(
extensions: <ThemeExtension<dynamic>>[ extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.light, ColorThemeExtension.light,
@ -185,6 +158,13 @@ class MyTheme {
); );
static ThemeData darkTheme = ThemeData( static ThemeData darkTheme = ThemeData(
brightness: Brightness.dark, 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, primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: const TabBarTheme( tabBarTheme: const TabBarTheme(
@ -192,6 +172,12 @@ class MyTheme {
), ),
splashColor: Colors.transparent, splashColor: Colors.transparent,
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
splashFactory: isDesktop ? NoSplash.splashFactory : null,
textButtonTheme: isDesktop
? TextButtonThemeData(
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
)
: null,
).copyWith( ).copyWith(
extensions: <ThemeExtension<dynamic>>[ extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.dark, ColorThemeExtension.dark,
@ -1073,14 +1059,38 @@ void connect(BuildContext context, String id,
assert(!(isFileTransfer && isTcpTunneling && isRDP), assert(!(isFileTransfer && isTcpTunneling && isRDP),
"more than one connect type"); "more than one connect type");
FocusScopeNode currentFocus = FocusScope.of(context); if (isDesktop) {
if (isFileTransfer) { if (isFileTransfer) {
await rustDeskWinManager.newFileTransfer(id); await rustDeskWinManager.newFileTransfer(id);
} else if (isTcpTunneling || isRDP) { } else if (isTcpTunneling || isRDP) {
await rustDeskWinManager.newPortForward(id, isRDP); await rustDeskWinManager.newPortForward(id, isRDP);
} else {
await rustDeskWinManager.newRemoteDesktop(id);
}
} else { } 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) { if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus(); currentFocus.unfocus();
} }

View File

@ -1,16 +1,18 @@
import 'package:contextmenu/contextmenu.dart'; import 'package:contextmenu/contextmenu.dart';
import 'package:flutter/material.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:flutter_hbb/models/ab_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../common.dart'; import '../../common.dart';
import '../../desktop/pages/desktop_home_page.dart'; import '../../desktop/pages/desktop_home_page.dart';
import '../../mobile/pages/settings_page.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
class AddressBook extends StatefulWidget { class AddressBook extends StatefulWidget {
const AddressBook({Key? key}) : super(key: key); final EdgeInsets? menuPadding;
const AddressBook({Key? key, this.menuPadding}) : super(key: key);
@override @override
State<StatefulWidget> createState() { State<StatefulWidget> createState() {
@ -37,11 +39,16 @@ class _AddressBookState extends State<AddressBook> {
}); });
handleLogin() { handleLogin() {
loginDialog().then((success) { // TODO refactor login dialog for desktop and mobile
if (success) { if (isDesktop) {
setState(() {}); loginDialog().then((success) {
} if (success) {
}); setState(() {});
}
});
} else {
showLogin(gFFI.dialogManager);
}
} }
Future<Widget> buildAddressBook(BuildContext context) async { Future<Widget> buildAddressBook(BuildContext context) async {
@ -108,7 +115,8 @@ class _AddressBookState extends State<AddressBook> {
Card( Card(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
side: const BorderSide(color: MyTheme.grayBg)), side: BorderSide(
color: Theme.of(context).scaffoldBackgroundColor)),
child: Container( child: Container(
width: 200, width: 200,
height: double.infinity, height: double.infinity,
@ -174,7 +182,9 @@ class _AddressBookState extends State<AddressBook> {
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: AddressBookPeerWidget()), child: AddressBookPeersView(
menuPadding: widget.menuPadding,
)),
) )
], ],
)); ));
@ -206,7 +216,8 @@ class _AddressBookState extends State<AddressBook> {
child: Text( child: Text(
tagName, tagName,
style: TextStyle( 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, position: position,
width: width, width: width,
height: height, height: height,
builder: (_, onPanUpdate) { builder: (context, onPanUpdate) {
return isIOS return isIOS
? ChatPage(chatModel: chatModel) ? ChatPage(chatModel: chatModel)
: Scaffold( : Scaffold(
@ -35,16 +35,16 @@ class DraggableChatWindow extends StatelessWidget {
onPanUpdate: onPanUpdate, onPanUpdate: onPanUpdate,
appBar: isDesktop appBar: isDesktop
? _buildDesktopAppBar() ? _buildDesktopAppBar()
: _buildMobileAppBar(), : _buildMobileAppBar(context),
), ),
body: ChatPage(chatModel: chatModel), body: ChatPage(chatModel: chatModel),
); );
}); });
} }
Widget _buildMobileAppBar() { Widget _buildMobileAppBar(BuildContext context) {
return Container( return Container(
color: MyTheme.accent50, color: Theme.of(context).colorScheme.primary,
height: 50, height: 50,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -169,17 +169,17 @@ class DraggableMobileActions extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
IconButton( IconButton(
color: MyTheme.white, color: Colors.white,
onPressed: onBackPressed, onPressed: onBackPressed,
splashRadius: 20, splashRadius: 20,
icon: const Icon(Icons.arrow_back)), icon: const Icon(Icons.arrow_back)),
IconButton( IconButton(
color: MyTheme.white, color: Colors.white,
onPressed: onHomePressed, onPressed: onHomePressed,
splashRadius: 20, splashRadius: 20,
icon: const Icon(Icons.home)), icon: const Icon(Icons.home)),
IconButton( IconButton(
color: MyTheme.white, color: Colors.white,
onPressed: onRecentPressed, onPressed: onRecentPressed,
splashRadius: 20, splashRadius: 20,
icon: const Icon(Icons.more_horiz)), icon: const Icon(Icons.more_horiz)),
@ -190,7 +190,7 @@ class DraggableMobileActions extends StatelessWidget {
endIndent: 10, endIndent: 10,
), ),
IconButton( IconButton(
color: MyTheme.white, color: Colors.white,
onPressed: onHidePressed, onPressed: onHidePressed,
splashRadius: 20, splashRadius: 20,
icon: const Icon(Icons.keyboard_arrow_down)), icon: const Icon(Icons.keyboard_arrow_down)),

View File

@ -1,6 +1,7 @@
import 'package:contextmenu/contextmenu.dart'; import 'package:contextmenu/contextmenu.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../common.dart'; import '../../common.dart';
@ -14,7 +15,7 @@ import '../../desktop/widgets/popup_menu.dart';
class _PopupMenuTheme { class _PopupMenuTheme {
static const Color commonColor = MyTheme.accent; static const Color commonColor = MyTheme.accent;
// kMinInteractiveDimension // kMinInteractiveDimension
static const double height = 25.0; static const double height = 20.0;
static const double dividerHeight = 3.0; static const double dividerHeight = 3.0;
} }
@ -73,12 +74,18 @@ class _PeerCardState extends State<_PeerCard>
_showPeerMenu(peer.id); _showPeerMenu(peer.id);
}, },
child: ListTile( child: ListTile(
contentPadding: const EdgeInsets.only(left: 12), contentPadding: const EdgeInsets.only(left: 12), //
subtitle: Text('${peer.username}@${peer.hostname}'), 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( leading: Container(
decoration: BoxDecoration(
color: str2color('${peer.id}${peer.platform}', 0x7f),
borderRadius: BorderRadius.circular(4),
),
padding: const EdgeInsets.all(6), padding: const EdgeInsets.all(6),
color: str2color('${peer.id}${peer.platform}', 0x7f),
child: getPlatformImage(peer.platform)), child: getPlatformImage(peer.platform)),
trailing: InkWell( trailing: InkWell(
child: const Padding( child: const Padding(
@ -105,7 +112,9 @@ class _PeerCardState extends State<_PeerCard>
return MouseRegion( return MouseRegion(
onEnter: (evt) { onEnter: (evt) {
deco.value = BoxDecoration( 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: peerCardUiType.value == PeerUiType.grid
? BorderRadius.circular(_cardRadis) ? BorderRadius.circular(_cardRadis)
: null); : null);
@ -127,8 +136,10 @@ class _PeerCardState extends State<_PeerCard>
Widget _buildPeerTile( Widget _buildPeerTile(
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) { BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
final greyStyle = final greyStyle = TextStyle(
TextStyle(fontSize: 12, color: MyTheme.color(context).lighterText); fontSize: 11,
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
final alias = bind.mainGetPeerOptionSync(id: peer.id, key: 'alias');
return Obx( return Obx(
() => Container( () => Container(
foregroundDecoration: deco.value, foregroundDecoration: deco.value,
@ -144,66 +155,36 @@ class _PeerCardState extends State<_PeerCard>
), ),
Expanded( Expanded(
child: Container( child: Container(
decoration: BoxDecoration(color: MyTheme.color(context).bg), decoration:
BoxDecoration(color: Theme.of(context).backgroundColor),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
Row(children: [ Row(children: [
Padding( getOnline(8, peer.online),
padding: const EdgeInsets.fromLTRB(0, 4, 4, 4), Expanded(
child: CircleAvatar( child: Text(
radius: 5, alias.isEmpty ? formatID(peer.id) : alias,
backgroundColor: peer.online overflow: TextOverflow.ellipsis,
? Colors.green )),
: Colors.yellow)), ]).marginOnly(bottom: 2),
Text(
formatID(peer.id),
style:
const TextStyle(fontWeight: FontWeight.w400),
),
]),
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: FutureBuilder<String>( child: Text(
future: bind.mainGetPeerOption( '${peer.username}@${peer.hostname}',
id: peer.id, key: 'alias'), style: greyStyle,
builder: (_, snapshot) { textAlign: TextAlign.start,
if (snapshot.hasData) { overflow: TextOverflow.ellipsis,
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,
);
}
},
), ),
), ),
], ],
), ).marginOnly(top: 2),
), ),
_actionMore(peer), _actionMore(peer),
], ],
).paddingSymmetric(horizontal: 4.0), ).paddingOnly(left: 10.0, top: 3.0),
), ),
) )
], ],
@ -268,21 +249,19 @@ class _PeerCardState extends State<_PeerCard>
), ),
), ),
Container( Container(
color: MyTheme.color(context).bg, color: Theme.of(context).backgroundColor,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row(children: [ Expanded(
Padding( child: Row(children: [
padding: const EdgeInsets.fromLTRB(0, 4, 8, 4), getOnline(8, peer.online),
child: CircleAvatar( Expanded(
radius: 5, child: Text(
backgroundColor: peer.online peer.alias.isEmpty ? formatID(peer.id) : peer.alias,
? Colors.green overflow: TextOverflow.ellipsis,
: Colors.yellow)), )),
Text( ]).paddingSymmetric(vertical: 8)),
peer.alias.isEmpty ? formatID(peer.id) : peer.alias)
]).paddingSymmetric(vertical: 8),
_actionMore(peer), _actionMore(peer),
], ],
).paddingSymmetric(horizontal: 12.0), ).paddingSymmetric(horizontal: 12.0),
@ -308,13 +287,21 @@ class _PeerCardState extends State<_PeerCard>
child: CircleAvatar( child: CircleAvatar(
radius: 14, radius: 14,
backgroundColor: _iconMoreHover.value backgroundColor: _iconMoreHover.value
? MyTheme.color(context).grayBg! ? Theme.of(context).scaffoldBackgroundColor
: MyTheme.color(context).bg!, : Theme.of(context).backgroundColor,
// ? Theme.of(context).scaffoldBackgroundColor!
// : Theme.of(context).backgroundColor!,
child: Icon(Icons.more_vert, child: Icon(Icons.more_vert,
size: 18, size: 18,
color: _iconMoreHover.value color: _iconMoreHover.value
? MyTheme.color(context).text ? Theme.of(context).textTheme.titleLarge?.color
: MyTheme.color(context).lightText)))); : 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. /// Show the peer menu and handle user's choice.
/// User might remove the peer or send a file to the peer. /// 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 { abstract class BasePeerCard extends StatelessWidget {
final Peer peer; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -379,6 +368,7 @@ abstract class BasePeerCard extends StatelessWidget {
isRDP: isRDP, isRDP: isRDP,
); );
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -428,17 +418,25 @@ abstract class BasePeerCard extends StatelessWidget {
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: IconButton( child: Transform.scale(
padding: EdgeInsets.zero, scale: 0.8,
icon: const Icon(Icons.edit), child: IconButton(
onPressed: () => _rdpDialog(id), icon: const Icon(Icons.edit),
), padding: EdgeInsets.zero,
onPressed: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
_rdpDialog(id);
},
)),
)) ))
], ],
)), )),
proc: () { proc: () {
connect(context, id, isRDP: true); connect(context, id, isRDP: true);
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -453,6 +451,7 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () { proc: () {
bind.mainWol(id: id); bind.mainWol(id: id);
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -461,6 +460,7 @@ abstract class BasePeerCard extends StatelessWidget {
Future<MenuEntryBase<String>> _forceAlwaysRelayAction(String id) async { Future<MenuEntryBase<String>> _forceAlwaysRelayAction(String id) async {
const option = 'force-always-relay'; const option = 'force-always-relay';
return MenuEntrySwitch<String>( return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox,
text: translate('Always connect via relay'), text: translate('Always connect via relay'),
getter: () async { getter: () async {
return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty; 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); await bind.mainSetPeerOption(id: id, key: option, value: value);
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -489,13 +490,15 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () { proc: () {
_rename(id, isAddressBook); _rename(id, isAddressBook);
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@protected @protected
MenuEntryBase<String> _removeAction( MenuEntryBase<String> _removeAction(
String id, Future<void> Function() reloadFunc) { String id, Future<void> Function() reloadFunc,
{bool isLan = false}) {
return MenuEntryButton<String>( return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text( childBuilder: (TextStyle? style) => Text(
translate('Remove'), translate('Remove'),
@ -503,12 +506,16 @@ abstract class BasePeerCard extends StatelessWidget {
), ),
proc: () { proc: () {
() async { () async {
await bind.mainRemovePeer(id: id); if (isLan) {
// TODO
} else {
await bind.mainRemovePeer(id: id);
}
removePreference(id); removePreference(id);
await reloadFunc(); await reloadFunc();
// Get.forceAppUpdate(); // TODO use inner model / state
}(); }();
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -523,6 +530,7 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () { proc: () {
bind.mainForgetPassword(id: id); bind.mainForgetPassword(id: id);
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -543,6 +551,7 @@ abstract class BasePeerCard extends StatelessWidget {
} }
}(); }();
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -561,10 +570,10 @@ abstract class BasePeerCard extends StatelessWidget {
if (favs.remove(id)) { if (favs.remove(id)) {
await bind.mainStoreFav(favs: favs); await bind.mainStoreFav(favs: favs);
await reloadFunc(); await reloadFunc();
// Get.forceAppUpdate(); // TODO use inner model / state
} }
}(); }();
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -606,8 +615,6 @@ abstract class BasePeerCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Form( child: Form(
child: TextFormField( child: TextFormField(
controller: controller, controller: controller,
@ -634,7 +641,8 @@ abstract class BasePeerCard extends StatelessWidget {
} }
class RecentPeerCard extends BasePeerCard { 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 @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -642,15 +650,13 @@ class RecentPeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer), _connectAction(context, peer),
_transferFileAction(context, peer.id), _transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
]; ];
MenuEntryBase<String>? rdpAction; if (isDesktop) {
if (peer.platform == 'Windows') { menuItems.add(_tcpTunnelingAction(context, peer.id));
rdpAction = _rdpAction(context, peer.id);
} }
menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) { if (peer.platform == 'Windows') {
menuItems.add(rdpAction); menuItems.add(_rdpAction(context, peer.id));
} }
menuItems.add(_wolAction(peer.id)); menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider()); menuItems.add(MenuEntryDivider());
@ -658,15 +664,17 @@ class RecentPeerCard extends BasePeerCard {
menuItems.add(_removeAction(peer.id, () async { menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadRecentPeers(); 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)); menuItems.add(_addFavAction(peer.id));
return menuItems; return menuItems;
} }
} }
class FavoritePeerCard extends BasePeerCard { class FavoritePeerCard extends BasePeerCard {
FavoritePeerCard({required Peer peer, Key? key}) FavoritePeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, key: key); : super(peer: peer, menuPadding: menuPadding, key: key);
@override @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -674,15 +682,13 @@ class FavoritePeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer), _connectAction(context, peer),
_transferFileAction(context, peer.id), _transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
]; ];
MenuEntryBase<String>? rdpAction; if (isDesktop) {
if (peer.platform == 'Windows') { menuItems.add(_tcpTunnelingAction(context, peer.id));
rdpAction = _rdpAction(context, peer.id);
} }
menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) { if (peer.platform == 'Windows') {
menuItems.add(rdpAction); menuItems.add(_rdpAction(context, peer.id));
} }
menuItems.add(_wolAction(peer.id)); menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider()); menuItems.add(MenuEntryDivider());
@ -690,7 +696,9 @@ class FavoritePeerCard extends BasePeerCard {
menuItems.add(_removeAction(peer.id, () async { menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadFavPeers(); 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 { menuItems.add(_rmFavAction(peer.id, () async {
await bind.mainLoadFavPeers(); await bind.mainLoadFavPeers();
})); }));
@ -699,8 +707,8 @@ class FavoritePeerCard extends BasePeerCard {
} }
class DiscoveredPeerCard extends BasePeerCard { class DiscoveredPeerCard extends BasePeerCard {
DiscoveredPeerCard({required Peer peer, Key? key}) DiscoveredPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, key: key); : super(peer: peer, menuPadding: menuPadding, key: key);
@override @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -708,30 +716,24 @@ class DiscoveredPeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer), _connectAction(context, peer),
_transferFileAction(context, peer.id), _transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
]; ];
MenuEntryBase<String>? rdpAction; if (isDesktop) {
if (peer.platform == 'Windows') { menuItems.add(_tcpTunnelingAction(context, peer.id));
rdpAction = _rdpAction(context, peer.id);
} }
menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) { if (peer.platform == 'Windows') {
menuItems.add(rdpAction); menuItems.add(_rdpAction(context, peer.id));
} }
menuItems.add(_wolAction(peer.id)); menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider()); menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false)); menuItems.add(_removeAction(peer.id, () async {}));
menuItems.add(_removeAction(peer.id, () async {
await bind.mainLoadLanPeers();
}));
menuItems.add(_unrememberPasswordAction(peer.id));
return menuItems; return menuItems;
} }
} }
class AddressBookPeerCard extends BasePeerCard { class AddressBookPeerCard extends BasePeerCard {
AddressBookPeerCard({required Peer peer, Key? key}) AddressBookPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, key: key); : super(peer: peer, menuPadding: menuPadding, key: key);
@override @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -739,22 +741,21 @@ class AddressBookPeerCard extends BasePeerCard {
final List<MenuEntryBase<String>> menuItems = [ final List<MenuEntryBase<String>> menuItems = [
_connectAction(context, peer), _connectAction(context, peer),
_transferFileAction(context, peer.id), _transferFileAction(context, peer.id),
_tcpTunnelingAction(context, peer.id),
]; ];
MenuEntryBase<String>? rdpAction; if (isDesktop) {
if (peer.platform == 'Windows') { menuItems.add(_tcpTunnelingAction(context, peer.id));
rdpAction = _rdpAction(context, peer.id);
} }
menuItems.add(await _forceAlwaysRelayAction(peer.id)); menuItems.add(await _forceAlwaysRelayAction(peer.id));
if (rdpAction != null) { if (peer.platform == 'Windows') {
menuItems.add(rdpAction); menuItems.add(_rdpAction(context, peer.id));
} }
menuItems.add(_wolAction(peer.id)); menuItems.add(_wolAction(peer.id));
menuItems.add(MenuEntryDivider()); menuItems.add(MenuEntryDivider());
menuItems.add(_renameAction(peer.id, false)); menuItems.add(_renameAction(peer.id, false));
menuItems.add(_removeAction(peer.id, () async {})); menuItems.add(_removeAction(peer.id, () async {}));
menuItems.add(_unrememberPasswordAction(peer.id)); if (await bind.mainPeerHasPassword(id: peer.id)) {
menuItems.add(_addFavAction(peer.id)); menuItems.add(_unrememberPasswordAction(peer.id));
}
menuItems.add(_editTagAction(peer.id)); menuItems.add(_editTagAction(peer.id));
return menuItems; return menuItems;
} }
@ -762,7 +763,8 @@ class AddressBookPeerCard extends BasePeerCard {
@protected @protected
@override @override
MenuEntryBase<String> _removeAction( MenuEntryBase<String> _removeAction(
String id, Future<void> Function() reloadFunc) { String id, Future<void> Function() reloadFunc,
{bool isLan = false}) {
return MenuEntryButton<String>( return MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Text( childBuilder: (TextStyle? style) => Text(
translate('Remove'), translate('Remove'),
@ -774,6 +776,7 @@ class AddressBookPeerCard extends BasePeerCard {
await gFFI.abModel.updateAb(); await gFFI.abModel.updateAb();
}(); }();
}, },
padding: super.menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -788,6 +791,7 @@ class AddressBookPeerCard extends BasePeerCard {
proc: () { proc: () {
_abEditTag(id); _abEditTag(id);
}, },
padding: super.menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -869,7 +873,7 @@ class AddressBookPeerCard extends BasePeerCard {
child: Text( child: Text(
tagName, tagName,
style: TextStyle( 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/material.dart';
import 'package:flutter_hbb/common/widgets/peer_widget.dart'; import 'package:flutter_hbb/common/widgets/peers_view.dart';
import 'package:flutter_hbb/common/widgets/peercard_widget.dart'; import 'package:flutter_hbb/common/widgets/peer_card.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -42,9 +42,6 @@ class _PeerTabPageState extends State<PeerTabPage>
// hard code for now // hard code for now
Future<void> _handleTabSelection(int index) async { Future<void> _handleTabSelection(int index) async {
// reset search text
peerSearchText.value = "";
peerSearchTextController.clear();
_tabIndex.value = index; _tabIndex.value = index;
await bind.mainSetLocalOption( await bind.mainSetLocalOption(
key: 'peer-tab-index', value: index.toString()); key: 'peer-tab-index', value: index.toString());
@ -101,6 +98,7 @@ class _PeerTabPageState extends State<PeerTabPage>
} }
Widget _createTabBar(BuildContext context) { Widget _createTabBar(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return ListView( return ListView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
shrinkWrap: true, shrinkWrap: true,
@ -111,9 +109,9 @@ class _PeerTabPageState extends State<PeerTabPage>
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _tabIndex.value == t.key color: _tabIndex.value == t.key
? MyTheme.color(context).bg ? Theme.of(context).backgroundColor
: null, : null,
borderRadius: BorderRadius.circular(2), borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
), ),
child: Align( child: Align(
alignment: Alignment.center, alignment: Alignment.center,
@ -123,9 +121,9 @@ class _PeerTabPageState extends State<PeerTabPage>
style: TextStyle( style: TextStyle(
height: 1, height: 1,
fontSize: 14, fontSize: 14,
color: _tabIndex.value == t.key color:
? MyTheme.color(context).text _tabIndex.value == t.key ? textColor : textColor
: MyTheme.color(context).lightText), ?..withOpacity(0.5)),
), ),
)), )),
onTap: () async => await _handleTabSelection(t.key), onTap: () async => await _handleTabSelection(t.key),
@ -147,7 +145,8 @@ class _PeerTabPageState extends State<PeerTabPage>
} }
Widget _createPeerViewTypeSwitch(BuildContext context) { 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( return Row(
children: [PeerUiType.grid, PeerUiType.list] children: [PeerUiType.grid, PeerUiType.list]
.map((type) => Obx( .map((type) => Obx(
@ -166,9 +165,9 @@ class _PeerTabPageState extends State<PeerTabPage>
? Icons.grid_view_rounded ? Icons.grid_view_rounded
: Icons.list, : Icons.list,
size: 18, size: 18,
color: peerCardUiType.value == type color:
? MyTheme.color(context).text peerCardUiType.value == type ? textColor : textColor
: MyTheme.color(context).lightText, ?..withOpacity(0.5),
)), )),
), ),
)) ))
@ -199,9 +198,9 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
drawer = true; drawer = true;
}); });
}, },
icon: const Icon( icon: Icon(
Icons.search_rounded, Icons.search_rounded,
color: MyTheme.dark, color: Theme.of(context).hintColor,
)); ));
} }
@ -212,7 +211,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
return Container( return Container(
width: 120, width: 120,
decoration: BoxDecoration( decoration: BoxDecoration(
color: MyTheme.color(context).bg, color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Obx(() => Row( child: Obx(() => Row(
@ -222,7 +221,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
children: [ children: [
Icon( Icon(
Icons.search_rounded, Icons.search_rounded,
color: MyTheme.color(context).placeholder, color: Theme.of(context).hintColor,
).marginSymmetric(horizontal: 4), ).marginSymmetric(horizontal: 4),
Expanded( Expanded(
child: TextField( child: TextField(
@ -234,7 +233,11 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
focusNode: focusNode, focusNode: focusNode,
textAlign: TextAlign.start, textAlign: TextAlign.start,
maxLines: 1, maxLines: 1,
cursorColor: MyTheme.color(context).lightText, cursorColor: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5),
cursorHeight: 18, cursorHeight: 18,
cursorWidth: 1, cursorWidth: 1,
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
@ -244,8 +247,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
hintText: hintText:
focused.value ? null : translate("Search ID"), focused.value ? null : translate("Search ID"),
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 14, fontSize: 14, color: Theme.of(context).hintColor),
color: MyTheme.color(context).placeholder),
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
), ),
@ -262,9 +264,9 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
drawer = false; drawer = false;
}); });
}, },
icon: const Icon( icon: Icon(
Icons.close, 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 '../../common.dart';
import '../../models/peer_model.dart'; import '../../models/peer_model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import 'peercard_widget.dart'; import 'peer_card.dart';
typedef OffstageFunc = bool Function(Peer peer); 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 /// for peer search text, global obs value
final peerSearchText = "".obs; final peerSearchText = "".obs;
final peerSearchTextController = final peerSearchTextController =
TextEditingController(text: peerSearchText.value); TextEditingController(text: peerSearchText.value);
class _PeerWidget extends StatefulWidget { class _PeersView extends StatefulWidget {
final Peers peers; final Peers peers;
final OffstageFunc offstageFunc; final OffstageFunc offstageFunc;
final PeerCardWidgetFunc peerCardWidgetFunc; final PeerCardBuilder peerCardBuilder;
const _PeerWidget( const _PeersView(
{required this.peers, {required this.peers,
required this.offstageFunc, required this.offstageFunc,
required this.peerCardWidgetFunc, required this.peerCardBuilder,
Key? key}) Key? key})
: super(key: key); : super(key: key);
@override @override
_PeerWidgetState createState() => _PeerWidgetState(); _PeersViewState createState() => _PeersViewState();
} }
/// State for the peer widget. /// State for the peer widget.
class _PeerWidgetState extends State<_PeerWidget> with WindowListener { class _PeersViewState extends State<_PeersView> with WindowListener {
static const int _maxQueryCount = 3; static const int _maxQueryCount = 3;
final space = isDesktop ? 12.0 : 8.0; final space = isDesktop ? 12.0 : 8.0;
final _curPeers = <String>{}; final _curPeers = <String>{};
@ -60,7 +60,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
return width; return width;
}(); }();
_PeerWidgetState() { _PeersViewState() {
_startCheckOnlines(); _startCheckOnlines();
} }
@ -119,7 +119,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
} }
_lastChangeTime = DateTime.now(); _lastChangeTime = DateTime.now();
}, },
child: widget.peerCardWidgetFunc(peer), child: widget.peerCardBuilder(peer),
); );
cards.add(Offstage( cards.add(Offstage(
key: ValueKey("off${peer.id}"), 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 name;
final String loadEvent; final String loadEvent;
final OffstageFunc offstageFunc; final OffstageFunc offstageFunc;
final PeerCardWidgetFunc peerCardWidgetFunc; final PeerCardBuilder peerCardBuilder;
final List<Peer> initPeers; final List<Peer> initPeers;
const BasePeerWidget({ const BasePeersView({
Key? key, Key? key,
required this.name, required this.name,
required this.loadEvent, required this.loadEvent,
required this.offstageFunc, required this.offstageFunc,
required this.peerCardWidgetFunc, required this.peerCardBuilder,
required this.initPeers, required this.initPeers,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _PeerWidget( return _PeersView(
peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers), peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers),
offstageFunc: offstageFunc, offstageFunc: offstageFunc,
peerCardWidgetFunc: peerCardWidgetFunc); peerCardBuilder: peerCardBuilder);
} }
} }
class RecentPeerWidget extends BasePeerWidget { class RecentPeersView extends BasePeersView {
RecentPeerWidget({Key? key}) RecentPeersView({Key? key, EdgeInsets? menuPadding})
: super( : super(
key: key, key: key,
name: 'recent peer', name: 'recent peer',
loadEvent: 'load_recent_peers', loadEvent: 'load_recent_peers',
offstageFunc: (Peer peer) => false, offstageFunc: (Peer peer) => false,
peerCardWidgetFunc: (Peer peer) => RecentPeerCard( peerCardBuilder: (Peer peer) => RecentPeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding,
), ),
initPeers: [], initPeers: [],
); );
@ -244,15 +245,16 @@ class RecentPeerWidget extends BasePeerWidget {
} }
} }
class FavoritePeerWidget extends BasePeerWidget { class FavoritePeersView extends BasePeersView {
FavoritePeerWidget({Key? key}) FavoritePeersView({Key? key, EdgeInsets? menuPadding})
: super( : super(
key: key, key: key,
name: 'favorite peer', name: 'favorite peer',
loadEvent: 'load_fav_peers', loadEvent: 'load_fav_peers',
offstageFunc: (Peer peer) => false, offstageFunc: (Peer peer) => false,
peerCardWidgetFunc: (Peer peer) => FavoritePeerCard( peerCardBuilder: (Peer peer) => FavoritePeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding,
), ),
initPeers: [], initPeers: [],
); );
@ -265,15 +267,16 @@ class FavoritePeerWidget extends BasePeerWidget {
} }
} }
class DiscoveredPeerWidget extends BasePeerWidget { class DiscoveredPeersView extends BasePeersView {
DiscoveredPeerWidget({Key? key}) DiscoveredPeersView({Key? key, EdgeInsets? menuPadding})
: super( : super(
key: key, key: key,
name: 'discovered peer', name: 'discovered peer',
loadEvent: 'load_lan_peers', loadEvent: 'load_lan_peers',
offstageFunc: (Peer peer) => false, offstageFunc: (Peer peer) => false,
peerCardWidgetFunc: (Peer peer) => DiscoveredPeerCard( peerCardBuilder: (Peer peer) => DiscoveredPeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding,
), ),
initPeers: [], initPeers: [],
); );
@ -286,16 +289,17 @@ class DiscoveredPeerWidget extends BasePeerWidget {
} }
} }
class AddressBookPeerWidget extends BasePeerWidget { class AddressBookPeersView extends BasePeersView {
AddressBookPeerWidget({Key? key}) AddressBookPeersView({Key? key, EdgeInsets? menuPadding})
: super( : super(
key: key, key: key,
name: 'address book peer', name: 'address book peer',
loadEvent: 'load_address_book_peers', loadEvent: 'load_address_book_peers',
offstageFunc: (Peer peer) => offstageFunc: (Peer peer) =>
!_hitTag(gFFI.abModel.selectedTags, peer.tags), !_hitTag(gFFI.abModel.selectedTags, peer.tags),
peerCardWidgetFunc: (Peer peer) => AddressBookPeerCard( peerCardBuilder: (Peer peer) => AddressBookPeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding,
), ),
initPeers: _loadPeers(), initPeers: _loadPeers(),
); );

View File

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

View File

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

View File

@ -5,6 +5,8 @@ import 'package:flutter/material.dart' hide MenuItem;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/desktop/pages/connection_page.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/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/utils/multi_window_manager.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:provider/provider.dart';
import 'package:tray_manager/tray_manager.dart'; import 'package:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:url_launcher/url_launcher.dart';
import '../widgets/button.dart';
class DesktopHomePage extends StatefulWidget { class DesktopHomePage extends StatefulWidget {
const DesktopHomePage({Key? key}) : super(key: key); const DesktopHomePage({Key? key}) : super(key: key);
@ -26,6 +31,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
with TrayListener, WindowListener, AutomaticKeepAliveClientMixin { with TrayListener, WindowListener, AutomaticKeepAliveClientMixin {
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
var updateUrl = '';
@override @override
void onWindowClose() async { void onWindowClose() async {
@ -68,12 +74,13 @@ class _DesktopHomePageState extends State<DesktopHomePage>
value: gFFI.serverModel, value: gFFI.serverModel,
child: Container( child: Container(
width: 200, width: 200,
color: MyTheme.color(context).bg, color: Theme.of(context).backgroundColor,
child: Column( child: Column(
children: [ children: [
buildTip(context), buildTip(context),
buildIDBoard(context), buildIDBoard(context),
buildPasswordBoard(context), buildPasswordBoard(context),
buildHelpCards(),
], ],
), ),
), ),
@ -82,7 +89,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
buildRightPane(BuildContext context) { buildRightPane(BuildContext context) {
return Container( return Container(
color: MyTheme.color(context).grayBg, color: Theme.of(context).scaffoldBackgroundColor,
child: ConnectionPage(), child: ConnectionPage(),
); );
} }
@ -116,7 +123,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
translate("ID"), translate("ID"),
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: MyTheme.color(context).lightText), color: Theme.of(context)
.textTheme
.titleLarge
?.color
?.withOpacity(0.5)),
).marginOnly(top: 5), ).marginOnly(top: 5),
buildPopupMenu(context) buildPopupMenu(context)
], ],
@ -152,21 +163,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
} }
Widget buildPopupMenu(BuildContext context) { Widget buildPopupMenu(BuildContext context) {
final textColor = Theme.of(context).textTheme.titleLarge?.color;
RxBool hover = false.obs; RxBool hover = false.obs;
return InkWell( return InkWell(
onTap: () async {}, onTap: DesktopTabPage.onAddSetting,
child: Obx( child: Obx(
() => CircleAvatar( () => CircleAvatar(
radius: 15, radius: 15,
backgroundColor: hover.value backgroundColor: hover.value
? MyTheme.color(context).grayBg! ? Theme.of(context).scaffoldBackgroundColor
: MyTheme.color(context).bg!, : Theme.of(context).backgroundColor,
child: Icon( child: Icon(
Icons.more_vert_outlined, Icons.more_vert_outlined,
size: 20, size: 20,
color: hover.value color: hover.value ? textColor : textColor?.withOpacity(0.5),
? MyTheme.color(context).text
: MyTheme.color(context).lightText,
), ),
), ),
), ),
@ -178,6 +188,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
final model = gFFI.serverModel; final model = gFFI.serverModel;
RxBool refreshHover = false.obs; RxBool refreshHover = false.obs;
RxBool editHover = false.obs; RxBool editHover = false.obs;
final textColor = Theme.of(context).textTheme.titleLarge?.color;
return Container( return Container(
margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13), margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13),
child: Row( child: Row(
@ -198,7 +209,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Text( Text(
translate("Password"), translate("Password"),
style: TextStyle( style: TextStyle(
fontSize: 14, color: MyTheme.color(context).lightText), fontSize: 14, color: textColor?.withOpacity(0.5)),
), ),
Row( Row(
children: [ children: [
@ -228,8 +239,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
() => Icon( () => Icon(
Icons.refresh, Icons.refresh,
color: refreshHover.value color: refreshHover.value
? MyTheme.color(context).text ? textColor
: Color(0xFFDDDDDD), : Color(0xFFDDDDDD), // TODO
size: 22, size: 22,
).marginOnly(right: 8, bottom: 2), ).marginOnly(right: 8, bottom: 2),
), ),
@ -241,12 +252,12 @@ class _DesktopHomePageState extends State<DesktopHomePage>
() => Icon( () => Icon(
Icons.edit, Icons.edit,
color: editHover.value color: editHover.value
? MyTheme.color(context).text ? textColor
: Color(0xFFDDDDDD), : Color(0xFFDDDDDD), // TODO
size: 22, size: 22,
).marginOnly(right: 8, bottom: 2), ).marginOnly(right: 8, bottom: 2),
), ),
onTap: () => {}, onTap: () => DesktopSettingPage.switch2page(1),
onHover: (value) => editHover.value = value, onHover: (value) => editHover.value = value,
), ),
], ],
@ -270,7 +281,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
children: [ children: [
Text( Text(
translate("Your Desktop"), 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( SizedBox(
height: 10.0, height: 10.0,
@ -278,16 +293,93 @@ class _DesktopHomePageState extends State<DesktopHomePage>
Text( Text(
translate("desk_tip"), translate("desk_tip"),
overflow: TextOverflow.clip, overflow: TextOverflow.clip,
style: TextStyle( style: Theme.of(context).textTheme.bodySmall,
fontSize: 12,
color: MyTheme.color(context).lighterText,
height: 1.25),
) )
], ],
), ),
); );
} }
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 @override
void onTrayMenuItemClick(MenuItem menuItem) { void onTrayMenuItemClick(MenuItem menuItem) {
debugPrint('click ${menuItem.key}'); debugPrint('click ${menuItem.key}');
@ -305,6 +397,10 @@ class _DesktopHomePageState extends State<DesktopHomePage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
Timer(const Duration(seconds: 5), () async {
updateUrl = await bind.mainGetSoftwareUpdateUrl();
if (updateUrl.isNotEmpty) setState(() {});
});
trayManager.addListener(this); trayManager.addListener(this);
windowManager.addListener(this); windowManager.addListener(this);
rustDeskWinManager.setMethodHandler((call, fromWindowId) async { rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
@ -331,7 +427,7 @@ Future<bool> loginDialog() async {
var userNameMsg = ""; var userNameMsg = "";
String pass = ""; String pass = "";
var passMsg = ""; var passMsg = "";
var userContontroller = TextEditingController(text: userName); var userController = TextEditingController(text: userName);
var pwdController = TextEditingController(text: pass); var pwdController = TextEditingController(text: pass);
var isInProgress = false; var isInProgress = false;
@ -349,7 +445,7 @@ Future<bool> loginDialog() async {
}); });
} }
userName = userContontroller.text; userName = userController.text;
pass = pwdController.text; pass = pwdController.text;
if (userName.isEmpty) { if (userName.isEmpty) {
userNameMsg = translate("Username missed"); userNameMsg = translate("Username missed");
@ -385,6 +481,7 @@ Future<bool> loginDialog() async {
close(); close();
} }
// dialog
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("Login")), title: Text(translate("Login")),
content: ConstrainedBox( content: ConstrainedBox(
@ -411,7 +508,7 @@ Future<bool> loginDialog() async {
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: userNameMsg.isNotEmpty ? userNameMsg : null), errorText: userNameMsg.isNotEmpty ? userNameMsg : null),
controller: userContontroller, controller: userController,
focusNode: FocusNode()..requestFocus(), focusNode: FocusNode()..requestFocus(),
), ),
), ),

View File

@ -5,7 +5,9 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.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_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/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/server_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -18,7 +20,7 @@ import '../../common/widgets/dialog.dart';
const double _kTabWidth = 235; const double _kTabWidth = 235;
const double _kTabHeight = 42; const double _kTabHeight = 42;
const double _kCardFixedWidth = 560; const double _kCardFixedWidth = 540;
const double _kCardLeftMargin = 15; const double _kCardLeftMargin = 15;
const double _kContentHMargin = 15; const double _kContentHMargin = 15;
const double _kContentHSubMargin = _kContentHMargin + 33; const double _kContentHSubMargin = _kContentHMargin + 33;
@ -28,6 +30,8 @@ const double _kListViewBottomMargin = 15;
const double _kTitleFontSize = 20; const double _kTitleFontSize = 20;
const double _kContentFontSize = 15; const double _kContentFontSize = 15;
const Color _accentColor = MyTheme.accent; const Color _accentColor = MyTheme.accent;
const String _kSettingPageControllerTag = "settingPageController";
const String _kSettingPageIndexTag = "settingPageIndex";
class _TabInfo { class _TabInfo {
late final String label; late final String label;
@ -37,10 +41,30 @@ class _TabInfo {
} }
class DesktopSettingPage extends StatefulWidget { 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 @override
State<DesktopSettingPage> createState() => _DesktopSettingPageState(); 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> class _DesktopSettingPageState extends State<DesktopSettingPage>
@ -50,12 +74,12 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
_TabInfo('Security', Icons.enhanced_encryption_outlined, _TabInfo('Security', Icons.enhanced_encryption_outlined,
Icons.enhanced_encryption), Icons.enhanced_encryption),
_TabInfo('Network', Icons.link_outlined, Icons.link), _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) _TabInfo('About', Icons.info_outline, Icons.info)
]; ];
late PageController controller; late PageController controller;
RxInt selectedIndex = 0.obs; late RxInt selectedIndex;
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
@ -63,14 +87,24 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
@override @override
void initState() { void initState() {
super.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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Scaffold( return Scaffold(
backgroundColor: MyTheme.color(context).bg, backgroundColor: Theme.of(context).backgroundColor,
body: Row( body: Row(
children: <Widget>[ children: <Widget>[
SizedBox( SizedBox(
@ -85,7 +119,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
const VerticalDivider(thickness: 1, width: 1), const VerticalDivider(thickness: 1, width: 1),
Expanded( Expanded(
child: Container( child: Container(
color: MyTheme.color(context).grayBg, color: Theme.of(context).scaffoldBackgroundColor,
child: DesktopScrollWrapper( child: DesktopScrollWrapper(
scrollController: controller, scrollController: controller,
child: PageView( child: PageView(
@ -94,7 +128,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
_General(), _General(),
_Safety(), _Safety(),
_Network(), _Network(),
_Acount(), _Account(),
_About(), _About(),
], ],
)), )),
@ -387,7 +421,7 @@ class _Safety extends StatefulWidget {
class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
bool locked = true; bool locked = bind.mainIsInstalled();
final scrollController = ScrollController(); final scrollController = ScrollController();
@override @override
@ -533,7 +567,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
_OptionCheckBox(context, 'Deny remote access', 'stop-service', _OptionCheckBox(context, 'Deny remote access', 'stop-service',
checkedIcon: const Icon( checkedIcon: const Icon(
Icons.warning_amber_rounded, Icons.warning_amber_rounded,
color: Color.fromARGB(255, 255, 204, 0), color: kColorWarn,
), ),
enabled: enabled), enabled: enabled),
Offstage( Offstage(
@ -541,6 +575,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
child: _OptionCheckBox(context, 'Enable RDP', 'enable-rdp', child: _OptionCheckBox(context, 'Enable RDP', 'enable-rdp',
enabled: enabled), enabled: enabled),
), ),
_OptionCheckBox(context, 'Deny LAN Discovery', 'enable-lan-discovery',
reverse: true, enabled: enabled),
...directIp(context), ...directIp(context),
whitelist(), whitelist(),
]); ]);
@ -700,14 +736,14 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
} }
} }
class _Acount extends StatefulWidget { class _Account extends StatefulWidget {
const _Acount({Key? key}) : super(key: key); const _Account({Key? key}) : super(key: key);
@override @override
State<_Acount> createState() => _AcountState(); State<_Account> createState() => _AccountState();
} }
class _AcountState extends State<_Acount> { class _AccountState extends State<_Account> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final scrollController = ScrollController(); final scrollController = ScrollController();
@ -717,12 +753,12 @@ class _AcountState extends State<_Acount> {
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
controller: scrollController, controller: scrollController,
children: [ children: [
_Card(title: 'Acount', children: [login()]), _Card(title: 'Account', children: [accountAction()]),
], ],
).marginOnly(bottom: _kListViewBottomMargin)); ).marginOnly(bottom: _kListViewBottomMargin));
} }
Widget login() { Widget accountAction() {
return _futureBuilder(future: () async { return _futureBuilder(future: () async {
return await gFFI.userModel.getUserName(); return await gFFI.userModel.getUserName();
}(), hasData: (data) { }(), hasData: (data) {
@ -730,12 +766,14 @@ class _AcountState extends State<_Acount> {
return _Button( return _Button(
username.isEmpty ? 'Login' : 'Logout', username.isEmpty ? 'Login' : 'Logout',
() => { () => {
loginDialog().then((success) { username.isEmpty
if (success) { ? loginDialog().then((success) {
// refresh frame if (success) {
setState(() {}); // 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) { 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 // 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 { void changeSocks5Proxy() async {
var socks = await bind.mainGetSocks(); var socks = await bind.mainGetSocks();

View File

@ -14,6 +14,23 @@ class DesktopTabPage extends StatefulWidget {
@override @override
State<DesktopTabPage> createState() => _DesktopTabPageState(); 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> { class _DesktopTabPageState extends State<DesktopTabPage> {
@ -22,6 +39,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
Get.put<DesktopTabController>(tabController);
tabController.add(TabInfo( tabController.add(TabInfo(
key: kTabLabelHomePage, key: kTabLabelHomePage,
label: kTabLabelHomePage, label: kTabLabelHomePage,
@ -33,6 +51,12 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
))); )));
} }
@override
void dispose() {
super.dispose();
Get.delete<DesktopTabController>();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
RxBool fullscreen = false.obs; RxBool fullscreen = false.obs;
@ -42,13 +66,13 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
OverlayEntry(builder: (context) { OverlayEntry(builder: (context) {
gFFI.dialogManager.setOverlayState(Overlay.of(context)); gFFI.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold( return Scaffold(
backgroundColor: MyTheme.color(context).bg, backgroundColor: Theme.of(context).backgroundColor,
body: DesktopTab( body: DesktopTab(
controller: tabController, controller: tabController,
tail: ActionIcon( tail: ActionIcon(
message: 'Settings', message: 'Settings',
icon: IconFont.menu, icon: IconFont.menu,
onTap: onAddSetting, onTap: DesktopTabPage.onAddSetting,
isClose: false, isClose: false,
), ),
)); ));
@ -62,13 +86,4 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
fullscreen.value ? kFullScreenEdgeSize : kWindowEdgeSize, fullscreen.value ? kFullScreenEdgeSize : kWindowEdgeSize,
child: tabWidget)); 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; return false;
}, },
child: Scaffold( child: Scaffold(
backgroundColor: MyTheme.color(context).bg, backgroundColor: Theme.of(context).backgroundColor,
body: Row( body: Row(
children: [ children: [
Flexible(flex: 3, child: body(isLocal: true)), Flexible(flex: 3, child: body(isLocal: true)),

View File

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

View File

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

View File

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

View File

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

View File

@ -69,7 +69,7 @@ class _DesktopServerPageState extends State<DesktopServerPage>
OverlayEntry(builder: (context) { OverlayEntry(builder: (context) {
gFFI.dialogManager.setOverlayState(Overlay.of(context)); gFFI.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold( return Scaffold(
backgroundColor: MyTheme.color(context).bg, backgroundColor: Theme.of(context).backgroundColor,
body: Center( body: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -145,7 +145,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
windowManager.startDragging(); windowManager.startDragging();
}, },
child: Container( child: Container(
color: MyTheme.color(context).bg, color: Theme.of(context).backgroundColor,
), ),
), ),
), ),
@ -310,14 +310,15 @@ class _CmHeaderState extends State<_CmHeader>
], ],
), ),
), ),
Offstage( Consumer<ServerModel>(
offstage: client.isFileTransfer, builder: (_, model, child) => Offstage(
child: IconButton( offstage: !client.authorized || client.isFileTransfer,
onPressed: () => checkClickTime( child: IconButton(
client.id, () => gFFI.chatModel.toggleCMChatPage(client.id)), onPressed: () => checkClickTime(client.id,
icon: Icon(Icons.message_outlined), () => 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) { } // void setState(VoidCallback fn) { }
// enum Menu { itemOne, itemTwo, itemThree, itemFour } // 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 _kMenuCloseIntervalEnd = 2.0 / 3.0;
const double _kMenuHorizontalPadding = 16.0; const double _kMenuHorizontalPadding = 16.0;
const double _kMenuDividerHeight = 16.0; const double _kMenuDividerHeight = 16.0;
@ -22,7 +23,7 @@ const double _kMenuDividerHeight = 16.0;
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep; const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
const double _kMenuMaxWidth = double.infinity; const double _kMenuMaxWidth = double.infinity;
// const double _kMenuVerticalPadding = 8.0; // const double _kMenuVerticalPadding = 8.0;
const double _kMenuVerticalPadding = 0.0; const double _kMenuVerticalPadding = 8.0;
const double _kMenuWidthStep = 0.0; const double _kMenuWidthStep = 0.0;
//const double _kMenuScreenPadding = 8.0; //const double _kMenuScreenPadding = 8.0;
const double _kMenuScreenPadding = 0.0; const double _kMenuScreenPadding = 0.0;

View File

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

View File

@ -20,7 +20,7 @@ import './material_mod_popup_menu.dart' as mod_menu;
class _MenubarTheme { class _MenubarTheme {
static const Color commonColor = MyTheme.accent; static const Color commonColor = MyTheme.accent;
// kMinInteractiveDimension // kMinInteractiveDimension
static const double height = 25.0; static const double height = 20.0;
static const double dividerHeight = 12.0; static const double dividerHeight = 12.0;
} }
@ -391,31 +391,41 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
List<MenuEntryBase<String>> _getControlMenu(BuildContext context) { List<MenuEntryBase<String>> _getControlMenu(BuildContext context) {
final pi = widget.ffi.ffiModel.pi; final pi = widget.ffi.ffiModel.pi;
final perms = widget.ffi.ffiModel.permissions; final perms = widget.ffi.ffiModel.permissions;
const EdgeInsets padding = EdgeInsets.only(left: 14.0, right: 5.0);
final List<MenuEntryBase<String>> displayMenu = []; final List<MenuEntryBase<String>> displayMenu = [];
displayMenu.addAll([ displayMenu.addAll([
MenuEntryButton<String>( MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Row( childBuilder: (TextStyle? style) => Container(
children: [ alignment: AlignmentDirectional.center,
Text( height: _MenubarTheme.height,
translate('OS Password'), child: Row(
style: style, children: [
), Text(
Expanded( translate('OS Password'),
child: Align( style: style,
alignment: Alignment.centerRight, ),
child: IconButton( Expanded(
padding: EdgeInsets.zero, child: Align(
icon: const Icon(Icons.edit), alignment: Alignment.centerRight,
onPressed: () => showSetOSPassword( child: Transform.scale(
widget.id, false, widget.ffi.dialogManager), 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: () { proc: () {
showSetOSPassword(widget.id, false, widget.ffi.dialogManager); showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
), ),
MenuEntryButton<String>( MenuEntryButton<String>(
@ -426,6 +436,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
connect(context, widget.id, isFileTransfer: true); connect(context, widget.id, isFileTransfer: true);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
), ),
MenuEntryButton<String>( MenuEntryButton<String>(
@ -433,6 +444,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
translate('TCP Tunneling'), translate('TCP Tunneling'),
style: style, style: style,
), ),
padding: padding,
proc: () { proc: () {
connect(context, widget.id, isTcpTunneling: true); connect(context, widget.id, isTcpTunneling: true);
}, },
@ -451,6 +463,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
showAuditDialog(widget.id, widget.ffi.dialogManager); showAuditDialog(widget.id, widget.ffi.dialogManager);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
), ),
); );
@ -467,6 +480,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
bind.sessionCtrlAltDel(id: widget.id); bind.sessionCtrlAltDel(id: widget.id);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
} }
@ -483,6 +497,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager); showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
} }
@ -496,6 +511,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
bind.sessionLockScreen(id: widget.id); bind.sessionLockScreen(id: widget.id);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
@ -513,6 +529,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
value: '${blockInput.value ? "un" : ""}block-input'); value: '${blockInput.value ? "un" : ""}block-input');
blockInput.value = !blockInput.value; blockInput.value = !blockInput.value;
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
} }
@ -527,6 +544,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
bind.sessionRefresh(id: widget.id); bind.sessionRefresh(id: widget.id);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
} }
@ -547,6 +565,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
// } // }
// }(); // }();
// }, // },
// padding: padding,
// dismissOnClicked: true, // dismissOnClicked: true,
// )); // ));
// } // }
@ -559,6 +578,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
widget.ffi.cursorModel.reset(); widget.ffi.cursorModel.reset();
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
} }
@ -567,125 +587,155 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
} }
List<MenuEntryBase<String>> _getDisplayMenu(dynamic futureData) { List<MenuEntryBase<String>> _getDisplayMenu(dynamic futureData) {
const EdgeInsets padding = EdgeInsets.only(left: 18.0, right: 8.0);
final displayMenu = [ final displayMenu = [
MenuEntryRadios<String>( MenuEntryRadios<String>(
text: translate('Ratio'), text: translate('Ratio'),
optionsGetter: () => [ optionsGetter: () => [
MenuEntryRadioOption( MenuEntryRadioOption(
text: translate('Scale original'), value: 'original'), text: translate('Scale original'),
MenuEntryRadioOption( value: 'original',
text: translate('Scale adaptive'), value: 'adaptive'), dismissOnClicked: true,
], ),
curOptionGetter: () async { MenuEntryRadioOption(
return await bind.sessionGetOption( text: translate('Scale adaptive'),
id: widget.id, arg: 'view-style') ?? value: 'adaptive',
'adaptive'; dismissOnClicked: true,
}, ),
optionSetter: (String oldValue, String newValue) async { ],
await bind.sessionPeerOption( curOptionGetter: () async {
id: widget.id, name: "view-style", value: newValue); return await bind.sessionGetOption(
widget.ffi.canvasModel.updateViewStyle(); 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>(), MenuEntryDivider<String>(),
MenuEntryRadios<String>( MenuEntryRadios<String>(
text: translate('Scroll Style'), text: translate('Scroll Style'),
optionsGetter: () => [ optionsGetter: () => [
MenuEntryRadioOption( MenuEntryRadioOption(
text: translate('ScrollAuto'), value: 'scrollauto'), text: translate('ScrollAuto'),
MenuEntryRadioOption( value: 'scrollauto',
text: translate('Scrollbar'), value: 'scrollbar'), dismissOnClicked: true,
], ),
curOptionGetter: () async { MenuEntryRadioOption(
return await bind.sessionGetOption( text: translate('Scrollbar'),
id: widget.id, arg: 'scroll-style') ?? value: 'scrollbar',
''; dismissOnClicked: true,
}, ),
optionSetter: (String oldValue, String newValue) async { ],
await bind.sessionPeerOption( curOptionGetter: () async {
id: widget.id, name: "scroll-style", value: newValue); return await bind.sessionGetOption(
widget.ffi.canvasModel.updateScrollStyle(); 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>(), MenuEntryDivider<String>(),
MenuEntryRadios<String>( MenuEntryRadios<String>(
text: translate('Image Quality'), text: translate('Image Quality'),
optionsGetter: () => [ optionsGetter: () => [
MenuEntryRadioOption( MenuEntryRadioOption(
text: translate('Good image quality'), value: 'best'), text: translate('Good image quality'),
MenuEntryRadioOption( value: 'best',
text: translate('Balanced'), value: 'balanced'), dismissOnClicked: true,
MenuEntryRadioOption( ),
text: translate('Optimize reaction time'), value: 'low'), MenuEntryRadioOption(
MenuEntryRadioOption( text: translate('Balanced'),
text: translate('Custom'), value: 'balanced',
value: 'custom', dismissOnClicked: true,
dismissOnClicked: true), ),
], MenuEntryRadioOption(
curOptionGetter: () async { text: translate('Optimize reaction time'),
String quality = value: 'low',
await bind.sessionGetImageQuality(id: widget.id) ?? 'balanced'; dismissOnClicked: true,
if (quality == '') quality = 'balanced'; ),
return quality; MenuEntryRadioOption(
}, text: translate('Custom'),
optionSetter: (String oldValue, String newValue) async { value: 'custom',
if (oldValue != newValue) { dismissOnClicked: true),
await bind.sessionSetImageQuality(id: widget.id, value: newValue); ],
} 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') { if (newValue == 'custom') {
final btnCancel = msgBoxButton(translate('Close'), () { final btnCancel = msgBoxButton(translate('Close'), () {
widget.ffi.dialogManager.dismissAll(); widget.ffi.dialogManager.dismissAll();
}); });
final quality = final quality =
await bind.sessionGetCustomImageQuality(id: widget.id); await bind.sessionGetCustomImageQuality(id: widget.id);
double initValue = quality != null && quality.isNotEmpty double initValue = quality != null && quality.isNotEmpty
? quality[0].toDouble() ? quality[0].toDouble()
: 50.0; : 50.0;
const minValue = 10.0; const minValue = 10.0;
const maxValue = 100.0; const maxValue = 100.0;
if (initValue < minValue) { if (initValue < minValue) {
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 (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>(), MenuEntryDivider<String>(),
]; ];
@ -701,30 +751,49 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
} finally {} } finally {}
if (codecs.length == 2 && (codecs[0] || codecs[1])) { if (codecs.length == 2 && (codecs[0] || codecs[1])) {
displayMenu.add(MenuEntryRadios<String>( displayMenu.add(MenuEntryRadios<String>(
text: translate('Codec Preference'), text: translate('Codec Preference'),
optionsGetter: () { optionsGetter: () {
final list = [ final list = [
MenuEntryRadioOption(text: translate('Auto'), value: 'auto'), MenuEntryRadioOption(
MenuEntryRadioOption(text: 'VP9', value: 'vp9'), text: translate('Auto'),
]; value: 'auto',
if (codecs[0]) { dismissOnClicked: true,
list.add(MenuEntryRadioOption(text: 'H264', value: 'h264')); ),
} MenuEntryRadioOption(
if (codecs[1]) { text: 'VP9',
list.add(MenuEntryRadioOption(text: 'H265', value: 'h265')); value: 'vp9',
} dismissOnClicked: true,
return list; ),
}, ];
curOptionGetter: () async { if (codecs[0]) {
return await bind.sessionGetOption( list.add(MenuEntryRadioOption(
id: widget.id, arg: 'codec-preference') ?? text: 'H264',
'auto'; value: 'h264',
}, dismissOnClicked: true,
optionSetter: (String oldValue, String newValue) async { ));
await bind.sessionPeerOption( }
id: widget.id, name: "codec-preference", value: newValue); if (codecs[1]) {
bind.sessionChangePreferCodec(id: widget.id); 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(() { displayMenu.add(() {
final state = ShowRemoteCursorState.find(widget.id); final state = ShowRemoteCursorState.find(widget.id);
return MenuEntrySwitch2<String>( return MenuEntrySwitch2<String>(
text: translate('Show remote cursor'), switchType: SwitchType.scheckbox,
getter: () { text: translate('Show remote cursor'),
return state; getter: () {
}, return state;
setter: (bool v) async { },
state.value = v; setter: (bool v) async {
await bind.sessionToggleOption( state.value = v;
id: widget.id, value: 'show-remote-cursor'); await bind.sessionToggleOption(
}); id: widget.id, value: 'show-remote-cursor');
},
padding: padding,
dismissOnClicked: true,
);
}()); }());
/// Show quality monitor /// Show quality monitor
displayMenu.add(MenuEntrySwitch<String>( displayMenu.add(MenuEntrySwitch<String>(
text: translate('Show quality monitor'), switchType: SwitchType.scheckbox,
getter: () async { text: translate('Show quality monitor'),
return bind.sessionGetToggleOptionSync( getter: () async {
id: widget.id, arg: 'show-quality-monitor'); return bind.sessionGetToggleOptionSync(
}, id: widget.id, arg: 'show-quality-monitor');
setter: (bool v) async { },
await bind.sessionToggleOption( setter: (bool v) async {
id: widget.id, value: 'show-quality-monitor'); await bind.sessionToggleOption(
widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id); id: widget.id, value: 'show-quality-monitor');
})); widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
},
padding: padding,
dismissOnClicked: true,
));
final perms = widget.ffi.ffiModel.permissions; final perms = widget.ffi.ffiModel.permissions;
final pi = widget.ffi.ffiModel.pi; final pi = widget.ffi.ffiModel.pi;
if (perms['audio'] != false) { if (perms['audio'] != false) {
displayMenu.add(_createSwitchMenuEntry('Mute', 'disable-audio')); displayMenu
.add(_createSwitchMenuEntry('Mute', 'disable-audio', padding, true));
} }
if (Platform.isWindows && if (Platform.isWindows &&
pi.platform == 'Windows' && pi.platform == 'Windows' &&
perms['file'] != false) { perms['file'] != false) {
displayMenu.add(_createSwitchMenuEntry( 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['keyboard'] != false) {
if (perms['clipboard'] != false) { if (perms['clipboard'] != false) {
displayMenu.add( displayMenu.add(_createSwitchMenuEntry(
_createSwitchMenuEntry('Disable clipboard', 'disable-clipboard')); 'Disable clipboard', 'disable-clipboard', padding, true));
} }
displayMenu.add(_createSwitchMenuEntry( 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') { if (pi.platform == 'Windows') {
displayMenu.add(MenuEntrySwitch2<String>( displayMenu.add(MenuEntrySwitch2<String>(
dismissOnClicked: true, switchType: SwitchType.scheckbox,
text: translate('Privacy mode'), text: translate('Privacy mode'),
getter: () { getter: () {
return PrivacyModeState.find(widget.id); return PrivacyModeState.find(widget.id);
}, },
setter: (bool v) async { setter: (bool v) async {
await bind.sessionToggleOption( await bind.sessionToggleOption(
id: widget.id, value: 'privacy-mode'); id: widget.id, value: 'privacy-mode');
})); },
padding: padding,
dismissOnClicked: true,
));
} }
} }
return displayMenu; return displayMenu;
@ -796,34 +877,39 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
List<MenuEntryBase<String>> _getKeyboardMenu() { List<MenuEntryBase<String>> _getKeyboardMenu() {
final keyboardMenu = [ final keyboardMenu = [
MenuEntryRadios<String>( MenuEntryRadios<String>(
text: translate('Ratio'), text: translate('Ratio'),
optionsGetter: () => [ optionsGetter: () => [
MenuEntryRadioOption( MenuEntryRadioOption(text: translate('Legacy mode'), value: 'legacy'),
text: translate('Legacy mode'), value: 'legacy'), MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'), ],
], curOptionGetter: () async {
curOptionGetter: () async { return await bind.sessionGetKeyboardName(id: widget.id);
return await bind.sessionGetKeyboardName(id: widget.id); },
}, optionSetter: (String oldValue, String newValue) async {
optionSetter: (String oldValue, String newValue) async { await bind.sessionSetKeyboardMode(
await bind.sessionSetKeyboardMode( id: widget.id, keyboardMode: newValue);
id: widget.id, keyboardMode: newValue); widget.ffi.canvasModel.updateViewStyle();
widget.ffi.canvasModel.updateViewStyle(); },
}) )
]; ];
return keyboardMenu; return keyboardMenu;
} }
MenuEntrySwitch<String> _createSwitchMenuEntry(String text, String option) { MenuEntrySwitch<String> _createSwitchMenuEntry(
String text, String option, EdgeInsets? padding, bool dismissOnClicked) {
return MenuEntrySwitch<String>( return MenuEntrySwitch<String>(
text: translate(text), switchType: SwitchType.scheckbox,
getter: () async { text: translate(text),
return bind.sessionGetToggleOptionSync(id: widget.id, arg: option); getter: () async {
}, return bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
setter: (bool v) async { },
await bind.sessionToggleOption(id: widget.id, value: 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.tabBuilder,
this.labelGetter}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Obx(() => ListView( return Obx(() => ListView(
@ -497,38 +506,41 @@ class _ListView extends StatelessWidget {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
shrinkWrap: true, shrinkWrap: true,
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
children: state.value.tabs.asMap().entries.map((e) { children: isHideSingleItem()
final index = e.key; ? List.empty()
final tab = e.value; : state.value.tabs.asMap().entries.map((e) {
return _Tab( final index = e.key;
index: index, final tab = e.value;
label: labelGetter == null return _Tab(
? Rx<String>(tab.label) index: index,
: labelGetter!(tab.label), label: labelGetter == null
selectedIcon: tab.selectedIcon, ? Rx<String>(tab.label)
unselectedIcon: tab.unselectedIcon, : labelGetter!(tab.label),
closable: tab.closable, selectedIcon: tab.selectedIcon,
selected: state.value.selected, unselectedIcon: tab.unselectedIcon,
onClose: () { closable: tab.closable,
if (tab.onTabCloseButton != null) { selected: state.value.selected,
tab.onTabCloseButton!(); onClose: () {
} else { if (tab.onTabCloseButton != null) {
controller.remove(index); 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,
);
}, },
); onSelected: () => controller.jumpTo(index),
}).toList())); 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); restoreWindowPosition(WindowType.Main);
await windowManager.show(); await windowManager.show();
await windowManager.focus(); await windowManager.focus();
await windowManager.setOpacity(1);
}); });
} }
@ -190,8 +191,13 @@ void runPortForwardScreen(Map<String, dynamic> argument) async {
void runConnectionManagerScreen() async { void runConnectionManagerScreen() async {
// initialize window // initialize window
WindowOptions windowOptions = WindowOptions windowOptions =
getHiddenTitleBarWindowOptions(size: const Size(300, 400)); getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
await initEnv(kAppTypeMain); // ensure initial window size to be changed
await windowManager.setSize(kConnectionManagerWindowSize);
await Future.wait([
windowManager.setAlignment(Alignment.topRight),
initEnv(kAppTypeMain)
]);
runApp(GetMaterialApp( runApp(GetMaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: MyTheme.lightTheme, theme: MyTheme.lightTheme,
@ -206,17 +212,17 @@ void runConnectionManagerScreen() async {
home: const DesktopServerPage(), home: const DesktopServerPage(),
builder: _keepScaleBuilder())); builder: _keepScaleBuilder()));
windowManager.waitUntilReadyToShow(windowOptions, () async { windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.setAlignment(Alignment.topRight); windowManager.show();
await windowManager.show(); windowManager.focus();
await windowManager.focus(); windowManager.setOpacity(1);
await windowManager.setAlignment(Alignment.topRight); // ensure windowManager.setAlignment(Alignment.topRight); // ensure
}); });
} }
WindowOptions getHiddenTitleBarWindowOptions({Size? size}) { WindowOptions getHiddenTitleBarWindowOptions({Size? size}) {
return WindowOptions( return WindowOptions(
size: size, size: size,
center: true, center: false,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
skipTaskbar: false, skipTaskbar: false,
titleBarStyle: TitleBarStyle.hidden, titleBarStyle: TitleBarStyle.hidden,

View File

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

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common/formatter/id_formatter.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:get/get.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -10,12 +9,11 @@ import 'package:url_launcher/url_launcher.dart';
import '../../common.dart'; import '../../common.dart';
import '../../common/widgets/address_book.dart'; import '../../common/widgets/address_book.dart';
import '../../common/widgets/peer_tab_page.dart'; import '../../common/widgets/peer_tab_page.dart';
import '../../common/widgets/peer_widget.dart'; import '../../common/widgets/peers_view.dart';
import '../../consts.dart'; import '../../consts.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
import 'home_page.dart'; import 'home_page.dart';
import 'remote_page.dart';
import 'scan_page.dart'; import 'scan_page.dart';
import 'settings_page.dart'; import 'settings_page.dart';
@ -84,9 +82,9 @@ class _ConnectionPageState extends State<ConnectionPage> {
translate('Address Book') translate('Address Book')
], ],
children: [ children: [
RecentPeerWidget(), RecentPeersView(),
FavoritePeerWidget(), FavoritePeersView(),
DiscoveredPeerWidget(), DiscoveredPeersView(),
const AddressBook(), const AddressBook(),
], ],
)), )),
@ -97,38 +95,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
/// Connects to the selected peer. /// Connects to the selected peer.
void onConnect() { void onConnect() {
var id = _idController.id; var id = _idController.id;
connect(id); connect(context, 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();
}
} }
/// UI for software update. /// UI for software update.
@ -161,8 +128,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 2), padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 2),
child: Ink( child: Ink(
decoration: const BoxDecoration( decoration: BoxDecoration(
color: MyTheme.white, color: Theme.of(context).cardColor,
borderRadius: BorderRadius.all(Radius.circular(13)), borderRadius: BorderRadius.all(Radius.circular(13)),
), ),
child: Row( child: Row(

View File

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

View File

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

View File

@ -325,17 +325,13 @@ class _RemotePageState extends State<RemotePage> {
}, },
onPointerSignal: (e) { onPointerSignal: (e) {
if (e is PointerScrollEvent) { if (e is PointerScrollEvent) {
var dx = e.scrollDelta.dx; var dy = 0;
var dy = e.scrollDelta.dy; if (e.scrollDelta.dy > 0) {
if (dx > 0)
dx = -1;
else if (dx < 0) dx = 1;
if (dy > 0)
dy = -1; dy = -1;
else if (dy < 0) dy = 1; } else if (e.scrollDelta.dy < 0) {
bind.sessionSendMouse( dy = 1;
id: widget.id, }
msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}'); gFFI.scroll(dy);
} }
}, },
child: MouseRegion( child: MouseRegion(
@ -752,7 +748,7 @@ class _RemotePageState extends State<RemotePage> {
void changeTouchMode() { void changeTouchMode() {
setState(() => _showEdit = false); setState(() => _showEdit = false);
showModalBottomSheet( showModalBottomSheet(
backgroundColor: MyTheme.grayBg, // backgroundColor: MyTheme.grayBg,
isScrollControlled: true, isScrollControlled: true,
context: context, context: context,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
@ -968,7 +964,9 @@ class ImagePainter extends CustomPainter {
} }
} }
// TODO global widget
class QualityMonitor extends StatelessWidget { class QualityMonitor extends StatelessWidget {
static final textColor = Colors.grey.shade200;
@override @override
Widget build(BuildContext context) => ChangeNotifierProvider.value( Widget build(BuildContext context) => ChangeNotifierProvider.value(
value: gFFI.qualityMonitorModel, value: gFFI.qualityMonitorModel,
@ -985,23 +983,23 @@ class QualityMonitor extends StatelessWidget {
children: [ children: [
Text( Text(
"Speed: ${qualityMonitorModel.data.speed ?? ''}", "Speed: ${qualityMonitorModel.data.speed ?? ''}",
style: TextStyle(color: MyTheme.grayBg), style: TextStyle(color: textColor),
), ),
Text( Text(
"FPS: ${qualityMonitorModel.data.fps ?? ''}", "FPS: ${qualityMonitorModel.data.fps ?? ''}",
style: TextStyle(color: MyTheme.grayBg), style: TextStyle(color: textColor),
), ),
Text( Text(
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms", "Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
style: TextStyle(color: MyTheme.grayBg), style: TextStyle(color: textColor),
), ),
Text( Text(
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb", "Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
style: TextStyle(color: MyTheme.grayBg), style: TextStyle(color: textColor),
), ),
Text( Text(
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}", "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), icon: const Icon(Icons.perm_identity),
labelText: translate("ID"), labelText: translate("ID"),
labelStyle: const TextStyle( labelStyle: const TextStyle(
fontWeight: FontWeight.bold, color: MyTheme.accent50), fontWeight: FontWeight.bold, color: MyTheme.accent80),
), ),
onSaved: (String? value) {}, onSaved: (String? value) {},
), ),
@ -185,7 +185,7 @@ class ServerInfo extends StatelessWidget {
icon: const Icon(Icons.lock), icon: const Icon(Icons.lock),
labelText: translate("Password"), labelText: translate("Password"),
labelStyle: const TextStyle( labelStyle: const TextStyle(
fontWeight: FontWeight.bold, color: MyTheme.accent50), fontWeight: FontWeight.bold, color: MyTheme.accent80),
suffix: isPermanent suffix: isPermanent
? null ? null
: IconButton( : IconButton(
@ -213,7 +213,7 @@ class ServerInfo extends StatelessWidget {
fontFamily: 'WorkSans', fontFamily: 'WorkSans',
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 18, fontSize: 18,
color: MyTheme.accent80, color: MyTheme.accent,
), ),
)) ))
], ],
@ -304,7 +304,8 @@ class _PermissionCheckerState extends State<PermissionChecker> {
softWrap: true, softWrap: true,
style: const TextStyle( style: const TextStyle(
fontSize: 14.0, fontSize: 14.0,
color: MyTheme.accent50))) fontWeight: FontWeight.w500,
color: MyTheme.accent80)))
], ],
) )
: const SizedBox.shrink()) : const SizedBox.shrink())
@ -333,8 +334,13 @@ class PermissionRow extends StatelessWidget {
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text(name, child: Text(name,
style: const TextStyle( style: TextStyle(
fontSize: 16.0, color: MyTheme.accent50)))), fontSize: 16.0,
fontWeight:
Theme.of(context).brightness == Brightness.dark
? FontWeight.bold
: null,
color: MyTheme.accent80)))),
Expanded( Expanded(
flex: 2, flex: 2,
child: FittedBox( child: FittedBox(
@ -398,7 +404,7 @@ class ConnectionManager extends StatelessWidget {
}, },
icon: const Icon( icon: const Icon(
Icons.chat, Icons.chat,
color: MyTheme.accent80, color: MyTheme.accent,
))) )))
], ],
), ),
@ -406,7 +412,9 @@ class ConnectionManager extends StatelessWidget {
? const SizedBox.shrink() ? const SizedBox.shrink()
: Text( : Text(
translate("android_new_connection_tip"), translate("android_new_connection_tip"),
style: const TextStyle(color: Colors.black54), style: Theme.of(globalKey.currentContext!)
.textTheme
.bodyMedium,
), ),
client.authorized client.authorized
? ElevatedButton.icon( ? ElevatedButton.icon(
@ -460,8 +468,8 @@ class PaddingCard extends StatelessWidget {
titleIcon != null titleIcon != null
? Padding( ? Padding(
padding: const EdgeInsets.only(right: 10), padding: const EdgeInsets.only(right: 10),
child: Icon(titleIcon, child:
color: MyTheme.accent80, size: 30)) Icon(titleIcon, color: MyTheme.accent, size: 30))
: const SizedBox.shrink(), : const SizedBox.shrink(),
Text( Text(
title!, title!,
@ -469,7 +477,7 @@ class PaddingCard extends StatelessWidget {
fontFamily: 'WorkSans', fontFamily: 'WorkSans',
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 20, fontSize: 20,
color: MyTheme.accent80, color: MyTheme.accent,
), ),
) )
], ],

View File

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

View File

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

View File

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

View File

@ -436,7 +436,7 @@ class ServerModel with ChangeNotifier {
clientInfo(client), clientInfo(client),
Text( Text(
translate("android_new_connection_tip"), 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 // bdw->setCustomFrame(true); // <-- add this line
gtk_window_set_default_size(window, 800, 600); // <-- comment this line gtk_window_set_default_size(window, 800, 600); // <-- comment this line
gtk_widget_show(GTK_WIDGET(window)); gtk_widget_show(GTK_WIDGET(window));
gtk_widget_set_opacity(GTK_WIDGET(window), 0);
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
FlView* view = fl_view_new(project); FlView* view = fl_view_new(project);
gtk_widget_show(GTK_WIDGET(view)); 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)); 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" dependency: "direct main"
description: description:
path: "." path: "."
ref: fee851fa43116e0b91c39acd0ec37063dc6015f8 ref: "74c10aa49fecc088992a9edef1f6da39f83df505"
resolved-ref: fee851fa43116e0b91c39acd0ec37063dc6015f8 resolved-ref: "74c10aa49fecc088992a9edef1f6da39f83df505"
url: "https://github.com/Kingtous/rustdesk_desktop_multi_window" url: "https://github.com/Kingtous/rustdesk_desktop_multi_window"
source: git source: git
version: "0.1.0" version: "0.1.0"

View File

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

View File

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

View File

@ -53,7 +53,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project); FlutterWindow window(project);
Win32Window::Point origin(10, 10); Win32Window::Point origin(10, 10);
Win32Window::Size size(800, 600); Win32Window::Size size(800, 600);
if (!window.CreateAndShow(L"flutter_hbb", origin, size)) if (!window.CreateAndShow(L"rustdesk", origin, size))
{ {
return EXIT_FAILURE; 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] lang = os.path.basename(fn)[:-3]
if lang in ['en','cn']: continue if lang in ['en','cn']: continue
dict = get_lang(lang) 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'): for line in open('./src/lang/cn.rs'):
line_strip = line.strip() line_strip = line.strip()
if line_strip.startswith('("'): if line_strip.startswith('("'):

View File

@ -15,18 +15,7 @@ use hbb_common::{message_proto::Hash, ResultType};
use crate::flutter::{self, SESSIONS}; use crate::flutter::{self, SESSIONS};
use crate::start_server; use crate::start_server;
use crate::ui_interface; use crate::ui_interface::{self, *};
#[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::{ use crate::{
client::file_trait::FileManager, client::file_trait::FileManager,
flutter::{make_fd_to_json, session_add, session_start_}, flutter::{make_fd_to_json, session_add, session_start_},
@ -482,6 +471,9 @@ pub fn main_get_app_name() -> String {
get_app_name() get_app_name()
} }
pub fn main_get_app_name_sync() -> SyncReturn<String> {
SyncReturn(get_app_name())
}
pub fn main_get_license() -> String { pub fn main_get_license() -> String {
get_license() get_license()
} }
@ -563,14 +555,27 @@ pub fn main_get_peer_option(id: String, key: String) -> String {
get_peer_option(id, key) 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) { pub fn main_set_peer_option(id: String, key: String, value: String) {
set_peer_option(id, key, value) 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) { pub fn main_forget_password(id: String) {
forget_password(id) forget_password(id)
} }
pub fn main_peer_has_password(id: String) -> bool {
peer_has_password(id)
}
pub fn main_get_recent_peers() -> String { pub fn main_get_recent_peers() -> String {
if !config::APP_DIR.read().unwrap().is_empty() { if !config::APP_DIR.read().unwrap().is_empty() {
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers() 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) { pub fn main_change_theme(dark: String) {
main_broadcast_message(&HashMap::from([ main_broadcast_message(&HashMap::from([("name", "theme"), ("dark", &dark)]));
("name", "theme"),
("dark", &dark),
]));
send_to_cm(&crate::ipc::Data::Theme(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) 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")] #[cfg(target_os = "android")]
pub mod server_side { pub mod server_side {
use jni::{ 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]) { if let Ok(msg_in) = Message::parse_from_bytes(&buf[0..len]) {
match msg_in.union { match msg_in.union {
Some(rendezvous_message::Union::PeerDiscovery(p)) => { 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) { if let Some(self_addr) = get_ipaddr_by_peer(&addr) {
let mut msg_out = Message::new(); let mut msg_out = Message::new();
let peer = PeerDiscovery { let peer = PeerDiscovery {

View File

@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "适应窗口"), ("Scale adaptive", "适应窗口"),
("General", "常规"), ("General", "常规"),
("Security", "安全"), ("Security", "安全"),
("Acount", "账户"), ("Account", "账户"),
("Theme", "主题"), ("Theme", "主题"),
("Dark Theme", "暗黑主题"), ("Dark Theme", "暗黑主题"),
("Dark", "黑暗"),
("Light", "明亮"),
("Follow System", "跟随系统"),
("Enable hardware codec", "使用硬件编解码"), ("Enable hardware codec", "使用硬件编解码"),
("Unlock Security Settings", "解锁安全设置"), ("Unlock Security Settings", "解锁安全设置"),
("Enable Audio", "允许传输音频"), ("Enable Audio", "允许传输音频"),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", "结束录屏"), ("Stop session recording", "结束录屏"),
("Enable Recording Session", "允许录制会话"), ("Enable Recording Session", "允许录制会话"),
("Allow recording session", "允许录制会话"), ("Allow recording session", "允许录制会话"),
("Enable LAN Discovery", "允许局域网发现"),
("Deny LAN Discovery", "拒绝局域网发现"),
("Write a message", "输入聊天消息"),
].iter().cloned().collect(); ].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"), ("Reboot required", "Je třeba restartovat"),
("Unsupported display server ", "Nepodporovaný zobrazovací server"), ("Unsupported display server ", "Nepodporovaný zobrazovací server"),
("x11 expected", "očekávány x11"), ("x11 expected", "očekávány x11"),
("Port", "Číslo portu"), ("Port", ""),
("Settings", "Nastavení"), ("Settings", "Nastavení"),
("Username", "Uživatelské jméno"), ("Username", "Uživatelské jméno"),
("Invalid port", "Neplatné číslo portu"), ("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_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_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]."), ("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"), ("Overwrite", "Přepsat"),
("This file exists, skip or overwrite this file?", "Tento soubor existuje přeskočit ho nebo přepsat?"), ("This file exists, skip or overwrite this file?", "Tento soubor existuje přeskočit ho nebo přepsat?"),
("Quit", "Ukončit"), ("Quit", "Ukončit"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Měřítko adaptivní"), ("Scale adaptive", "Měřítko adaptivní"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].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"), ("Reboot required", "Genstart krævet"),
("Unsupported display server ", "Ikke-understøttet displayserver"), ("Unsupported display server ", "Ikke-understøttet displayserver"),
("x11 expected", "X11 Forventet"), ("x11 expected", "X11 Forventet"),
("Port", "Port"), ("Port", ""),
("Settings", "Indstillinger"), ("Settings", "Indstillinger"),
("Username", " Brugernavn"), ("Username", " Brugernavn"),
("Invalid port", "Ugyldig port"), ("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_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_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."), ("android_start_service_tip", "Tryk på [Start Service] eller åbn autorisationen [skærmoptagelse] for at starte skærmudgivelsen."),
("Account", "Konto"), ("Account", ""),
("Overwrite", "Overskriv"), ("Overwrite", "Overskriv"),
("This file exists, skip or overwrite this file?", "Denne fil findes, springer over denne fil eller overskriver?"), ("This file exists, skip or overwrite this file?", "Denne fil findes, springer over denne fil eller overskriver?"),
("Quit", "Afslut"), ("Quit", "Afslut"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Skala adaptiv"), ("Scale adaptive", "Skala adaptiv"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Neustart erforderlich"), ("Reboot required", "Neustart erforderlich"),
("Unsupported display server ", "Nicht unterstützter Display-Server"), ("Unsupported display server ", "Nicht unterstützter Display-Server"),
("x11 expected", "X11 erwartet"), ("x11 expected", "X11 erwartet"),
("Port", "Port"), ("Port", ""),
("Settings", "Einstellungen"), ("Settings", "Einstellungen"),
("Username", " Benutzername"), ("Username", " Benutzername"),
("Invalid port", "Ungültiger Port"), ("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_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_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."), ("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"), ("Overwrite", "Überschreiben"),
("This file exists, skip or overwrite this file?", "Diese Datei existiert; überspringen oder überschreiben?"), ("This file exists, skip or overwrite this file?", "Diese Datei existiert; überspringen oder überschreiben?"),
("Quit", "Beenden"), ("Quit", "Beenden"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Adaptiv skalieren"), ("Scale adaptive", "Adaptiv skalieren"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

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

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Reinicio requerido"), ("Reboot required", "Reinicio requerido"),
("Unsupported display server ", "Servidor de visualización no compatible"), ("Unsupported display server ", "Servidor de visualización no compatible"),
("x11 expected", "x11 necesario"), ("x11 expected", "x11 necesario"),
("Port", "Puerto"), ("Port", ""),
("Settings", "Ajustes"), ("Settings", "Ajustes"),
("Username", " Nombre de usuario"), ("Username", " Nombre de usuario"),
("Invalid port", "Puerto inválido"), ("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_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_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."), ("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"), ("Overwrite", "Sobrescribir"),
("This file exists, skip or overwrite this file?", "Este archivo existe, ¿omitir o sobrescribir este archivo?"), ("This file exists, skip or overwrite this file?", "Este archivo existe, ¿omitir o sobrescribir este archivo?"),
("Quit", "Salir"), ("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"), ("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"), ("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""), ("Connection not allowed", "Conexión no disponible"),
("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", ""),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),
("Translate mode", ""), ("Translate mode", ""),
("Connection not allowed", "Conexión no disponible"),
("Use temporary password", "Usar contraseña temporal"), ("Use temporary password", "Usar contraseña temporal"),
("Use permanent password", "Usar contraseña permamente"), ("Use permanent password", "Usar contraseña permamente"),
("Use both passwords", "Usar ambas comtraseñas"), ("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"), ("Scale adaptive", "Adaptable a escala"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -367,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].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"), ("Reboot required", "Redémarrage pour prendre effet"),
("Unsupported display server ", "Le serveur d'affichage actuel n'est pas pris en charge"), ("Unsupported display server ", "Le serveur d'affichage actuel n'est pas pris en charge"),
("x11 expected", "Veuillez passer à x11"), ("x11 expected", "Veuillez passer à x11"),
("Port", "Port"), ("Port", ""),
("Settings", "Paramètres"), ("Settings", "Paramètres"),
("Username", " Nom d'utilisateur"), ("Username", " Nom d'utilisateur"),
("Invalid port", "Port invalide"), ("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_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_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."), ("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"), ("Overwrite", "Écraser"),
("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"), ("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"),
("Quit", "Quitter"), ("Quit", "Quitter"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Échelle adaptative"), ("Scale adaptive", "Échelle adaptative"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].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"), ("Reboot required", "Újraindítás szükséges"),
("Unsupported display server ", "Nem támogatott kijelző szerver"), ("Unsupported display server ", "Nem támogatott kijelző szerver"),
("x11 expected", "x11-re számítottt"), ("x11 expected", "x11-re számítottt"),
("Port", "Port"), ("Port", ""),
("Settings", "Beállítások"), ("Settings", "Beállítások"),
("Username", "Felhasználónév"), ("Username", "Felhasználónév"),
("Invalid port", "Érvénytelen port"), ("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_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_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."), ("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"), ("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?"), ("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"), ("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"), ("Scale adaptive", "Skála adaptív"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].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"), ("Reboot required", "Diperlukan boot ulang"),
("Unsupported display server ", "Server tampilan tidak didukung "), ("Unsupported display server ", "Server tampilan tidak didukung "),
("x11 expected", "x11 diharapkan"), ("x11 expected", "x11 diharapkan"),
("Port", "Port"), ("Port", ""),
("Settings", "Pengaturan"), ("Settings", "Pengaturan"),
("Username", "Username"), ("Username", "Username"),
("Invalid port", "Kesalahan port"), ("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_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_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."), ("android_start_service_tip", "Ketuk izin [Mulai Layanan] atau BUKA [Tangkapan Layar] untuk memulai layanan berbagi layar."),
("Account", "Akun"), ("Account", ""),
("Overwrite", "Timpa"), ("Overwrite", "Timpa"),
("This file exists, skip or overwrite this file?", "File ini sudah ada, lewati atau timpa file ini?"), ("This file exists, skip or overwrite this file?", "File ini sudah ada, lewati atau timpa file ini?"),
("Quit", "Keluar"), ("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"), ("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"),
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"), ("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""), ("Connection not allowed", "Koneksi tidak dijinkan"),
("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", ""),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),
("Translate mode", ""), ("Translate mode", ""),
("Connection not allowed", "Koneksi tidak dijinkan"),
("Use temporary password", "Gunakan kata sandi sementara"), ("Use temporary password", "Gunakan kata sandi sementara"),
("Use permanent password", "Gunakan kata sandi permanaen"), ("Use permanent password", "Gunakan kata sandi permanaen"),
("Use both passwords", "Gunakan kedua kata sandi "), ("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"), ("Scale adaptive", "Skala adaptif"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -367,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Riavvio necessario"), ("Reboot required", "Riavvio necessario"),
("Unsupported display server ", "Display server non supportato"), ("Unsupported display server ", "Display server non supportato"),
("x11 expected", "x11 necessario"), ("x11 expected", "x11 necessario"),
("Port", "Porta"), ("Port", ""),
("Settings", "Impostazioni"), ("Settings", "Impostazioni"),
("Username", " Nome utente"), ("Username", " Nome utente"),
("Invalid port", "Porta non valida"), ("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_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_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."), ("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"), ("Overwrite", "Sovrascrivi"),
("This file exists, skip or overwrite this file?", "Questo file esiste, saltare o sovrascrivere questo file?"), ("This file exists, skip or overwrite this file?", "Questo file esiste, saltare o sovrascrivere questo file?"),
("Quit", "Esci"), ("Quit", "Esci"),
@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Connection not allowed", "Connessione non consentita"), ("Connection not allowed", "Connessione non consentita"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "Usa password temporanea"), ("Use temporary password", "Usa password temporanea"),
("Use permanent password", "Usa password permanente"), ("Use permanent password", "Usa password permanente"),
("Use both passwords", "Usa entrambe le password"), ("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?"), ("Are you sure you want to restart", "Sei sicuro di voler riavviare?"),
("Restarting Remote Device", "Il dispositivo remoto si sta riavviando"), ("Restarting Remote Device", "Il dispositivo remoto si sta riavviando"),
("remote_restarting_tip", "Riavviare il dispositivo remoto"), ("remote_restarting_tip", "Riavviare il dispositivo remoto"),
("Copied", ""),
("Exit Fullscreen", "Esci dalla modalità schermo intero"), ("Exit Fullscreen", "Esci dalla modalità schermo intero"),
("Fullscreen", "A schermo intero"), ("Fullscreen", "A schermo intero"),
("Mobile Actions", "Azioni mobili"), ("Mobile Actions", "Azioni mobili"),
@ -318,14 +322,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Insecure Connection", "Connessione insicura"), ("Insecure Connection", "Connessione insicura"),
("Scale original", "Scala originale"), ("Scale original", "Scala originale"),
("Scale adaptive", "Scala adattiva"), ("Scale adaptive", "Scala adattiva"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -353,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

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

View File

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

View File

@ -329,5 +329,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow 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(); ].iter().cloned().collect();
} }

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Wymagany restart"), ("Reboot required", "Wymagany restart"),
("Unsupported display server ", "Nieobsługiwany serwer wyświetlania "), ("Unsupported display server ", "Nieobsługiwany serwer wyświetlania "),
("x11 expected", "oczekiwane X11"), ("x11 expected", "oczekiwane X11"),
("Port", "Port"), ("Port", ""),
("Settings", "Ustawienia"), ("Settings", "Ustawienia"),
("Username", "Nazwa użytkownika"), ("Username", "Nazwa użytkownika"),
("Invalid port", "Nieprawidłowy port"), ("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_stop_service_tip", "android_stop_service_tip"),
("android_version_audio_tip", "android_version_audio_tip"), ("android_version_audio_tip", "android_version_audio_tip"),
("android_start_service_tip", "android_start_service_tip"), ("android_start_service_tip", "android_start_service_tip"),
("Account", "Konto"), ("Account", ""),
("Overwrite", "Nadpisz"), ("Overwrite", "Nadpisz"),
("This file exists, skip or overwrite this file?", "Ten plik istnieje, pominąć czy nadpisać ten plik?"), ("This file exists, skip or overwrite this file?", "Ten plik istnieje, pominąć czy nadpisać ten plik?"),
("Quit", "Zrezygnuj"), ("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"), ("Keep RustDesk background service", "Zachowaj usługę w tle RustDesk"),
("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"), ("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"),
("android_open_battery_optimizations_tip", "android_open_battery_optimizations_tip"), ("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"), ("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", ""), ("Copied", ""),
("Exit Fullscreen", "Wyłączyć tryb pełnoekranowy"), ("Exit Fullscreen", "Wyłączyć tryb pełnoekranowy"),
("Fullscreen", "Pełny ekran"), ("Fullscreen", "Pełny ekran"),
@ -322,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Skala adaptacyjna"), ("Scale adaptive", "Skala adaptacyjna"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -352,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Original"), ("Original", "Original"),
("Shrink", "Reduzir"), ("Shrink", "Reduzir"),
("Stretch", "Aumentar"), ("Stretch", "Aumentar"),
("Scrollbar", ""),
("ScrollAuto", ""),
("Good image quality", "Qualidade visual boa"), ("Good image quality", "Qualidade visual boa"),
("Balanced", "Equilibrada"), ("Balanced", "Equilibrada"),
("Optimize reaction time", "Optimizar tempo de reacção"), ("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"), ("Set Password", "Definir palavra-chave"),
("OS Password", "Senha do SO"), ("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."), ("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 download", "Clique para carregar"),
("Click to update", "Clique para fazer a actualização"), ("Click to update", "Clique para fazer a actualização"),
("Configure", "Configurar"), ("Configure", "Configurar"),
@ -185,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Reinicialização necessária"), ("Reboot required", "Reinicialização necessária"),
("Unsupported display server ", "Servidor de display não suportado"), ("Unsupported display server ", "Servidor de display não suportado"),
("x11 expected", "x11 em falha"), ("x11 expected", "x11 em falha"),
("Port", "Porta"), ("Port", ""),
("Settings", "Configurações"), ("Settings", "Configurações"),
("Username", "Nome de utilizador"), ("Username", "Nome de utilizador"),
("Invalid port", "Porta inválida"), ("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_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_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."), ("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"), ("Overwrite", "Substituir"),
("This file exists, skip or overwrite this file?", "Este ficheiro já existe, ignorar ou substituir este ficheiro?"), ("This file exists, skip or overwrite this file?", "Este ficheiro já existe, ignorar ou substituir este ficheiro?"),
("Quit", "Saída"), ("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"), ("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Connection not allowed", "Ligação não autorizada"), ("Connection not allowed", "Ligação não autorizada"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "Utilizar palavra-chave temporária"), ("Use temporary password", "Utilizar palavra-chave temporária"),
("Use permanent password", "Utilizar palavra-chave permanente"), ("Use permanent password", "Utilizar palavra-chave permanente"),
("Use both passwords", "Utilizar ambas as palavras-chave"), ("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"), ("Are you sure you want to restart", "Tem a certeza que pretende reiniciar"),
("Restarting Remote Device", "A reiniciar sistema remoto"), ("Restarting Remote Device", "A reiniciar sistema remoto"),
("remote_restarting_tip", ""), ("remote_restarting_tip", ""),
("Copied", ""),
("Exit Fullscreen", "Sair da tela cheia"), ("Exit Fullscreen", "Sair da tela cheia"),
("Fullscreen", "Tela cheia"), ("Fullscreen", "Tela cheia"),
("Mobile Actions", "Ações para celular"), ("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"), ("Scale adaptive", "Escala adaptável"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -348,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].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"), ("Reboot required", "Reinicialização necessária"),
("Unsupported display server ", "Servidor de display não suportado"), ("Unsupported display server ", "Servidor de display não suportado"),
("x11 expected", "x11 esperado"), ("x11 expected", "x11 esperado"),
("Port", "Porta"), ("Port", ""),
("Settings", "Configurações"), ("Settings", "Configurações"),
("Username", "Nome de usuário"), ("Username", "Nome de usuário"),
("Invalid port", "Porta inválida"), ("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_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_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."), ("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"), ("Overwrite", "Substituir"),
("This file exists, skip or overwrite this file?", "Este arquivo existe, pular ou substituir este arquivo?"), ("This file exists, skip or overwrite this file?", "Este arquivo existe, pular ou substituir este arquivo?"),
("Quit", "Saída"), ("Quit", "Saída"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", ""), ("Scale adaptive", ""),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "Требуется перезагрузка"), ("Reboot required", "Требуется перезагрузка"),
("Unsupported display server ", "Неподдерживаемый сервер дисплея"), ("Unsupported display server ", "Неподдерживаемый сервер дисплея"),
("x11 expected", "Ожидается X11"), ("x11 expected", "Ожидается X11"),
("Port", "Порт"), ("Port", ""),
("Settings", "Настройки"), ("Settings", "Настройки"),
("Username", "Имя пользователя"), ("Username", "Имя пользователя"),
("Invalid port", "Неверный порт"), ("Invalid 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 не поддерживает захват звука, обновите ее до Android 10 или выше."), ("android_version_audio_tip", "Текущая версия Android не поддерживает захват звука, обновите ее до Android 10 или выше."),
("android_start_service_tip", "Нажмите [Запуск промежуточного сервера] или ОТКРЫТЬ разрешение [Захват экрана], чтобы запустить службу демонстрации экрана."), ("android_start_service_tip", "Нажмите [Запуск промежуточного сервера] или ОТКРЫТЬ разрешение [Захват экрана], чтобы запустить службу демонстрации экрана."),
("Account", "Аккаунт"), ("Account", ""),
("Overwrite", "Перезаписать"), ("Overwrite", "Перезаписать"),
("This file exists, skip or overwrite this file?", "Этот файл существует, пропустить или перезаписать файл?"), ("This file exists, skip or overwrite this file?", "Этот файл существует, пропустить или перезаписать файл?"),
("Quit", "Выйти"), ("Quit", "Выйти"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Масштаб адаптивный"), ("Scale adaptive", "Масштаб адаптивный"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].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"), ("Reboot required", "Vyžaduje sa reštart"),
("Unsupported display server ", "Nepodporovaný zobrazovací (display) server"), ("Unsupported display server ", "Nepodporovaný zobrazovací (display) server"),
("x11 expected", "očakáva sa x11"), ("x11 expected", "očakáva sa x11"),
("Port", "Port"), ("Port", ""),
("Settings", "Nastavenia"), ("Settings", "Nastavenia"),
("Username", "Uživateľské meno"), ("Username", "Uživateľské meno"),
("Invalid port", "Neplatný port"), ("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_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_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."), ("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ť"), ("Overwrite", "Prepísať"),
("This file exists, skip or overwrite this file?", "Preskočiť alebo prepísať existujúci súbor?"), ("This file exists, skip or overwrite this file?", "Preskočiť alebo prepísať existujúci súbor?"),
("Quit", "Ukončiť"), ("Quit", "Ukončiť"),
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "Prispôsobivá mierka"), ("Scale adaptive", "Prispôsobivá mierka"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", ""), ("Scale adaptive", ""),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].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"), ("Reboot required", "Yeniden başlatma gerekli"),
("Unsupported display server ", "Desteklenmeyen görüntü sunucusu"), ("Unsupported display server ", "Desteklenmeyen görüntü sunucusu"),
("x11 expected", "x11 bekleniyor"), ("x11 expected", "x11 bekleniyor"),
("Port", "Port"), ("Port", ""),
("Settings", "Ayarlar"), ("Settings", "Ayarlar"),
("Username", "Kullanıcı Adı"), ("Username", "Kullanıcı Adı"),
("Invalid port", "Geçersiz port"), ("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_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_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."), ("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"), ("Overwrite", "üzerine yaz"),
("This file exists, skip or overwrite this file?", "Bu dosya var, bu dosya atlansın veya üzerine yazılsın mı?"), ("This file exists, skip or overwrite this file?", "Bu dosya var, bu dosya atlansın veya üzerine yazılsın mı?"),
("Quit", "Çıkış"), ("Quit", "Çıkış"),
@ -289,23 +289,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""), ("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""), ("Connection not allowed", "bağlantıya izin verilmedi"),
("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", ""),
("Legacy mode", ""), ("Legacy mode", ""),
("Map mode", ""), ("Map mode", ""),
("Translate mode", ""), ("Translate mode", ""),
("Connection not allowed", "bağlantıya izin verilmedi"),
("Use temporary password", "Geçici şifre kullan"), ("Use temporary password", "Geçici şifre kullan"),
("Use permanent password", "Kalıcı şifre kullan"), ("Use permanent password", "Kalıcı şifre kullan"),
("Use both passwords", "İki şifreyide 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"), ("Scale adaptive", "Ölçek uyarlanabilir"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -367,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Reboot required", "需要重新啟動"), ("Reboot required", "需要重新啟動"),
("Unsupported display server ", "不支援顯示伺服器"), ("Unsupported display server ", "不支援顯示伺服器"),
("x11 expected", "預期 x11"), ("x11 expected", "預期 x11"),
("Port", "連接埠"), ("Port", "端口"),
("Settings", "設定"), ("Settings", "設定"),
("Username", "使用者名稱"), ("Username", "使用者名稱"),
("Invalid port", "連接埠無效"), ("Invalid 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 版本不支持音訊錄製,請升級至 Android 10 或以上版本。"), ("android_version_audio_tip", "目前的 Android 版本不支持音訊錄製,請升級至 Android 10 或以上版本。"),
("android_start_service_tip", "點擊 「啟動服務」 或啟用 「畫面錄製」 權限以開啟手機畫面共享服務。"), ("android_start_service_tip", "點擊 「啟動服務」 或啟用 「畫面錄製」 權限以開啟手機畫面共享服務。"),
("Account", ""), ("Account", ""),
("Overwrite", "覆寫"), ("Overwrite", "覆寫"),
("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要跳過或是覆寫此檔案嗎?"), ("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要跳過或是覆寫此檔案嗎?"),
("Quit", "退出"), ("Quit", "退出"),
@ -324,14 +324,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Scale adaptive", "適應窗口"), ("Scale adaptive", "適應窗口"),
("General", "常規"), ("General", "常規"),
("Security", "安全"), ("Security", "安全"),
("Acount", "賬戶"), ("Account", "賬戶"),
("Theme", "主題"), ("Theme", "主題"),
("Dark Theme", "暗黑主題"), ("Dark Theme", "暗黑主題"),
("Dark", "黑暗"),
("Light", "明亮"),
("Follow System", "跟隨系統"),
("Enable hardware codec", "使用硬件編解碼"), ("Enable hardware codec", "使用硬件編解碼"),
("Unlock Security Settings", "解鎖安全設置"), ("Unlock Security Settings", "解鎖安全設置"),
("Enable Audio", "允許傳輸音頻"), ("Enable Audio", "允許傳輸音頻"),
("Temporary Password Length", "临时密码长"), ("Temporary Password Length", "臨時密碼長"),
("Unlock Network Settings", "臨時密碼長度"), ("Unlock Network Settings", "解鎖網絡設置"),
("Server", "服務器"), ("Server", "服務器"),
("Direct IP Access", "IP直接訪問"), ("Direct IP Access", "IP直接訪問"),
("Proxy", "代理"), ("Proxy", "代理"),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", "結束錄屏"), ("Stop session recording", "結束錄屏"),
("Enable Recording Session", "允許錄製會話"), ("Enable Recording Session", "允許錄製會話"),
("Allow recording session", "允許錄製會話"), ("Allow recording session", "允許錄製會話"),
("Enable LAN Discovery", "允許局域網發現"),
("Deny LAN Discovery", "拒絕局域網發現"),
("Write a message", "輸入聊天消息"),
].iter().cloned().collect(); ].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"), ("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ọ"), ("Unsupported display server ", "Máy chủ hiển thị không đuợc hỗ trọ"),
("x11 expected", "Cần x11"), ("x11 expected", "Cần x11"),
("Port", "Cổng"), ("Port", ""),
("Settings", "Cài đặt"), ("Settings", "Cài đặt"),
("Username", "Tên người dùng"), ("Username", "Tên người dùng"),
("Invalid port", "Cổng không hợp lệ"), ("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_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_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."), ("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 đè"), ("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?"), ("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"), ("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"), ("Scale adaptive", "Quy mô thích ứng"),
("General", ""), ("General", ""),
("Security", ""), ("Security", ""),
("Acount", ""), ("Account", ""),
("Theme", ""), ("Theme", ""),
("Dark Theme", ""), ("Dark Theme", ""),
("Dark", ""),
("Light", ""),
("Follow System", ""),
("Enable hardware codec", ""), ("Enable hardware codec", ""),
("Unlock Security Settings", ""), ("Unlock Security Settings", ""),
("Enable Audio", ""), ("Enable Audio", ""),
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Stop session recording", ""), ("Stop session recording", ""),
("Enable Recording Session", ""), ("Enable Recording Session", ""),
("Allow recording session", ""), ("Allow recording session", ""),
("Enable LAN Discovery", ""),
("Deny LAN Discovery", ""),
("Write a message", ""),
].iter().cloned().collect(); ].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-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-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-tunnel><span>{svg_checkmark}</span>{translate('Enable TCP Tunneling')}</li>
<li #enable-lan-discovery><span>{svg_checkmark}</span>{translate('Enable LAN Discovery')}</li>
<AudioInputs /> <AudioInputs />
<Enhancements /> <Enhancements />
<li #allow-remote-config-modification><span>{svg_checkmark}</span>{translate('Enable remote configuration modification')}</li> <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(); .ok();
} }
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[inline] #[inline]
pub fn is_installed() -> bool { 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] #[inline]