Merge branch 'master' into record
4
.gitignore
vendored
@ -35,4 +35,6 @@ flatpak/ccache/**
|
||||
flatpak/.flatpak-builder/build/**
|
||||
flatpak/.flatpak-builder/shared-modules/**
|
||||
flatpak/.flatpak-builder/shared-modules/*.tar.xz
|
||||
flatpak/.flatpak-builder/debian-binary
|
||||
flatpak/.flatpak-builder/debian-binary
|
||||
# bridge file
|
||||
lib/generated_bridge.dart
|
||||
|
@ -17,6 +17,8 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'common/widgets/overlay.dart';
|
||||
import 'mobile/pages/file_manager_page.dart';
|
||||
import 'mobile/pages/remote_page.dart';
|
||||
import 'models/model.dart';
|
||||
import 'models/platform_model.dart';
|
||||
|
||||
@ -76,59 +78,22 @@ class IconFont {
|
||||
|
||||
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
const ColorThemeExtension({
|
||||
required this.bg,
|
||||
required this.grayBg,
|
||||
required this.text,
|
||||
required this.lightText,
|
||||
required this.lighterText,
|
||||
required this.placeholder,
|
||||
required this.border,
|
||||
});
|
||||
|
||||
final Color? bg;
|
||||
final Color? grayBg;
|
||||
final Color? text;
|
||||
final Color? lightText;
|
||||
final Color? lighterText;
|
||||
final Color? placeholder;
|
||||
final Color? border;
|
||||
|
||||
static const light = ColorThemeExtension(
|
||||
bg: Color(0xFFFFFFFF),
|
||||
grayBg: Color(0xFFEEEEEE),
|
||||
text: Color(0xFF222222),
|
||||
lightText: Color(0xFF666666),
|
||||
lighterText: Color(0xFF888888),
|
||||
placeholder: Color(0xFFAAAAAA),
|
||||
border: Color(0xFFCCCCCC),
|
||||
);
|
||||
|
||||
static const dark = ColorThemeExtension(
|
||||
bg: Color(0xFF252525),
|
||||
grayBg: Color(0xFF141414),
|
||||
text: Color(0xFFFFFFFF),
|
||||
lightText: Color(0xFF999999),
|
||||
lighterText: Color(0xFF777777),
|
||||
placeholder: Color(0xFF555555),
|
||||
border: Color(0xFF555555),
|
||||
);
|
||||
|
||||
@override
|
||||
ThemeExtension<ColorThemeExtension> copyWith(
|
||||
{Color? bg,
|
||||
Color? grayBg,
|
||||
Color? text,
|
||||
Color? lightText,
|
||||
Color? lighterText,
|
||||
Color? placeholder,
|
||||
Color? border}) {
|
||||
ThemeExtension<ColorThemeExtension> copyWith({Color? border}) {
|
||||
return ColorThemeExtension(
|
||||
bg: bg ?? this.bg,
|
||||
grayBg: grayBg ?? this.grayBg,
|
||||
text: text ?? this.text,
|
||||
lightText: lightText ?? this.lightText,
|
||||
lighterText: lighterText ?? this.lighterText,
|
||||
placeholder: placeholder ?? this.placeholder,
|
||||
border: border ?? this.border,
|
||||
);
|
||||
}
|
||||
@ -140,12 +105,6 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
return this;
|
||||
}
|
||||
return ColorThemeExtension(
|
||||
bg: Color.lerp(bg, other.bg, t),
|
||||
grayBg: Color.lerp(grayBg, other.grayBg, t),
|
||||
text: Color.lerp(text, other.text, t),
|
||||
lightText: Color.lerp(lightText, other.lightText, t),
|
||||
lighterText: Color.lerp(lighterText, other.lighterText, t),
|
||||
placeholder: Color.lerp(placeholder, other.placeholder, t),
|
||||
border: Color.lerp(border, other.border, t),
|
||||
);
|
||||
}
|
||||
@ -170,6 +129,14 @@ class MyTheme {
|
||||
|
||||
static ThemeData lightTheme = ThemeData(
|
||||
brightness: Brightness.light,
|
||||
backgroundColor: Color(0xFFFFFFFF),
|
||||
scaffoldBackgroundColor: Color(0xFFEEEEEE),
|
||||
textTheme: const TextTheme(
|
||||
titleLarge: TextStyle(fontSize: 19, color: Colors.black87),
|
||||
bodySmall: TextStyle(fontSize: 12, color: Colors.black54, height: 1.25),
|
||||
bodyMedium: TextStyle(fontSize: 14, color: Colors.black54, height: 1.25),
|
||||
),
|
||||
hintColor: Color(0xFFAAAAAA),
|
||||
primarySwatch: Colors.blue,
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
tabBarTheme: const TabBarTheme(
|
||||
@ -177,6 +144,12 @@ class MyTheme {
|
||||
),
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
splashFactory: isDesktop ? NoSplash.splashFactory : null,
|
||||
textButtonTheme: isDesktop
|
||||
? TextButtonThemeData(
|
||||
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
|
||||
)
|
||||
: null,
|
||||
).copyWith(
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
ColorThemeExtension.light,
|
||||
@ -185,6 +158,13 @@ class MyTheme {
|
||||
);
|
||||
static ThemeData darkTheme = ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
backgroundColor: Color(0xFF252525),
|
||||
scaffoldBackgroundColor: Color(0xFF141414),
|
||||
textTheme: const TextTheme(
|
||||
titleLarge: TextStyle(fontSize: 19),
|
||||
bodySmall: TextStyle(fontSize: 12, height: 1.25),
|
||||
bodyMedium: TextStyle(fontSize: 14, height: 1.25)),
|
||||
cardColor: Color(0xFF252525),
|
||||
primarySwatch: Colors.blue,
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
tabBarTheme: const TabBarTheme(
|
||||
@ -192,6 +172,12 @@ class MyTheme {
|
||||
),
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
splashFactory: isDesktop ? NoSplash.splashFactory : null,
|
||||
textButtonTheme: isDesktop
|
||||
? TextButtonThemeData(
|
||||
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
|
||||
)
|
||||
: null,
|
||||
).copyWith(
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
ColorThemeExtension.dark,
|
||||
@ -1073,14 +1059,38 @@ void connect(BuildContext context, String id,
|
||||
assert(!(isFileTransfer && isTcpTunneling && isRDP),
|
||||
"more than one connect type");
|
||||
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (isFileTransfer) {
|
||||
await rustDeskWinManager.newFileTransfer(id);
|
||||
} else if (isTcpTunneling || isRDP) {
|
||||
await rustDeskWinManager.newPortForward(id, isRDP);
|
||||
if (isDesktop) {
|
||||
if (isFileTransfer) {
|
||||
await rustDeskWinManager.newFileTransfer(id);
|
||||
} else if (isTcpTunneling || isRDP) {
|
||||
await rustDeskWinManager.newPortForward(id, isRDP);
|
||||
} else {
|
||||
await rustDeskWinManager.newRemoteDesktop(id);
|
||||
}
|
||||
} else {
|
||||
await rustDeskWinManager.newRemoteDesktop(id);
|
||||
if (isFileTransfer) {
|
||||
if (!await PermissionManager.check("file")) {
|
||||
if (!await PermissionManager.request("file")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => FileManagerPage(id: id),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => RemotePage(id: id),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.unfocus();
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
import 'package:contextmenu/contextmenu.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peer_widget.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peers_view.dart';
|
||||
import 'package:flutter_hbb/models/ab_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../desktop/pages/desktop_home_page.dart';
|
||||
import '../../mobile/pages/settings_page.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
|
||||
class AddressBook extends StatefulWidget {
|
||||
const AddressBook({Key? key}) : super(key: key);
|
||||
final EdgeInsets? menuPadding;
|
||||
const AddressBook({Key? key, this.menuPadding}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
@ -37,11 +39,16 @@ class _AddressBookState extends State<AddressBook> {
|
||||
});
|
||||
|
||||
handleLogin() {
|
||||
loginDialog().then((success) {
|
||||
if (success) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
// TODO refactor login dialog for desktop and mobile
|
||||
if (isDesktop) {
|
||||
loginDialog().then((success) {
|
||||
if (success) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
showLogin(gFFI.dialogManager);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Widget> buildAddressBook(BuildContext context) async {
|
||||
@ -108,7 +115,8 @@ class _AddressBookState extends State<AddressBook> {
|
||||
Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
side: const BorderSide(color: MyTheme.grayBg)),
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).scaffoldBackgroundColor)),
|
||||
child: Container(
|
||||
width: 200,
|
||||
height: double.infinity,
|
||||
@ -174,7 +182,9 @@ class _AddressBookState extends State<AddressBook> {
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: AddressBookPeerWidget()),
|
||||
child: AddressBookPeersView(
|
||||
menuPadding: widget.menuPadding,
|
||||
)),
|
||||
)
|
||||
],
|
||||
));
|
||||
@ -206,7 +216,8 @@ class _AddressBookState extends State<AddressBook> {
|
||||
child: Text(
|
||||
tagName,
|
||||
style: TextStyle(
|
||||
color: rxTags.contains(tagName) ? MyTheme.white : null),
|
||||
color:
|
||||
rxTags.contains(tagName) ? Colors.white : null), // TODO
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class DraggableChatWindow extends StatelessWidget {
|
||||
position: position,
|
||||
width: width,
|
||||
height: height,
|
||||
builder: (_, onPanUpdate) {
|
||||
builder: (context, onPanUpdate) {
|
||||
return isIOS
|
||||
? ChatPage(chatModel: chatModel)
|
||||
: Scaffold(
|
||||
@ -35,16 +35,16 @@ class DraggableChatWindow extends StatelessWidget {
|
||||
onPanUpdate: onPanUpdate,
|
||||
appBar: isDesktop
|
||||
? _buildDesktopAppBar()
|
||||
: _buildMobileAppBar(),
|
||||
: _buildMobileAppBar(context),
|
||||
),
|
||||
body: ChatPage(chatModel: chatModel),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildMobileAppBar() {
|
||||
Widget _buildMobileAppBar(BuildContext context) {
|
||||
return Container(
|
||||
color: MyTheme.accent50,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
height: 50,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@ -169,17 +169,17 @@ class DraggableMobileActions extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
IconButton(
|
||||
color: MyTheme.white,
|
||||
color: Colors.white,
|
||||
onPressed: onBackPressed,
|
||||
splashRadius: 20,
|
||||
icon: const Icon(Icons.arrow_back)),
|
||||
IconButton(
|
||||
color: MyTheme.white,
|
||||
color: Colors.white,
|
||||
onPressed: onHomePressed,
|
||||
splashRadius: 20,
|
||||
icon: const Icon(Icons.home)),
|
||||
IconButton(
|
||||
color: MyTheme.white,
|
||||
color: Colors.white,
|
||||
onPressed: onRecentPressed,
|
||||
splashRadius: 20,
|
||||
icon: const Icon(Icons.more_horiz)),
|
||||
@ -190,7 +190,7 @@ class DraggableMobileActions extends StatelessWidget {
|
||||
endIndent: 10,
|
||||
),
|
||||
IconButton(
|
||||
color: MyTheme.white,
|
||||
color: Colors.white,
|
||||
onPressed: onHidePressed,
|
||||
splashRadius: 20,
|
||||
icon: const Icon(Icons.keyboard_arrow_down)),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:contextmenu/contextmenu.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
@ -14,7 +15,7 @@ import '../../desktop/widgets/popup_menu.dart';
|
||||
class _PopupMenuTheme {
|
||||
static const Color commonColor = MyTheme.accent;
|
||||
// kMinInteractiveDimension
|
||||
static const double height = 25.0;
|
||||
static const double height = 20.0;
|
||||
static const double dividerHeight = 3.0;
|
||||
}
|
||||
|
||||
@ -73,12 +74,18 @@ class _PeerCardState extends State<_PeerCard>
|
||||
_showPeerMenu(peer.id);
|
||||
},
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.only(left: 12),
|
||||
contentPadding: const EdgeInsets.only(left: 12), //
|
||||
subtitle: Text('${peer.username}@${peer.hostname}'),
|
||||
title: Text(peer.alias.isEmpty ? formatID(peer.id) : peer.alias),
|
||||
title: Row(children: [
|
||||
getOnline(4, peer.online),
|
||||
Text(peer.alias.isEmpty ? formatID(peer.id) : peer.alias)
|
||||
]),
|
||||
leading: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: str2color('${peer.id}${peer.platform}', 0x7f),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
padding: const EdgeInsets.all(6),
|
||||
color: str2color('${peer.id}${peer.platform}', 0x7f),
|
||||
child: getPlatformImage(peer.platform)),
|
||||
trailing: InkWell(
|
||||
child: const Padding(
|
||||
@ -105,7 +112,9 @@ class _PeerCardState extends State<_PeerCard>
|
||||
return MouseRegion(
|
||||
onEnter: (evt) {
|
||||
deco.value = BoxDecoration(
|
||||
border: Border.all(color: MyTheme.button, width: _borderWidth),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
width: _borderWidth),
|
||||
borderRadius: peerCardUiType.value == PeerUiType.grid
|
||||
? BorderRadius.circular(_cardRadis)
|
||||
: null);
|
||||
@ -127,8 +136,10 @@ class _PeerCardState extends State<_PeerCard>
|
||||
|
||||
Widget _buildPeerTile(
|
||||
BuildContext context, Peer peer, Rx<BoxDecoration?> deco) {
|
||||
final greyStyle =
|
||||
TextStyle(fontSize: 12, color: MyTheme.color(context).lighterText);
|
||||
final greyStyle = TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6));
|
||||
final alias = bind.mainGetPeerOptionSync(id: peer.id, key: 'alias');
|
||||
return Obx(
|
||||
() => Container(
|
||||
foregroundDecoration: deco.value,
|
||||
@ -144,66 +155,36 @@ class _PeerCardState extends State<_PeerCard>
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(color: MyTheme.color(context).bg),
|
||||
decoration:
|
||||
BoxDecoration(color: Theme.of(context).backgroundColor),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Row(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 4, 4, 4),
|
||||
child: CircleAvatar(
|
||||
radius: 5,
|
||||
backgroundColor: peer.online
|
||||
? Colors.green
|
||||
: Colors.yellow)),
|
||||
Text(
|
||||
formatID(peer.id),
|
||||
style:
|
||||
const TextStyle(fontWeight: FontWeight.w400),
|
||||
),
|
||||
]),
|
||||
getOnline(8, peer.online),
|
||||
Expanded(
|
||||
child: Text(
|
||||
alias.isEmpty ? formatID(peer.id) : alias,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
]).marginOnly(bottom: 2),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FutureBuilder<String>(
|
||||
future: bind.mainGetPeerOption(
|
||||
id: peer.id, key: 'alias'),
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final name = snapshot.data!.isEmpty
|
||||
? '${peer.username}@${peer.hostname}'
|
||||
: snapshot.data!;
|
||||
return Tooltip(
|
||||
message: name,
|
||||
waitDuration: const Duration(seconds: 1),
|
||||
child: Text(
|
||||
name,
|
||||
style: greyStyle,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// alias has not arrived
|
||||
return Text(
|
||||
'${peer.username}@${peer.hostname}',
|
||||
style: greyStyle,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
'${peer.username}@${peer.hostname}',
|
||||
style: greyStyle,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
).marginOnly(top: 2),
|
||||
),
|
||||
_actionMore(peer),
|
||||
],
|
||||
).paddingSymmetric(horizontal: 4.0),
|
||||
).paddingOnly(left: 10.0, top: 3.0),
|
||||
),
|
||||
)
|
||||
],
|
||||
@ -268,21 +249,19 @@ class _PeerCardState extends State<_PeerCard>
|
||||
),
|
||||
),
|
||||
Container(
|
||||
color: MyTheme.color(context).bg,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 4, 8, 4),
|
||||
child: CircleAvatar(
|
||||
radius: 5,
|
||||
backgroundColor: peer.online
|
||||
? Colors.green
|
||||
: Colors.yellow)),
|
||||
Text(
|
||||
peer.alias.isEmpty ? formatID(peer.id) : peer.alias)
|
||||
]).paddingSymmetric(vertical: 8),
|
||||
Expanded(
|
||||
child: Row(children: [
|
||||
getOnline(8, peer.online),
|
||||
Expanded(
|
||||
child: Text(
|
||||
peer.alias.isEmpty ? formatID(peer.id) : peer.alias,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
]).paddingSymmetric(vertical: 8)),
|
||||
_actionMore(peer),
|
||||
],
|
||||
).paddingSymmetric(horizontal: 12.0),
|
||||
@ -308,13 +287,21 @@ class _PeerCardState extends State<_PeerCard>
|
||||
child: CircleAvatar(
|
||||
radius: 14,
|
||||
backgroundColor: _iconMoreHover.value
|
||||
? MyTheme.color(context).grayBg!
|
||||
: MyTheme.color(context).bg!,
|
||||
? Theme.of(context).scaffoldBackgroundColor
|
||||
: Theme.of(context).backgroundColor,
|
||||
// ? Theme.of(context).scaffoldBackgroundColor!
|
||||
// : Theme.of(context).backgroundColor!,
|
||||
child: Icon(Icons.more_vert,
|
||||
size: 18,
|
||||
color: _iconMoreHover.value
|
||||
? MyTheme.color(context).text
|
||||
: MyTheme.color(context).lightText))));
|
||||
? Theme.of(context).textTheme.titleLarge?.color
|
||||
: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.color
|
||||
?.withOpacity(0.5)))));
|
||||
// ? MyTheme.color(context).text
|
||||
// : MyTheme.color(context).lightText))));
|
||||
|
||||
/// Show the peer menu and handle user's choice.
|
||||
/// User might remove the peer or send a file to the peer.
|
||||
@ -333,8 +320,10 @@ class _PeerCardState extends State<_PeerCard>
|
||||
|
||||
abstract class BasePeerCard extends StatelessWidget {
|
||||
final Peer peer;
|
||||
final EdgeInsets? menuPadding;
|
||||
|
||||
BasePeerCard({required this.peer, Key? key}) : super(key: key);
|
||||
BasePeerCard({required this.peer, this.menuPadding, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -379,6 +368,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
isRDP: isRDP,
|
||||
);
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
@ -428,17 +418,25 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(Icons.edit),
|
||||
onPressed: () => _rdpDialog(id),
|
||||
),
|
||||
child: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.edit),
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
_rdpDialog(id);
|
||||
},
|
||||
)),
|
||||
))
|
||||
],
|
||||
)),
|
||||
proc: () {
|
||||
connect(context, id, isRDP: true);
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
@ -453,6 +451,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
proc: () {
|
||||
bind.mainWol(id: id);
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
@ -461,6 +460,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
Future<MenuEntryBase<String>> _forceAlwaysRelayAction(String id) async {
|
||||
const option = 'force-always-relay';
|
||||
return MenuEntrySwitch<String>(
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Always connect via relay'),
|
||||
getter: () async {
|
||||
return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty;
|
||||
@ -475,6 +475,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
}
|
||||
await bind.mainSetPeerOption(id: id, key: option, value: value);
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
@ -489,13 +490,15 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
proc: () {
|
||||
_rename(id, isAddressBook);
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
MenuEntryBase<String> _removeAction(
|
||||
String id, Future<void> Function() reloadFunc) {
|
||||
String id, Future<void> Function() reloadFunc,
|
||||
{bool isLan = false}) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Remove'),
|
||||
@ -503,12 +506,16 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
),
|
||||
proc: () {
|
||||
() async {
|
||||
await bind.mainRemovePeer(id: id);
|
||||
if (isLan) {
|
||||
// TODO
|
||||
} else {
|
||||
await bind.mainRemovePeer(id: id);
|
||||
}
|
||||
removePreference(id);
|
||||
await reloadFunc();
|
||||
// Get.forceAppUpdate(); // TODO use inner model / state
|
||||
}();
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
@ -523,6 +530,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
proc: () {
|
||||
bind.mainForgetPassword(id: id);
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
@ -543,6 +551,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
}
|
||||
}();
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
@ -561,10 +570,10 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
if (favs.remove(id)) {
|
||||
await bind.mainStoreFav(favs: favs);
|
||||
await reloadFunc();
|
||||
// Get.forceAppUpdate(); // TODO use inner model / state
|
||||
}
|
||||
}();
|
||||
},
|
||||
padding: menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
@ -606,8 +615,6 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Form(
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
@ -634,7 +641,8 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
class RecentPeerCard extends BasePeerCard {
|
||||
RecentPeerCard({required Peer peer, Key? key}) : super(peer: peer, key: key);
|
||||
RecentPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
|
||||
: super(peer: peer, menuPadding: menuPadding, key: key);
|
||||
|
||||
@override
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
@ -642,15 +650,13 @@ class RecentPeerCard extends BasePeerCard {
|
||||
final List<MenuEntryBase<String>> menuItems = [
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
_tcpTunnelingAction(context, peer.id),
|
||||
];
|
||||
MenuEntryBase<String>? rdpAction;
|
||||
if (peer.platform == 'Windows') {
|
||||
rdpAction = _rdpAction(context, peer.id);
|
||||
if (isDesktop) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (rdpAction != null) {
|
||||
menuItems.add(rdpAction);
|
||||
if (peer.platform == 'Windows') {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
menuItems.add(_wolAction(peer.id));
|
||||
menuItems.add(MenuEntryDivider());
|
||||
@ -658,15 +664,17 @@ class RecentPeerCard extends BasePeerCard {
|
||||
menuItems.add(_removeAction(peer.id, () async {
|
||||
await bind.mainLoadRecentPeers();
|
||||
}));
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
}
|
||||
menuItems.add(_addFavAction(peer.id));
|
||||
return menuItems;
|
||||
}
|
||||
}
|
||||
|
||||
class FavoritePeerCard extends BasePeerCard {
|
||||
FavoritePeerCard({required Peer peer, Key? key})
|
||||
: super(peer: peer, key: key);
|
||||
FavoritePeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
|
||||
: super(peer: peer, menuPadding: menuPadding, key: key);
|
||||
|
||||
@override
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
@ -674,15 +682,13 @@ class FavoritePeerCard extends BasePeerCard {
|
||||
final List<MenuEntryBase<String>> menuItems = [
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
_tcpTunnelingAction(context, peer.id),
|
||||
];
|
||||
MenuEntryBase<String>? rdpAction;
|
||||
if (peer.platform == 'Windows') {
|
||||
rdpAction = _rdpAction(context, peer.id);
|
||||
if (isDesktop) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (rdpAction != null) {
|
||||
menuItems.add(rdpAction);
|
||||
if (peer.platform == 'Windows') {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
menuItems.add(_wolAction(peer.id));
|
||||
menuItems.add(MenuEntryDivider());
|
||||
@ -690,7 +696,9 @@ class FavoritePeerCard extends BasePeerCard {
|
||||
menuItems.add(_removeAction(peer.id, () async {
|
||||
await bind.mainLoadFavPeers();
|
||||
}));
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
}
|
||||
menuItems.add(_rmFavAction(peer.id, () async {
|
||||
await bind.mainLoadFavPeers();
|
||||
}));
|
||||
@ -699,8 +707,8 @@ class FavoritePeerCard extends BasePeerCard {
|
||||
}
|
||||
|
||||
class DiscoveredPeerCard extends BasePeerCard {
|
||||
DiscoveredPeerCard({required Peer peer, Key? key})
|
||||
: super(peer: peer, key: key);
|
||||
DiscoveredPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
|
||||
: super(peer: peer, menuPadding: menuPadding, key: key);
|
||||
|
||||
@override
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
@ -708,30 +716,24 @@ class DiscoveredPeerCard extends BasePeerCard {
|
||||
final List<MenuEntryBase<String>> menuItems = [
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
_tcpTunnelingAction(context, peer.id),
|
||||
];
|
||||
MenuEntryBase<String>? rdpAction;
|
||||
if (peer.platform == 'Windows') {
|
||||
rdpAction = _rdpAction(context, peer.id);
|
||||
if (isDesktop) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (rdpAction != null) {
|
||||
menuItems.add(rdpAction);
|
||||
if (peer.platform == 'Windows') {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
menuItems.add(_wolAction(peer.id));
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_renameAction(peer.id, false));
|
||||
menuItems.add(_removeAction(peer.id, () async {
|
||||
await bind.mainLoadLanPeers();
|
||||
}));
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
menuItems.add(_removeAction(peer.id, () async {}));
|
||||
return menuItems;
|
||||
}
|
||||
}
|
||||
|
||||
class AddressBookPeerCard extends BasePeerCard {
|
||||
AddressBookPeerCard({required Peer peer, Key? key})
|
||||
: super(peer: peer, key: key);
|
||||
AddressBookPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
|
||||
: super(peer: peer, menuPadding: menuPadding, key: key);
|
||||
|
||||
@override
|
||||
Future<List<MenuEntryBase<String>>> _buildMenuItems(
|
||||
@ -739,22 +741,21 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
final List<MenuEntryBase<String>> menuItems = [
|
||||
_connectAction(context, peer),
|
||||
_transferFileAction(context, peer.id),
|
||||
_tcpTunnelingAction(context, peer.id),
|
||||
];
|
||||
MenuEntryBase<String>? rdpAction;
|
||||
if (peer.platform == 'Windows') {
|
||||
rdpAction = _rdpAction(context, peer.id);
|
||||
if (isDesktop) {
|
||||
menuItems.add(_tcpTunnelingAction(context, peer.id));
|
||||
}
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
if (rdpAction != null) {
|
||||
menuItems.add(rdpAction);
|
||||
if (peer.platform == 'Windows') {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
menuItems.add(_wolAction(peer.id));
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(_renameAction(peer.id, false));
|
||||
menuItems.add(_removeAction(peer.id, () async {}));
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
menuItems.add(_addFavAction(peer.id));
|
||||
if (await bind.mainPeerHasPassword(id: peer.id)) {
|
||||
menuItems.add(_unrememberPasswordAction(peer.id));
|
||||
}
|
||||
menuItems.add(_editTagAction(peer.id));
|
||||
return menuItems;
|
||||
}
|
||||
@ -762,7 +763,8 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
@protected
|
||||
@override
|
||||
MenuEntryBase<String> _removeAction(
|
||||
String id, Future<void> Function() reloadFunc) {
|
||||
String id, Future<void> Function() reloadFunc,
|
||||
{bool isLan = false}) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Remove'),
|
||||
@ -774,6 +776,7 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
await gFFI.abModel.updateAb();
|
||||
}();
|
||||
},
|
||||
padding: super.menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
@ -788,6 +791,7 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
proc: () {
|
||||
_abEditTag(id);
|
||||
},
|
||||
padding: super.menuPadding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}
|
||||
@ -869,7 +873,7 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
child: Text(
|
||||
tagName,
|
||||
style: TextStyle(
|
||||
color: rxTags.contains(tagName) ? MyTheme.white : null),
|
||||
color: rxTags.contains(tagName) ? Colors.white : null),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -995,3 +999,13 @@ void _rdpDialog(String id) async {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget getOnline(double rightPadding, bool online) {
|
||||
return Tooltip(
|
||||
message: translate(online ? 'Online' : 'Offline'),
|
||||
waitDuration: const Duration(seconds: 1),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.fromLTRB(0, 4, rightPadding, 4),
|
||||
child: CircleAvatar(
|
||||
radius: 3, backgroundColor: online ? Colors.green : kColorWarn)));
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peer_widget.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peercard_widget.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peers_view.dart';
|
||||
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
@ -42,9 +42,6 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
|
||||
// hard code for now
|
||||
Future<void> _handleTabSelection(int index) async {
|
||||
// reset search text
|
||||
peerSearchText.value = "";
|
||||
peerSearchTextController.clear();
|
||||
_tabIndex.value = index;
|
||||
await bind.mainSetLocalOption(
|
||||
key: 'peer-tab-index', value: index.toString());
|
||||
@ -101,6 +98,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
}
|
||||
|
||||
Widget _createTabBar(BuildContext context) {
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
return ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
@ -111,9 +109,9 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: _tabIndex.value == t.key
|
||||
? MyTheme.color(context).bg
|
||||
? Theme.of(context).backgroundColor
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
borderRadius: BorderRadius.circular(isDesktop ? 2 : 6),
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
@ -123,9 +121,9 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: _tabIndex.value == t.key
|
||||
? MyTheme.color(context).text
|
||||
: MyTheme.color(context).lightText),
|
||||
color:
|
||||
_tabIndex.value == t.key ? textColor : textColor
|
||||
?..withOpacity(0.5)),
|
||||
),
|
||||
)),
|
||||
onTap: () async => await _handleTabSelection(t.key),
|
||||
@ -147,7 +145,8 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
}
|
||||
|
||||
Widget _createPeerViewTypeSwitch(BuildContext context) {
|
||||
final activeDeco = BoxDecoration(color: MyTheme.color(context).bg);
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
final activeDeco = BoxDecoration(color: Theme.of(context).backgroundColor);
|
||||
return Row(
|
||||
children: [PeerUiType.grid, PeerUiType.list]
|
||||
.map((type) => Obx(
|
||||
@ -166,9 +165,9 @@ class _PeerTabPageState extends State<PeerTabPage>
|
||||
? Icons.grid_view_rounded
|
||||
: Icons.list,
|
||||
size: 18,
|
||||
color: peerCardUiType.value == type
|
||||
? MyTheme.color(context).text
|
||||
: MyTheme.color(context).lightText,
|
||||
color:
|
||||
peerCardUiType.value == type ? textColor : textColor
|
||||
?..withOpacity(0.5),
|
||||
)),
|
||||
),
|
||||
))
|
||||
@ -199,9 +198,9 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
|
||||
drawer = true;
|
||||
});
|
||||
},
|
||||
icon: const Icon(
|
||||
icon: Icon(
|
||||
Icons.search_rounded,
|
||||
color: MyTheme.dark,
|
||||
color: Theme.of(context).hintColor,
|
||||
));
|
||||
}
|
||||
|
||||
@ -212,7 +211,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
|
||||
return Container(
|
||||
width: 120,
|
||||
decoration: BoxDecoration(
|
||||
color: MyTheme.color(context).bg,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Obx(() => Row(
|
||||
@ -222,7 +221,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
|
||||
children: [
|
||||
Icon(
|
||||
Icons.search_rounded,
|
||||
color: MyTheme.color(context).placeholder,
|
||||
color: Theme.of(context).hintColor,
|
||||
).marginSymmetric(horizontal: 4),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
@ -234,7 +233,11 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
|
||||
focusNode: focusNode,
|
||||
textAlign: TextAlign.start,
|
||||
maxLines: 1,
|
||||
cursorColor: MyTheme.color(context).lightText,
|
||||
cursorColor: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.color
|
||||
?.withOpacity(0.5),
|
||||
cursorHeight: 18,
|
||||
cursorWidth: 1,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
@ -244,8 +247,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
|
||||
hintText:
|
||||
focused.value ? null : translate("Search ID"),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
color: MyTheme.color(context).placeholder),
|
||||
fontSize: 14, color: Theme.of(context).hintColor),
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
@ -262,9 +264,9 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
|
||||
drawer = false;
|
||||
});
|
||||
},
|
||||
icon: const Icon(
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: MyTheme.dark,
|
||||
color: Theme.of(context).hintColor,
|
||||
)),
|
||||
],
|
||||
),
|
||||
|
@ -11,34 +11,34 @@ import 'package:window_manager/window_manager.dart';
|
||||
import '../../common.dart';
|
||||
import '../../models/peer_model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import 'peercard_widget.dart';
|
||||
import 'peer_card.dart';
|
||||
|
||||
typedef OffstageFunc = bool Function(Peer peer);
|
||||
typedef PeerCardWidgetFunc = Widget Function(Peer peer);
|
||||
typedef PeerCardBuilder = BasePeerCard Function(Peer peer);
|
||||
|
||||
/// for peer search text, global obs value
|
||||
final peerSearchText = "".obs;
|
||||
final peerSearchTextController =
|
||||
TextEditingController(text: peerSearchText.value);
|
||||
|
||||
class _PeerWidget extends StatefulWidget {
|
||||
class _PeersView extends StatefulWidget {
|
||||
final Peers peers;
|
||||
final OffstageFunc offstageFunc;
|
||||
final PeerCardWidgetFunc peerCardWidgetFunc;
|
||||
final PeerCardBuilder peerCardBuilder;
|
||||
|
||||
const _PeerWidget(
|
||||
const _PeersView(
|
||||
{required this.peers,
|
||||
required this.offstageFunc,
|
||||
required this.peerCardWidgetFunc,
|
||||
required this.peerCardBuilder,
|
||||
Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_PeerWidgetState createState() => _PeerWidgetState();
|
||||
_PeersViewState createState() => _PeersViewState();
|
||||
}
|
||||
|
||||
/// State for the peer widget.
|
||||
class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
|
||||
class _PeersViewState extends State<_PeersView> with WindowListener {
|
||||
static const int _maxQueryCount = 3;
|
||||
final space = isDesktop ? 12.0 : 8.0;
|
||||
final _curPeers = <String>{};
|
||||
@ -60,7 +60,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
|
||||
return width;
|
||||
}();
|
||||
|
||||
_PeerWidgetState() {
|
||||
_PeersViewState() {
|
||||
_startCheckOnlines();
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
|
||||
}
|
||||
_lastChangeTime = DateTime.now();
|
||||
},
|
||||
child: widget.peerCardWidgetFunc(peer),
|
||||
child: widget.peerCardBuilder(peer),
|
||||
);
|
||||
cards.add(Offstage(
|
||||
key: ValueKey("off${peer.id}"),
|
||||
@ -198,40 +198,41 @@ class _PeerWidgetState extends State<_PeerWidget> with WindowListener {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BasePeerWidget extends StatelessWidget {
|
||||
abstract class BasePeersView extends StatelessWidget {
|
||||
final String name;
|
||||
final String loadEvent;
|
||||
final OffstageFunc offstageFunc;
|
||||
final PeerCardWidgetFunc peerCardWidgetFunc;
|
||||
final PeerCardBuilder peerCardBuilder;
|
||||
final List<Peer> initPeers;
|
||||
|
||||
const BasePeerWidget({
|
||||
const BasePeersView({
|
||||
Key? key,
|
||||
required this.name,
|
||||
required this.loadEvent,
|
||||
required this.offstageFunc,
|
||||
required this.peerCardWidgetFunc,
|
||||
required this.peerCardBuilder,
|
||||
required this.initPeers,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _PeerWidget(
|
||||
return _PeersView(
|
||||
peers: Peers(name: name, loadEvent: loadEvent, peers: initPeers),
|
||||
offstageFunc: offstageFunc,
|
||||
peerCardWidgetFunc: peerCardWidgetFunc);
|
||||
peerCardBuilder: peerCardBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
class RecentPeerWidget extends BasePeerWidget {
|
||||
RecentPeerWidget({Key? key})
|
||||
class RecentPeersView extends BasePeersView {
|
||||
RecentPeersView({Key? key, EdgeInsets? menuPadding})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'recent peer',
|
||||
loadEvent: 'load_recent_peers',
|
||||
offstageFunc: (Peer peer) => false,
|
||||
peerCardWidgetFunc: (Peer peer) => RecentPeerCard(
|
||||
peerCardBuilder: (Peer peer) => RecentPeerCard(
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
);
|
||||
@ -244,15 +245,16 @@ class RecentPeerWidget extends BasePeerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class FavoritePeerWidget extends BasePeerWidget {
|
||||
FavoritePeerWidget({Key? key})
|
||||
class FavoritePeersView extends BasePeersView {
|
||||
FavoritePeersView({Key? key, EdgeInsets? menuPadding})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'favorite peer',
|
||||
loadEvent: 'load_fav_peers',
|
||||
offstageFunc: (Peer peer) => false,
|
||||
peerCardWidgetFunc: (Peer peer) => FavoritePeerCard(
|
||||
peerCardBuilder: (Peer peer) => FavoritePeerCard(
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
);
|
||||
@ -265,15 +267,16 @@ class FavoritePeerWidget extends BasePeerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class DiscoveredPeerWidget extends BasePeerWidget {
|
||||
DiscoveredPeerWidget({Key? key})
|
||||
class DiscoveredPeersView extends BasePeersView {
|
||||
DiscoveredPeersView({Key? key, EdgeInsets? menuPadding})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'discovered peer',
|
||||
loadEvent: 'load_lan_peers',
|
||||
offstageFunc: (Peer peer) => false,
|
||||
peerCardWidgetFunc: (Peer peer) => DiscoveredPeerCard(
|
||||
peerCardBuilder: (Peer peer) => DiscoveredPeerCard(
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: [],
|
||||
);
|
||||
@ -286,16 +289,17 @@ class DiscoveredPeerWidget extends BasePeerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class AddressBookPeerWidget extends BasePeerWidget {
|
||||
AddressBookPeerWidget({Key? key})
|
||||
class AddressBookPeersView extends BasePeersView {
|
||||
AddressBookPeersView({Key? key, EdgeInsets? menuPadding})
|
||||
: super(
|
||||
key: key,
|
||||
name: 'address book peer',
|
||||
loadEvent: 'load_address_book_peers',
|
||||
offstageFunc: (Peer peer) =>
|
||||
!_hitTag(gFFI.abModel.selectedTags, peer.tags),
|
||||
peerCardWidgetFunc: (Peer peer) => AddressBookPeerCard(
|
||||
peerCardBuilder: (Peer peer) => AddressBookPeerCard(
|
||||
peer: peer,
|
||||
menuPadding: menuPadding,
|
||||
),
|
||||
initPeers: _loadPeers(),
|
||||
);
|
@ -11,12 +11,16 @@ const String kAppTypeDesktopPortForward = "port forward";
|
||||
const String kTabLabelHomePage = "Home";
|
||||
const String kTabLabelSettingPage = "Settings";
|
||||
|
||||
const Color kColorWarn = Color.fromARGB(255, 245, 133, 59);
|
||||
|
||||
const int kMobileDefaultDisplayWidth = 720;
|
||||
const int kMobileDefaultDisplayHeight = 1280;
|
||||
|
||||
const int kDesktopDefaultDisplayWidth = 1080;
|
||||
const int kDesktopDefaultDisplayHeight = 720;
|
||||
|
||||
const Size kConnectionManagerWindowSize = Size(300, 400);
|
||||
|
||||
/// [kDefaultScrollAmountMultiplier] indicates how many rows can be scrolled after a minimum scroll action of mouse
|
||||
const kDefaultScrollAmountMultiplier = 5.0;
|
||||
const kDefaultScrollDuration = Duration(milliseconds: 50);
|
||||
|
@ -5,14 +5,16 @@ import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/widgets/address_book.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../common/formatter/id_formatter.dart';
|
||||
import '../../common/widgets/peer_tab_page.dart';
|
||||
import '../../common/widgets/peer_widget.dart';
|
||||
import '../../common/widgets/peers_view.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import '../widgets/button.dart';
|
||||
|
||||
/// Connection page for connecting to a remote peer.
|
||||
class ConnectionPage extends StatefulWidget {
|
||||
@ -74,10 +76,18 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
translate('Address Book')
|
||||
],
|
||||
children: [
|
||||
RecentPeerWidget(),
|
||||
FavoritePeerWidget(),
|
||||
DiscoveredPeerWidget(),
|
||||
const AddressBook(),
|
||||
RecentPeersView(
|
||||
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
|
||||
),
|
||||
FavoritePeersView(
|
||||
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
|
||||
),
|
||||
DiscoveredPeersView(
|
||||
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
|
||||
),
|
||||
const AddressBook(
|
||||
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
|
||||
),
|
||||
],
|
||||
)),
|
||||
],
|
||||
@ -100,10 +110,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
/// UI for the remote ID TextField.
|
||||
/// Search for a peer and connect to it if the id exists.
|
||||
Widget _buildRemoteIDTextField(BuildContext context) {
|
||||
RxBool ftHover = false.obs;
|
||||
RxBool ftPressed = false.obs;
|
||||
RxBool connHover = false.obs;
|
||||
RxBool connPressed = false.obs;
|
||||
RxBool inputFocused = false.obs;
|
||||
FocusNode focusNode = FocusNode();
|
||||
focusNode.addListener(() {
|
||||
@ -113,7 +119,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
width: 320 + 20 * 2,
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 22),
|
||||
decoration: BoxDecoration(
|
||||
color: MyTheme.color(context).bg,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(13)),
|
||||
),
|
||||
child: Ink(
|
||||
@ -123,7 +129,10 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
children: [
|
||||
Text(
|
||||
translate('Control Remote Desktop'),
|
||||
style: const TextStyle(fontSize: 19, height: 1),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.merge(TextStyle(height: 1)),
|
||||
),
|
||||
],
|
||||
).marginOnly(bottom: 15),
|
||||
@ -142,13 +151,12 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
height: 1,
|
||||
),
|
||||
maxLines: 1,
|
||||
cursorColor: MyTheme.color(context).text!,
|
||||
cursorColor:
|
||||
Theme.of(context).textTheme.titleLarge?.color,
|
||||
decoration: InputDecoration(
|
||||
hintText: inputFocused.value
|
||||
? null
|
||||
: translate('Enter Remote ID'),
|
||||
hintStyle: TextStyle(
|
||||
color: MyTheme.color(context).placeholder),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.zero,
|
||||
borderSide: BorderSide(
|
||||
@ -180,84 +188,17 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Obx(() => InkWell(
|
||||
onTapDown: (_) => ftPressed.value = true,
|
||||
onTapUp: (_) => ftPressed.value = false,
|
||||
onTapCancel: () => ftPressed.value = false,
|
||||
onHover: (value) => ftHover.value = value,
|
||||
onTap: () {
|
||||
onConnect(isFileTransfer: true);
|
||||
},
|
||||
child: Container(
|
||||
height: 27,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: ftPressed.value
|
||||
? MyTheme.accent
|
||||
: Colors.transparent,
|
||||
border: Border.all(
|
||||
color: ftPressed.value
|
||||
? MyTheme.accent
|
||||
: ftHover.value
|
||||
? MyTheme.hoverBorder
|
||||
: MyTheme.border,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: Text(
|
||||
translate(
|
||||
"Transfer File",
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: ftPressed.value
|
||||
? MyTheme.color(context).bg
|
||||
: MyTheme.color(context).text),
|
||||
).marginSymmetric(horizontal: 12),
|
||||
),
|
||||
)),
|
||||
Button(
|
||||
isOutline: true,
|
||||
onTap: () {
|
||||
onConnect(isFileTransfer: true);
|
||||
},
|
||||
text: "Transfer File",
|
||||
),
|
||||
const SizedBox(
|
||||
width: 17,
|
||||
),
|
||||
Obx(
|
||||
() => InkWell(
|
||||
onTapDown: (_) => connPressed.value = true,
|
||||
onTapUp: (_) => connPressed.value = false,
|
||||
onTapCancel: () => connPressed.value = false,
|
||||
onHover: (value) => connHover.value = value,
|
||||
onTap: onConnect,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: 80.0,
|
||||
),
|
||||
child: Container(
|
||||
height: 27,
|
||||
decoration: BoxDecoration(
|
||||
color: connPressed.value
|
||||
? MyTheme.accent
|
||||
: MyTheme.button,
|
||||
border: Border.all(
|
||||
color: connPressed.value
|
||||
? MyTheme.accent
|
||||
: connHover.value
|
||||
? MyTheme.hoverBorder
|
||||
: MyTheme.button,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
translate(
|
||||
"Connect",
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: MyTheme.color(context).bg),
|
||||
),
|
||||
).marginSymmetric(horizontal: 12),
|
||||
)),
|
||||
),
|
||||
),
|
||||
Button(onTap: onConnect, text: "Connect"),
|
||||
],
|
||||
),
|
||||
)
|
||||
@ -289,7 +230,11 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
width: 8,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: svcStopped.value ? Colors.redAccent : Colors.green,
|
||||
color: svcStopped.value || svcStatusCode.value == 0
|
||||
? kColorWarn
|
||||
: (svcStatusCode.value == 1
|
||||
? Color.fromARGB(255, 50, 190, 166)
|
||||
: Color.fromARGB(255, 224, 79, 95)),
|
||||
),
|
||||
).paddingSymmetric(horizontal: 12.0);
|
||||
if (svcStopped.value) {
|
||||
|
@ -5,6 +5,8 @@ import 'package:flutter/material.dart' hide MenuItem;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/connection_page.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||
@ -12,6 +14,9 @@ import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../widgets/button.dart';
|
||||
|
||||
class DesktopHomePage extends StatefulWidget {
|
||||
const DesktopHomePage({Key? key}) : super(key: key);
|
||||
@ -26,6 +31,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
with TrayListener, WindowListener, AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
var updateUrl = '';
|
||||
|
||||
@override
|
||||
void onWindowClose() async {
|
||||
@ -68,12 +74,13 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
value: gFFI.serverModel,
|
||||
child: Container(
|
||||
width: 200,
|
||||
color: MyTheme.color(context).bg,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: Column(
|
||||
children: [
|
||||
buildTip(context),
|
||||
buildIDBoard(context),
|
||||
buildPasswordBoard(context),
|
||||
buildHelpCards(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -82,7 +89,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
|
||||
buildRightPane(BuildContext context) {
|
||||
return Container(
|
||||
color: MyTheme.color(context).grayBg,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: ConnectionPage(),
|
||||
);
|
||||
}
|
||||
@ -116,7 +123,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
translate("ID"),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: MyTheme.color(context).lightText),
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.color
|
||||
?.withOpacity(0.5)),
|
||||
).marginOnly(top: 5),
|
||||
buildPopupMenu(context)
|
||||
],
|
||||
@ -152,21 +163,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
}
|
||||
|
||||
Widget buildPopupMenu(BuildContext context) {
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
RxBool hover = false.obs;
|
||||
return InkWell(
|
||||
onTap: () async {},
|
||||
onTap: DesktopTabPage.onAddSetting,
|
||||
child: Obx(
|
||||
() => CircleAvatar(
|
||||
radius: 15,
|
||||
backgroundColor: hover.value
|
||||
? MyTheme.color(context).grayBg!
|
||||
: MyTheme.color(context).bg!,
|
||||
? Theme.of(context).scaffoldBackgroundColor
|
||||
: Theme.of(context).backgroundColor,
|
||||
child: Icon(
|
||||
Icons.more_vert_outlined,
|
||||
size: 20,
|
||||
color: hover.value
|
||||
? MyTheme.color(context).text
|
||||
: MyTheme.color(context).lightText,
|
||||
color: hover.value ? textColor : textColor?.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -178,6 +188,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
final model = gFFI.serverModel;
|
||||
RxBool refreshHover = false.obs;
|
||||
RxBool editHover = false.obs;
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
return Container(
|
||||
margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13),
|
||||
child: Row(
|
||||
@ -198,7 +209,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
Text(
|
||||
translate("Password"),
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: MyTheme.color(context).lightText),
|
||||
fontSize: 14, color: textColor?.withOpacity(0.5)),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
@ -228,8 +239,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
() => Icon(
|
||||
Icons.refresh,
|
||||
color: refreshHover.value
|
||||
? MyTheme.color(context).text
|
||||
: Color(0xFFDDDDDD),
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD), // TODO
|
||||
size: 22,
|
||||
).marginOnly(right: 8, bottom: 2),
|
||||
),
|
||||
@ -241,12 +252,12 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
() => Icon(
|
||||
Icons.edit,
|
||||
color: editHover.value
|
||||
? MyTheme.color(context).text
|
||||
: Color(0xFFDDDDDD),
|
||||
? textColor
|
||||
: Color(0xFFDDDDDD), // TODO
|
||||
size: 22,
|
||||
).marginOnly(right: 8, bottom: 2),
|
||||
),
|
||||
onTap: () => {},
|
||||
onTap: () => DesktopSettingPage.switch2page(1),
|
||||
onHover: (value) => editHover.value = value,
|
||||
),
|
||||
],
|
||||
@ -270,7 +281,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
children: [
|
||||
Text(
|
||||
translate("Your Desktop"),
|
||||
style: TextStyle(fontWeight: FontWeight.normal, fontSize: 19),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
// style: TextStyle(
|
||||
// // color: MyTheme.color(context).text,
|
||||
// fontWeight: FontWeight.normal,
|
||||
// fontSize: 19),
|
||||
),
|
||||
SizedBox(
|
||||
height: 10.0,
|
||||
@ -278,16 +293,93 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
Text(
|
||||
translate("desk_tip"),
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: MyTheme.color(context).lighterText,
|
||||
height: 1.25),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildHelpCards() {
|
||||
if (Platform.isWindows) {
|
||||
if (!bind.mainIsInstalled()) {
|
||||
return buildInstallCard(
|
||||
"", "install_tip", "Install", bind.mainGotoInstall);
|
||||
} else if (bind.mainIsInstalledLowerVersion()) {
|
||||
return buildInstallCard("Status", "Your installation is lower version.",
|
||||
"Click to upgrade", bind.mainUpdateMe);
|
||||
}
|
||||
}
|
||||
if (updateUrl.isNotEmpty) {
|
||||
return buildInstallCard(
|
||||
"Status",
|
||||
"There is a newer version of ${bind.mainGetAppNameSync()} ${bind.mainGetNewVersion()} available.",
|
||||
"Click to download", () async {
|
||||
final Uri url = Uri.parse('https://rustdesk.com');
|
||||
await launchUrl(url);
|
||||
});
|
||||
}
|
||||
if (Platform.isMacOS) {}
|
||||
if (bind.mainIsInstalledLowerVersion()) {}
|
||||
return Container();
|
||||
}
|
||||
|
||||
Widget buildInstallCard(String title, String content, String btnText,
|
||||
GestureTapCallback onPressed) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 20),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: [
|
||||
Color.fromARGB(255, 226, 66, 188),
|
||||
Color.fromARGB(255, 244, 114, 124),
|
||||
],
|
||||
)),
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: (title.isNotEmpty
|
||||
? <Widget>[
|
||||
Center(
|
||||
child: Text(
|
||||
translate(title),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15),
|
||||
).marginOnly(bottom: 6)),
|
||||
]
|
||||
: <Widget>[]) +
|
||||
<Widget>[
|
||||
Text(
|
||||
translate(content),
|
||||
style: TextStyle(
|
||||
height: 1.5,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 13),
|
||||
).marginOnly(bottom: 20),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Button(
|
||||
padding: 8,
|
||||
isOutline: true,
|
||||
text: translate(btnText),
|
||||
textColor: Colors.white,
|
||||
borderColor: Colors.white,
|
||||
textSize: 20,
|
||||
radius: 10,
|
||||
onTap: onPressed,
|
||||
)
|
||||
]),
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTrayMenuItemClick(MenuItem menuItem) {
|
||||
debugPrint('click ${menuItem.key}');
|
||||
@ -305,6 +397,10 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Timer(const Duration(seconds: 5), () async {
|
||||
updateUrl = await bind.mainGetSoftwareUpdateUrl();
|
||||
if (updateUrl.isNotEmpty) setState(() {});
|
||||
});
|
||||
trayManager.addListener(this);
|
||||
windowManager.addListener(this);
|
||||
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
|
||||
@ -331,7 +427,7 @@ Future<bool> loginDialog() async {
|
||||
var userNameMsg = "";
|
||||
String pass = "";
|
||||
var passMsg = "";
|
||||
var userContontroller = TextEditingController(text: userName);
|
||||
var userController = TextEditingController(text: userName);
|
||||
var pwdController = TextEditingController(text: pass);
|
||||
|
||||
var isInProgress = false;
|
||||
@ -349,7 +445,7 @@ Future<bool> loginDialog() async {
|
||||
});
|
||||
}
|
||||
|
||||
userName = userContontroller.text;
|
||||
userName = userController.text;
|
||||
pass = pwdController.text;
|
||||
if (userName.isEmpty) {
|
||||
userNameMsg = translate("Username missed");
|
||||
@ -385,6 +481,7 @@ Future<bool> loginDialog() async {
|
||||
close();
|
||||
}
|
||||
|
||||
// 登录dialog
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Login")),
|
||||
content: ConstrainedBox(
|
||||
@ -411,7 +508,7 @@ Future<bool> loginDialog() async {
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: userNameMsg.isNotEmpty ? userNameMsg : null),
|
||||
controller: userContontroller,
|
||||
controller: userController,
|
||||
focusNode: FocusNode()..requestFocus(),
|
||||
),
|
||||
),
|
||||
|
@ -5,7 +5,9 @@ import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
|
||||
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:flutter_hbb/models/server_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
@ -18,7 +20,7 @@ import '../../common/widgets/dialog.dart';
|
||||
|
||||
const double _kTabWidth = 235;
|
||||
const double _kTabHeight = 42;
|
||||
const double _kCardFixedWidth = 560;
|
||||
const double _kCardFixedWidth = 540;
|
||||
const double _kCardLeftMargin = 15;
|
||||
const double _kContentHMargin = 15;
|
||||
const double _kContentHSubMargin = _kContentHMargin + 33;
|
||||
@ -28,6 +30,8 @@ const double _kListViewBottomMargin = 15;
|
||||
const double _kTitleFontSize = 20;
|
||||
const double _kContentFontSize = 15;
|
||||
const Color _accentColor = MyTheme.accent;
|
||||
const String _kSettingPageControllerTag = "settingPageController";
|
||||
const String _kSettingPageIndexTag = "settingPageIndex";
|
||||
|
||||
class _TabInfo {
|
||||
late final String label;
|
||||
@ -37,10 +41,30 @@ class _TabInfo {
|
||||
}
|
||||
|
||||
class DesktopSettingPage extends StatefulWidget {
|
||||
const DesktopSettingPage({Key? key}) : super(key: key);
|
||||
final int initialPage;
|
||||
|
||||
const DesktopSettingPage({Key? key, required this.initialPage})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<DesktopSettingPage> createState() => _DesktopSettingPageState();
|
||||
|
||||
static void switch2page(int page) {
|
||||
if (page >= 5) return;
|
||||
try {
|
||||
if (Get.isRegistered<PageController>(tag: _kSettingPageControllerTag)) {
|
||||
DesktopTabPage.onAddSetting(initialPage: page);
|
||||
PageController controller = Get.find(tag: _kSettingPageControllerTag);
|
||||
RxInt selectedIndex = Get.find(tag: _kSettingPageIndexTag);
|
||||
selectedIndex.value = page;
|
||||
controller.jumpToPage(page);
|
||||
} else {
|
||||
DesktopTabPage.onAddSetting(initialPage: page);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
@ -50,12 +74,12 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
_TabInfo('Security', Icons.enhanced_encryption_outlined,
|
||||
Icons.enhanced_encryption),
|
||||
_TabInfo('Network', Icons.link_outlined, Icons.link),
|
||||
_TabInfo('Acount', Icons.person_outline, Icons.person),
|
||||
_TabInfo('Account', Icons.person_outline, Icons.person),
|
||||
_TabInfo('About', Icons.info_outline, Icons.info)
|
||||
];
|
||||
|
||||
late PageController controller;
|
||||
RxInt selectedIndex = 0.obs;
|
||||
late RxInt selectedIndex;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@ -63,14 +87,24 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
controller = PageController();
|
||||
selectedIndex = (widget.initialPage < 5 ? widget.initialPage : 0).obs;
|
||||
Get.put<RxInt>(selectedIndex, tag: _kSettingPageIndexTag);
|
||||
controller = PageController(initialPage: widget.initialPage);
|
||||
Get.put<PageController>(controller, tag: _kSettingPageControllerTag);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
Get.delete<PageController>(tag: _kSettingPageControllerTag);
|
||||
Get.delete<RxInt>(tag: _kSettingPageIndexTag);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Scaffold(
|
||||
backgroundColor: MyTheme.color(context).bg,
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: Row(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
@ -85,7 +119,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: MyTheme.color(context).grayBg,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: DesktopScrollWrapper(
|
||||
scrollController: controller,
|
||||
child: PageView(
|
||||
@ -94,7 +128,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||
_General(),
|
||||
_Safety(),
|
||||
_Network(),
|
||||
_Acount(),
|
||||
_Account(),
|
||||
_About(),
|
||||
],
|
||||
)),
|
||||
@ -387,7 +421,7 @@ class _Safety extends StatefulWidget {
|
||||
class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
bool locked = true;
|
||||
bool locked = bind.mainIsInstalled();
|
||||
final scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
@ -533,7 +567,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
_OptionCheckBox(context, 'Deny remote access', 'stop-service',
|
||||
checkedIcon: const Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
color: Color.fromARGB(255, 255, 204, 0),
|
||||
color: kColorWarn,
|
||||
),
|
||||
enabled: enabled),
|
||||
Offstage(
|
||||
@ -541,6 +575,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
child: _OptionCheckBox(context, 'Enable RDP', 'enable-rdp',
|
||||
enabled: enabled),
|
||||
),
|
||||
_OptionCheckBox(context, 'Deny LAN Discovery', 'enable-lan-discovery',
|
||||
reverse: true, enabled: enabled),
|
||||
...directIp(context),
|
||||
whitelist(),
|
||||
]);
|
||||
@ -700,14 +736,14 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
}
|
||||
|
||||
class _Acount extends StatefulWidget {
|
||||
const _Acount({Key? key}) : super(key: key);
|
||||
class _Account extends StatefulWidget {
|
||||
const _Account({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_Acount> createState() => _AcountState();
|
||||
State<_Account> createState() => _AccountState();
|
||||
}
|
||||
|
||||
class _AcountState extends State<_Acount> {
|
||||
class _AccountState extends State<_Account> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scrollController = ScrollController();
|
||||
@ -717,12 +753,12 @@ class _AcountState extends State<_Acount> {
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
children: [
|
||||
_Card(title: 'Acount', children: [login()]),
|
||||
_Card(title: 'Account', children: [accountAction()]),
|
||||
],
|
||||
).marginOnly(bottom: _kListViewBottomMargin));
|
||||
}
|
||||
|
||||
Widget login() {
|
||||
Widget accountAction() {
|
||||
return _futureBuilder(future: () async {
|
||||
return await gFFI.userModel.getUserName();
|
||||
}(), hasData: (data) {
|
||||
@ -730,12 +766,14 @@ class _AcountState extends State<_Acount> {
|
||||
return _Button(
|
||||
username.isEmpty ? 'Login' : 'Logout',
|
||||
() => {
|
||||
loginDialog().then((success) {
|
||||
if (success) {
|
||||
// refresh frame
|
||||
setState(() {});
|
||||
}
|
||||
})
|
||||
username.isEmpty
|
||||
? loginDialog().then((success) {
|
||||
if (success) {
|
||||
// refresh frame
|
||||
setState(() {});
|
||||
}
|
||||
})
|
||||
: gFFI.userModel.logOut()
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -859,7 +897,9 @@ Widget _Card({required String title, required List<Widget> children}) {
|
||||
}
|
||||
|
||||
Color? _disabledTextColor(BuildContext context, bool enabled) {
|
||||
return enabled ? null : MyTheme.color(context).lighterText;
|
||||
return enabled
|
||||
? null
|
||||
: Theme.of(context).textTheme.titleLarge?.color?.withOpacity(0.6);
|
||||
}
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
@ -1339,91 +1379,6 @@ void changeServer() async {
|
||||
});
|
||||
}
|
||||
|
||||
void changeWhiteList({Function()? callback}) async {
|
||||
Map<String, dynamic> oldOptions = jsonDecode(await bind.mainGetOptions());
|
||||
var newWhiteList = ((oldOptions['whitelist'] ?? "") as String).split(',');
|
||||
var newWhiteListField = newWhiteList.join('\n');
|
||||
var controller = TextEditingController(text: newWhiteListField);
|
||||
var msg = "";
|
||||
var isInProgress = false;
|
||||
gFFI.dialogManager.show((setState, close) {
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("IP Whitelisting")),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(translate("whitelist_sep")),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: msg.isEmpty ? null : translate(msg),
|
||||
),
|
||||
controller: controller,
|
||||
focusNode: FocusNode()..requestFocus()),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4.0,
|
||||
),
|
||||
Offstage(
|
||||
offstage: !isInProgress, child: const LinearProgressIndicator())
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: close, child: Text(translate("Cancel"))),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await bind.mainSetOption(key: 'whitelist', value: '');
|
||||
callback?.call();
|
||||
close();
|
||||
},
|
||||
child: Text(translate("Clear"))),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
msg = "";
|
||||
isInProgress = true;
|
||||
});
|
||||
newWhiteListField = controller.text.trim();
|
||||
var newWhiteList = "";
|
||||
if (newWhiteListField.isEmpty) {
|
||||
// pass
|
||||
} else {
|
||||
final ips =
|
||||
newWhiteListField.trim().split(RegExp(r"[\s,;\n]+"));
|
||||
// test ip
|
||||
final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$");
|
||||
for (final ip in ips) {
|
||||
if (!ipMatch.hasMatch(ip)) {
|
||||
msg = "${translate("Invalid IP")} $ip";
|
||||
setState(() {
|
||||
isInProgress = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
newWhiteList = ips.join(',');
|
||||
}
|
||||
oldOptions['whitelist'] = newWhiteList;
|
||||
await bind.mainSetOptions(json: jsonEncode(oldOptions));
|
||||
callback?.call();
|
||||
close();
|
||||
},
|
||||
child: Text(translate("OK"))),
|
||||
],
|
||||
onCancel: close,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void changeSocks5Proxy() async {
|
||||
var socks = await bind.mainGetSocks();
|
||||
|
||||
|
@ -14,6 +14,23 @@ class DesktopTabPage extends StatefulWidget {
|
||||
|
||||
@override
|
||||
State<DesktopTabPage> createState() => _DesktopTabPageState();
|
||||
|
||||
static void onAddSetting({int initialPage = 0}) {
|
||||
try {
|
||||
DesktopTabController tabController = Get.find();
|
||||
tabController.add(TabInfo(
|
||||
key: kTabLabelSettingPage,
|
||||
label: kTabLabelSettingPage,
|
||||
selectedIcon: Icons.build_sharp,
|
||||
unselectedIcon: Icons.build_outlined,
|
||||
page: DesktopSettingPage(
|
||||
key: const ValueKey(kTabLabelSettingPage),
|
||||
initialPage: initialPage,
|
||||
)));
|
||||
} catch (e) {
|
||||
debugPrint('$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||
@ -22,6 +39,7 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Get.put<DesktopTabController>(tabController);
|
||||
tabController.add(TabInfo(
|
||||
key: kTabLabelHomePage,
|
||||
label: kTabLabelHomePage,
|
||||
@ -33,6 +51,12 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||
)));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
Get.delete<DesktopTabController>();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
RxBool fullscreen = false.obs;
|
||||
@ -42,13 +66,13 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||
OverlayEntry(builder: (context) {
|
||||
gFFI.dialogManager.setOverlayState(Overlay.of(context));
|
||||
return Scaffold(
|
||||
backgroundColor: MyTheme.color(context).bg,
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: DesktopTab(
|
||||
controller: tabController,
|
||||
tail: ActionIcon(
|
||||
message: 'Settings',
|
||||
icon: IconFont.menu,
|
||||
onTap: onAddSetting,
|
||||
onTap: DesktopTabPage.onAddSetting,
|
||||
isClose: false,
|
||||
),
|
||||
));
|
||||
@ -62,13 +86,4 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||
fullscreen.value ? kFullScreenEdgeSize : kWindowEdgeSize,
|
||||
child: tabWidget));
|
||||
}
|
||||
|
||||
void onAddSetting() {
|
||||
tabController.add(TabInfo(
|
||||
key: kTabLabelSettingPage,
|
||||
label: kTabLabelSettingPage,
|
||||
selectedIcon: Icons.build_sharp,
|
||||
unselectedIcon: Icons.build_outlined,
|
||||
page: DesktopSettingPage(key: const ValueKey(kTabLabelSettingPage))));
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
return false;
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: MyTheme.color(context).bg,
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: Row(
|
||||
children: [
|
||||
Flexible(flex: 3, child: body(isLocal: true)),
|
||||
|
@ -72,7 +72,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: MyTheme.color(context).border!)),
|
||||
child: Scaffold(
|
||||
backgroundColor: MyTheme.color(context).bg,
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: DesktopTab(
|
||||
controller: tabController,
|
||||
onWindowCloseButton: handleWindowCloseButton,
|
||||
|
@ -70,7 +70,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Scaffold(
|
||||
backgroundColor: MyTheme.color(context).grayBg,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
body: FutureBuilder(future: () async {
|
||||
if (!widget.isRDP) {
|
||||
refreshTunnelConfig();
|
||||
@ -80,7 +80,8 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 20, color: MyTheme.color(context).grayBg!)),
|
||||
width: 20,
|
||||
color: Theme.of(context).scaffoldBackgroundColor)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
@ -88,7 +89,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
Flexible(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: MyTheme.color(context).bg,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
border: Border.all(width: 1, color: MyTheme.border)),
|
||||
child:
|
||||
widget.isRDP ? buildRdp(context) : buildTunnel(context),
|
||||
@ -131,7 +132,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
|
||||
return Theme(
|
||||
data: Theme.of(context)
|
||||
.copyWith(backgroundColor: MyTheme.color(context).bg),
|
||||
.copyWith(backgroundColor: Theme.of(context).backgroundColor),
|
||||
child: Obx(() => ListView.builder(
|
||||
controller: ScrollController(),
|
||||
itemCount: pfs.length + 2,
|
||||
@ -139,7 +140,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
if (index == 0) {
|
||||
return Container(
|
||||
height: 25,
|
||||
color: MyTheme.color(context).grayBg,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Row(children: [
|
||||
text('Local Port'),
|
||||
const SizedBox(width: _kColumn1Width),
|
||||
@ -166,7 +167,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
|
||||
return Container(
|
||||
height: _kRowHeight,
|
||||
decoration: BoxDecoration(color: MyTheme.color(context).bg),
|
||||
decoration: BoxDecoration(color: Theme.of(context).backgroundColor),
|
||||
child: Row(children: [
|
||||
buildTunnelInputCell(context,
|
||||
controller: localPortController,
|
||||
@ -216,11 +217,12 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
{required TextEditingController controller,
|
||||
List<TextInputFormatter>? inputFormatters,
|
||||
String? hint}) {
|
||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||
return Expanded(
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
inputFormatters: inputFormatters,
|
||||
cursorColor: MyTheme.color(context).text,
|
||||
cursorColor: textColor,
|
||||
cursorHeight: 20,
|
||||
cursorWidth: 1,
|
||||
decoration: InputDecoration(
|
||||
@ -228,12 +230,12 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
borderSide: BorderSide(color: MyTheme.color(context).border!)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: MyTheme.color(context).border!)),
|
||||
fillColor: MyTheme.color(context).bg,
|
||||
fillColor: Theme.of(context).backgroundColor,
|
||||
contentPadding: const EdgeInsets.all(10),
|
||||
hintText: hint,
|
||||
hintStyle: TextStyle(
|
||||
color: MyTheme.color(context).placeholder, fontSize: 16)),
|
||||
style: TextStyle(color: MyTheme.color(context).text, fontSize: 16),
|
||||
hintStyle:
|
||||
TextStyle(color: Theme.of(context).hintColor, fontSize: 16)),
|
||||
style: TextStyle(color: textColor, fontSize: 16),
|
||||
).marginAll(10),
|
||||
);
|
||||
}
|
||||
@ -250,7 +252,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
? MyTheme.currentThemeMode() == ThemeMode.dark
|
||||
? const Color(0xFF202020)
|
||||
: const Color(0xFFF4F5F6)
|
||||
: MyTheme.color(context).bg),
|
||||
: Theme.of(context).backgroundColor),
|
||||
child: Row(children: [
|
||||
text(pf.localPort.toString()),
|
||||
const SizedBox(width: _kColumn1Width),
|
||||
@ -292,7 +294,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
).marginOnly(left: _kTextLeftMargin));
|
||||
return Theme(
|
||||
data: Theme.of(context)
|
||||
.copyWith(backgroundColor: MyTheme.color(context).bg),
|
||||
.copyWith(backgroundColor: Theme.of(context).backgroundColor),
|
||||
child: ListView.builder(
|
||||
controller: ScrollController(),
|
||||
itemCount: 2,
|
||||
@ -300,7 +302,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
if (index == 0) {
|
||||
return Container(
|
||||
height: 25,
|
||||
color: MyTheme.color(context).grayBg,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Row(children: [
|
||||
text1('Local Port'),
|
||||
const SizedBox(width: _kColumn1Width),
|
||||
@ -311,7 +313,8 @@ class _PortForwardPageState extends State<PortForwardPage>
|
||||
} else {
|
||||
return Container(
|
||||
height: _kRowHeight,
|
||||
decoration: BoxDecoration(color: MyTheme.color(context).bg),
|
||||
decoration:
|
||||
BoxDecoration(color: Theme.of(context).backgroundColor),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Align(
|
||||
|
@ -80,7 +80,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: MyTheme.color(context).border!)),
|
||||
child: Scaffold(
|
||||
backgroundColor: MyTheme.color(context).bg,
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: DesktopTab(
|
||||
controller: tabController,
|
||||
onWindowCloseButton: () async {
|
||||
|
@ -11,7 +11,6 @@ import 'package:provider/provider.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
import 'package:flutter_custom_cursor/flutter_custom_cursor.dart';
|
||||
|
||||
import '../../consts.dart';
|
||||
import '../widgets/remote_menubar.dart';
|
||||
import '../../common.dart';
|
||||
import '../../mobile/widgets/dialog.dart';
|
||||
@ -45,7 +44,6 @@ class _RemotePageState extends State<RemotePage>
|
||||
late RxBool _keyboardEnabled;
|
||||
|
||||
final FocusNode _rawKeyFocusNode = FocusNode();
|
||||
var _isPhysicalMouse = false;
|
||||
var _imageFocused = false;
|
||||
|
||||
Function(bool)? _onEnterOrLeaveImage4Menubar;
|
||||
@ -139,7 +137,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
|
||||
Widget buildBody(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: MyTheme.color(context).bg,
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: Overlay(
|
||||
initialEntries: [
|
||||
OverlayEntry(builder: (context) {
|
||||
@ -445,6 +443,7 @@ class ImagePainter extends CustomPainter {
|
||||
}
|
||||
|
||||
class QualityMonitor extends StatelessWidget {
|
||||
static const textStyle = TextStyle(color: MyTheme.grayBg);
|
||||
final QualityMonitorModel qualityMonitorModel;
|
||||
QualityMonitor(this.qualityMonitorModel);
|
||||
|
||||
@ -464,23 +463,23 @@ class QualityMonitor extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
"Speed: ${qualityMonitorModel.data.speed ?? ''}",
|
||||
style: const TextStyle(color: MyTheme.grayBg),
|
||||
style: textStyle,
|
||||
),
|
||||
Text(
|
||||
"FPS: ${qualityMonitorModel.data.fps ?? ''}",
|
||||
style: const TextStyle(color: MyTheme.grayBg),
|
||||
style: textStyle,
|
||||
),
|
||||
Text(
|
||||
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
|
||||
style: const TextStyle(color: MyTheme.grayBg),
|
||||
style: textStyle,
|
||||
),
|
||||
Text(
|
||||
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
|
||||
style: const TextStyle(color: MyTheme.grayBg),
|
||||
style: textStyle,
|
||||
),
|
||||
Text(
|
||||
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
|
||||
style: const TextStyle(color: MyTheme.grayBg),
|
||||
style: textStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -91,7 +91,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: MyTheme.color(context).border!)),
|
||||
child: Scaffold(
|
||||
backgroundColor: MyTheme.color(context).bg,
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: DesktopTab(
|
||||
controller: tabController,
|
||||
showTabBar: fullscreen.isFalse,
|
||||
|
@ -69,7 +69,7 @@ class _DesktopServerPageState extends State<DesktopServerPage>
|
||||
OverlayEntry(builder: (context) {
|
||||
gFFI.dialogManager.setOverlayState(Overlay.of(context));
|
||||
return Scaffold(
|
||||
backgroundColor: MyTheme.color(context).bg,
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
@ -145,7 +145,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
|
||||
windowManager.startDragging();
|
||||
},
|
||||
child: Container(
|
||||
color: MyTheme.color(context).bg,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -310,14 +310,15 @@ class _CmHeaderState extends State<_CmHeader>
|
||||
],
|
||||
),
|
||||
),
|
||||
Offstage(
|
||||
offstage: client.isFileTransfer,
|
||||
child: IconButton(
|
||||
onPressed: () => checkClickTime(
|
||||
client.id, () => gFFI.chatModel.toggleCMChatPage(client.id)),
|
||||
icon: Icon(Icons.message_outlined),
|
||||
),
|
||||
)
|
||||
Consumer<ServerModel>(
|
||||
builder: (_, model, child) => Offstage(
|
||||
offstage: !client.authorized || client.isFileTransfer,
|
||||
child: IconButton(
|
||||
onPressed: () => checkClickTime(client.id,
|
||||
() => gFFI.chatModel.toggleCMChatPage(client.id)),
|
||||
icon: Icon(Icons.message_outlined),
|
||||
),
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
84
flutter/lib/desktop/widgets/button.dart
Normal 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),
|
||||
)),
|
||||
));
|
||||
}
|
||||
}
|
@ -14,7 +14,8 @@ import 'package:flutter/material.dart';
|
||||
// void setState(VoidCallback fn) { }
|
||||
// enum Menu { itemOne, itemTwo, itemThree, itemFour }
|
||||
|
||||
const Duration _kMenuDuration = Duration(milliseconds: 300);
|
||||
// const Duration _kMenuDuration = Duration(milliseconds: 300);
|
||||
const Duration _kMenuDuration = Duration(milliseconds: 0);
|
||||
const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
|
||||
const double _kMenuHorizontalPadding = 16.0;
|
||||
const double _kMenuDividerHeight = 16.0;
|
||||
@ -22,7 +23,7 @@ const double _kMenuDividerHeight = 16.0;
|
||||
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
|
||||
const double _kMenuMaxWidth = double.infinity;
|
||||
// const double _kMenuVerticalPadding = 8.0;
|
||||
const double _kMenuVerticalPadding = 0.0;
|
||||
const double _kMenuVerticalPadding = 8.0;
|
||||
const double _kMenuWidthStep = 0.0;
|
||||
//const double _kMenuScreenPadding = 8.0;
|
||||
const double _kMenuScreenPadding = 0.0;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'dart:core';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import './material_mod_popup_menu.dart' as mod_menu;
|
||||
@ -78,7 +77,8 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
|
||||
duration: kThemeChangeDuration,
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
constraints: BoxConstraints(minHeight: widget.height),
|
||||
constraints: BoxConstraints(
|
||||
minHeight: widget.height, maxHeight: widget.height),
|
||||
padding:
|
||||
widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: widget.child,
|
||||
@ -156,12 +156,14 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
||||
final RadioCurOptionGetter curOptionGetter;
|
||||
final RadioOptionSetter optionSetter;
|
||||
final RxString _curOption = "".obs;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
MenuEntryRadios({
|
||||
required this.text,
|
||||
required this.optionsGetter,
|
||||
required this.curOptionGetter,
|
||||
required this.optionSetter,
|
||||
this.padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled) {
|
||||
@ -189,30 +191,37 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
||||
height: conf.height,
|
||||
child: TextButton(
|
||||
child: Container(
|
||||
padding: padding,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
constraints: BoxConstraints(minHeight: conf.height),
|
||||
constraints:
|
||||
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
opt.text,
|
||||
style: TextStyle(
|
||||
color: MyTheme.color(context).text,
|
||||
color: Theme.of(context).textTheme.titleLarge?.color,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal),
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: SizedBox(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
child: Obx(() => opt.value == curOption.value
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: conf.commonColor,
|
||||
)
|
||||
: const SizedBox.shrink())),
|
||||
)),
|
||||
alignment: Alignment.centerRight,
|
||||
child: Transform.scale(
|
||||
scale: MenuConfig.iconScale,
|
||||
child: Obx(() => opt.value == curOption.value
|
||||
? IconButton(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
8.0, 0.0, 8.0, 0.0),
|
||||
hoverColor: Colors.transparent,
|
||||
focusColor: Colors.transparent,
|
||||
onPressed: () {},
|
||||
icon: Icon(
|
||||
Icons.check,
|
||||
color: conf.commonColor,
|
||||
))
|
||||
: const SizedBox.shrink()),
|
||||
))),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -239,12 +248,14 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||
final RadioCurOptionGetter curOptionGetter;
|
||||
final RadioOptionSetter optionSetter;
|
||||
final RxString _curOption = "".obs;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
MenuEntrySubRadios({
|
||||
required this.text,
|
||||
required this.optionsGetter,
|
||||
required this.curOptionGetter,
|
||||
required this.optionSetter,
|
||||
this.padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
}) : super(
|
||||
@ -275,28 +286,34 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||
height: conf.height,
|
||||
child: TextButton(
|
||||
child: Container(
|
||||
padding: padding,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
constraints: BoxConstraints(minHeight: conf.height),
|
||||
constraints:
|
||||
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
opt.text,
|
||||
style: TextStyle(
|
||||
color: MyTheme.color(context).text,
|
||||
color: Theme.of(context).textTheme.titleLarge?.color,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal),
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: SizedBox(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
child: Transform.scale(
|
||||
scale: MenuConfig.iconScale,
|
||||
child: Obx(() => opt.value == curOption.value
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: conf.commonColor,
|
||||
)
|
||||
? IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
hoverColor: Colors.transparent,
|
||||
focusColor: Colors.transparent,
|
||||
onPressed: () {},
|
||||
icon: Icon(
|
||||
Icons.check,
|
||||
color: conf.commonColor,
|
||||
))
|
||||
: const SizedBox.shrink())),
|
||||
)),
|
||||
],
|
||||
@ -318,7 +335,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||
return [
|
||||
PopupMenuChildrenItem(
|
||||
enabled: super.enabled,
|
||||
padding: EdgeInsets.zero,
|
||||
padding: padding,
|
||||
height: conf.height,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(),
|
||||
@ -327,7 +344,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: MyTheme.color(context).text,
|
||||
color: Theme.of(context).textTheme.titleLarge?.color,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal),
|
||||
),
|
||||
@ -345,28 +362,37 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||
}
|
||||
}
|
||||
|
||||
enum SwitchType {
|
||||
sswitch,
|
||||
scheckbox,
|
||||
}
|
||||
|
||||
typedef SwitchGetter = Future<bool> Function();
|
||||
typedef SwitchSetter = Future<void> Function(bool);
|
||||
|
||||
abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
final SwitchType switchType;
|
||||
final String text;
|
||||
final EdgeInsets? padding;
|
||||
Rx<TextStyle>? textStyle;
|
||||
|
||||
MenuEntrySwitchBase({
|
||||
required this.switchType,
|
||||
required this.text,
|
||||
required dismissOnClicked,
|
||||
this.textStyle,
|
||||
this.padding,
|
||||
RxBool? enabled,
|
||||
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled);
|
||||
|
||||
RxBool get curOption;
|
||||
Future<void> setOption(bool option);
|
||||
Future<void> setOption(bool? option);
|
||||
|
||||
@override
|
||||
List<mod_menu.PopupMenuEntry<T>> build(
|
||||
BuildContext context, MenuConfig conf) {
|
||||
textStyle ??= const TextStyle(
|
||||
color: Colors.black,
|
||||
textStyle ??= TextStyle(
|
||||
color: Theme.of(context).textTheme.titleLarge?.color,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal)
|
||||
.obs;
|
||||
@ -376,6 +402,7 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
height: conf.height,
|
||||
child: TextButton(
|
||||
child: Container(
|
||||
padding: padding,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
height: conf.height,
|
||||
child: Row(children: [
|
||||
@ -386,16 +413,33 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Obx(() => Switch(
|
||||
value: curOption.value,
|
||||
onChanged: (v) {
|
||||
if (super.dismissOnClicked &&
|
||||
Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
setOption(v);
|
||||
},
|
||||
)),
|
||||
child: Transform.scale(
|
||||
scale: MenuConfig.iconScale,
|
||||
child: Obx(() {
|
||||
if (switchType == SwitchType.sswitch) {
|
||||
return Switch(
|
||||
value: curOption.value,
|
||||
onChanged: (v) {
|
||||
if (super.dismissOnClicked &&
|
||||
Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
setOption(v);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Checkbox(
|
||||
value: curOption.value,
|
||||
onChanged: (v) {
|
||||
if (super.dismissOnClicked &&
|
||||
Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
setOption(v);
|
||||
},
|
||||
);
|
||||
}
|
||||
})),
|
||||
))
|
||||
])),
|
||||
onPressed: () {
|
||||
@ -416,15 +460,19 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
|
||||
final RxBool _curOption = false.obs;
|
||||
|
||||
MenuEntrySwitch({
|
||||
required SwitchType switchType,
|
||||
required String text,
|
||||
required this.getter,
|
||||
required this.setter,
|
||||
Rx<TextStyle>? textStyle,
|
||||
EdgeInsets? padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
}) : super(
|
||||
switchType: switchType,
|
||||
text: text,
|
||||
textStyle: textStyle,
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
enabled: enabled,
|
||||
) {
|
||||
@ -436,11 +484,13 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
|
||||
@override
|
||||
RxBool get curOption => _curOption;
|
||||
@override
|
||||
setOption(bool option) async {
|
||||
await setter(option);
|
||||
final opt = await getter();
|
||||
if (_curOption.value != opt) {
|
||||
_curOption.value = opt;
|
||||
setOption(bool? option) async {
|
||||
if (option != null) {
|
||||
await setter(option);
|
||||
final opt = await getter();
|
||||
if (_curOption.value != opt) {
|
||||
_curOption.value = opt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -453,32 +503,40 @@ class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
|
||||
final SwitchSetter setter;
|
||||
|
||||
MenuEntrySwitch2({
|
||||
required SwitchType switchType,
|
||||
required String text,
|
||||
required this.getter,
|
||||
required this.setter,
|
||||
Rx<TextStyle>? textStyle,
|
||||
EdgeInsets? padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
}) : super(
|
||||
switchType: switchType,
|
||||
text: text,
|
||||
textStyle: textStyle,
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked);
|
||||
|
||||
@override
|
||||
RxBool get curOption => getter();
|
||||
@override
|
||||
setOption(bool option) async {
|
||||
await setter(option);
|
||||
setOption(bool? option) async {
|
||||
if (option != null) {
|
||||
await setter(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
|
||||
final String text;
|
||||
final List<MenuEntryBase<T>> entries;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
MenuEntrySubMenu({
|
||||
required this.text,
|
||||
required this.entries,
|
||||
this.padding,
|
||||
RxBool? enabled,
|
||||
}) : super(enabled: enabled);
|
||||
|
||||
@ -490,7 +548,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
|
||||
PopupMenuChildrenItem(
|
||||
enabled: super.enabled,
|
||||
height: conf.height,
|
||||
padding: EdgeInsets.zero,
|
||||
padding: padding,
|
||||
position: mod_menu.PopupMenuPosition.overSide,
|
||||
itemBuilder: (BuildContext context) => entries
|
||||
.map((entry) => entry.build(context, conf))
|
||||
@ -501,7 +559,9 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
|
||||
Obx(() => Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: super.enabled!.value ? Colors.black : Colors.grey,
|
||||
color: super.enabled!.value
|
||||
? Theme.of(context).textTheme.titleLarge?.color
|
||||
: Colors.grey,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal),
|
||||
)),
|
||||
@ -522,10 +582,12 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
|
||||
class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||
final Widget Function(TextStyle? style) childBuilder;
|
||||
Function() proc;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
MenuEntryButton({
|
||||
required this.childBuilder,
|
||||
required this.proc,
|
||||
this.padding,
|
||||
dismissOnClicked = false,
|
||||
RxBool? enabled,
|
||||
}) : super(
|
||||
@ -534,8 +596,8 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||
);
|
||||
|
||||
Widget _buildChild(BuildContext context, MenuConfig conf) {
|
||||
const enabledStyle = TextStyle(
|
||||
color: Colors.black,
|
||||
final enabledStyle = TextStyle(
|
||||
color: Theme.of(context).textTheme.titleLarge?.color,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal);
|
||||
const disabledStyle = TextStyle(
|
||||
@ -553,8 +615,10 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||
}
|
||||
: null,
|
||||
child: Container(
|
||||
padding: padding,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
constraints: BoxConstraints(minHeight: conf.height),
|
||||
constraints:
|
||||
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
|
||||
child: childBuilder(
|
||||
super.enabled!.value ? enabledStyle : disabledStyle),
|
||||
),
|
||||
|
@ -20,7 +20,7 @@ import './material_mod_popup_menu.dart' as mod_menu;
|
||||
class _MenubarTheme {
|
||||
static const Color commonColor = MyTheme.accent;
|
||||
// kMinInteractiveDimension
|
||||
static const double height = 25.0;
|
||||
static const double height = 20.0;
|
||||
static const double dividerHeight = 12.0;
|
||||
}
|
||||
|
||||
@ -391,31 +391,41 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
List<MenuEntryBase<String>> _getControlMenu(BuildContext context) {
|
||||
final pi = widget.ffi.ffiModel.pi;
|
||||
final perms = widget.ffi.ffiModel.permissions;
|
||||
|
||||
const EdgeInsets padding = EdgeInsets.only(left: 14.0, right: 5.0);
|
||||
final List<MenuEntryBase<String>> displayMenu = [];
|
||||
displayMenu.addAll([
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Row(
|
||||
children: [
|
||||
Text(
|
||||
translate('OS Password'),
|
||||
style: style,
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(Icons.edit),
|
||||
onPressed: () => showSetOSPassword(
|
||||
widget.id, false, widget.ffi.dialogManager),
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
childBuilder: (TextStyle? style) => Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
height: _MenubarTheme.height,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
translate('OS Password'),
|
||||
style: style,
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(Icons.edit),
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
showSetOSPassword(
|
||||
widget.id, false, widget.ffi.dialogManager);
|
||||
})),
|
||||
))
|
||||
],
|
||||
)),
|
||||
proc: () {
|
||||
showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryButton<String>(
|
||||
@ -426,6 +436,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
proc: () {
|
||||
connect(context, widget.id, isFileTransfer: true);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryButton<String>(
|
||||
@ -433,6 +444,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
translate('TCP Tunneling'),
|
||||
style: style,
|
||||
),
|
||||
padding: padding,
|
||||
proc: () {
|
||||
connect(context, widget.id, isTcpTunneling: true);
|
||||
},
|
||||
@ -451,6 +463,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
proc: () {
|
||||
showAuditDialog(widget.id, widget.ffi.dialogManager);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
);
|
||||
@ -467,6 +480,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
proc: () {
|
||||
bind.sessionCtrlAltDel(id: widget.id);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
@ -483,6 +497,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
proc: () {
|
||||
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
@ -496,6 +511,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
proc: () {
|
||||
bind.sessionLockScreen(id: widget.id);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
|
||||
@ -513,6 +529,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
value: '${blockInput.value ? "un" : ""}block-input');
|
||||
blockInput.value = !blockInput.value;
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
@ -527,6 +544,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
proc: () {
|
||||
bind.sessionRefresh(id: widget.id);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
@ -547,6 +565,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
// }
|
||||
// }();
|
||||
// },
|
||||
// padding: padding,
|
||||
// dismissOnClicked: true,
|
||||
// ));
|
||||
// }
|
||||
@ -559,6 +578,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
proc: () {
|
||||
widget.ffi.cursorModel.reset();
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
@ -567,125 +587,155 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
}
|
||||
|
||||
List<MenuEntryBase<String>> _getDisplayMenu(dynamic futureData) {
|
||||
const EdgeInsets padding = EdgeInsets.only(left: 18.0, right: 8.0);
|
||||
final displayMenu = [
|
||||
MenuEntryRadios<String>(
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale original'), value: 'original'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale adaptive'), value: 'adaptive'),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetOption(
|
||||
id: widget.id, arg: 'view-style') ??
|
||||
'adaptive';
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionPeerOption(
|
||||
id: widget.id, name: "view-style", value: newValue);
|
||||
widget.ffi.canvasModel.updateViewStyle();
|
||||
}),
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale original'),
|
||||
value: 'original',
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale adaptive'),
|
||||
value: 'adaptive',
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetOption(
|
||||
id: widget.id, arg: 'view-style') ??
|
||||
'adaptive';
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionPeerOption(
|
||||
id: widget.id, name: "view-style", value: newValue);
|
||||
widget.ffi.canvasModel.updateViewStyle();
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryDivider<String>(),
|
||||
MenuEntryRadios<String>(
|
||||
text: translate('Scroll Style'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('ScrollAuto'), value: 'scrollauto'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scrollbar'), value: 'scrollbar'),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetOption(
|
||||
id: widget.id, arg: 'scroll-style') ??
|
||||
'';
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionPeerOption(
|
||||
id: widget.id, name: "scroll-style", value: newValue);
|
||||
widget.ffi.canvasModel.updateScrollStyle();
|
||||
}),
|
||||
text: translate('Scroll Style'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('ScrollAuto'),
|
||||
value: 'scrollauto',
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scrollbar'),
|
||||
value: 'scrollbar',
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetOption(
|
||||
id: widget.id, arg: 'scroll-style') ??
|
||||
'';
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionPeerOption(
|
||||
id: widget.id, name: "scroll-style", value: newValue);
|
||||
widget.ffi.canvasModel.updateScrollStyle();
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryDivider<String>(),
|
||||
MenuEntryRadios<String>(
|
||||
text: translate('Image Quality'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Good image quality'), value: 'best'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Balanced'), value: 'balanced'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Optimize reaction time'), value: 'low'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Custom'),
|
||||
value: 'custom',
|
||||
dismissOnClicked: true),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
String quality =
|
||||
await bind.sessionGetImageQuality(id: widget.id) ?? 'balanced';
|
||||
if (quality == '') quality = 'balanced';
|
||||
return quality;
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
if (oldValue != newValue) {
|
||||
await bind.sessionSetImageQuality(id: widget.id, value: newValue);
|
||||
}
|
||||
text: translate('Image Quality'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Good image quality'),
|
||||
value: 'best',
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Balanced'),
|
||||
value: 'balanced',
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Optimize reaction time'),
|
||||
value: 'low',
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Custom'),
|
||||
value: 'custom',
|
||||
dismissOnClicked: true),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
String quality =
|
||||
await bind.sessionGetImageQuality(id: widget.id) ?? 'balanced';
|
||||
if (quality == '') quality = 'balanced';
|
||||
return quality;
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
if (oldValue != newValue) {
|
||||
await bind.sessionSetImageQuality(id: widget.id, value: newValue);
|
||||
}
|
||||
|
||||
if (newValue == 'custom') {
|
||||
final btnCancel = msgBoxButton(translate('Close'), () {
|
||||
widget.ffi.dialogManager.dismissAll();
|
||||
});
|
||||
final quality =
|
||||
await bind.sessionGetCustomImageQuality(id: widget.id);
|
||||
double initValue = quality != null && quality.isNotEmpty
|
||||
? quality[0].toDouble()
|
||||
: 50.0;
|
||||
const minValue = 10.0;
|
||||
const maxValue = 100.0;
|
||||
if (initValue < minValue) {
|
||||
initValue = minValue;
|
||||
}
|
||||
if (initValue > maxValue) {
|
||||
initValue = maxValue;
|
||||
}
|
||||
final RxDouble sliderValue = RxDouble(initValue);
|
||||
final rxReplay = rxdart.ReplaySubject<double>();
|
||||
rxReplay
|
||||
.throttleTime(const Duration(milliseconds: 1000),
|
||||
trailing: true, leading: false)
|
||||
.listen((double v) {
|
||||
() async {
|
||||
await bind.sessionSetCustomImageQuality(
|
||||
id: widget.id, value: v.toInt());
|
||||
}();
|
||||
});
|
||||
final slider = Obx(() {
|
||||
return Slider(
|
||||
value: sliderValue.value,
|
||||
min: minValue,
|
||||
max: maxValue,
|
||||
divisions: 90,
|
||||
onChanged: (double value) {
|
||||
sliderValue.value = value;
|
||||
rxReplay.add(value);
|
||||
},
|
||||
);
|
||||
});
|
||||
final content = Row(
|
||||
children: [
|
||||
slider,
|
||||
SizedBox(
|
||||
width: 90,
|
||||
child: Obx(() => Text(
|
||||
'${sliderValue.value.round()}% Bitrate',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)))
|
||||
],
|
||||
);
|
||||
msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
|
||||
content, [btnCancel]);
|
||||
if (newValue == 'custom') {
|
||||
final btnCancel = msgBoxButton(translate('Close'), () {
|
||||
widget.ffi.dialogManager.dismissAll();
|
||||
});
|
||||
final quality =
|
||||
await bind.sessionGetCustomImageQuality(id: widget.id);
|
||||
double initValue = quality != null && quality.isNotEmpty
|
||||
? quality[0].toDouble()
|
||||
: 50.0;
|
||||
const minValue = 10.0;
|
||||
const maxValue = 100.0;
|
||||
if (initValue < minValue) {
|
||||
initValue = minValue;
|
||||
}
|
||||
}),
|
||||
if (initValue > maxValue) {
|
||||
initValue = maxValue;
|
||||
}
|
||||
final RxDouble sliderValue = RxDouble(initValue);
|
||||
final rxReplay = rxdart.ReplaySubject<double>();
|
||||
rxReplay
|
||||
.throttleTime(const Duration(milliseconds: 1000),
|
||||
trailing: true, leading: false)
|
||||
.listen((double v) {
|
||||
() async {
|
||||
await bind.sessionSetCustomImageQuality(
|
||||
id: widget.id, value: v.toInt());
|
||||
}();
|
||||
});
|
||||
final slider = Obx(() {
|
||||
return Slider(
|
||||
value: sliderValue.value,
|
||||
min: minValue,
|
||||
max: maxValue,
|
||||
divisions: 90,
|
||||
onChanged: (double value) {
|
||||
sliderValue.value = value;
|
||||
rxReplay.add(value);
|
||||
},
|
||||
);
|
||||
});
|
||||
final content = Row(
|
||||
children: [
|
||||
slider,
|
||||
SizedBox(
|
||||
width: 90,
|
||||
child: Obx(() => Text(
|
||||
'${sliderValue.value.round()}% Bitrate',
|
||||
style: const TextStyle(fontSize: 15),
|
||||
)))
|
||||
],
|
||||
);
|
||||
msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
|
||||
content, [btnCancel]);
|
||||
}
|
||||
},
|
||||
padding: padding,
|
||||
),
|
||||
MenuEntryDivider<String>(),
|
||||
];
|
||||
|
||||
@ -701,30 +751,49 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
} finally {}
|
||||
if (codecs.length == 2 && (codecs[0] || codecs[1])) {
|
||||
displayMenu.add(MenuEntryRadios<String>(
|
||||
text: translate('Codec Preference'),
|
||||
optionsGetter: () {
|
||||
final list = [
|
||||
MenuEntryRadioOption(text: translate('Auto'), value: 'auto'),
|
||||
MenuEntryRadioOption(text: 'VP9', value: 'vp9'),
|
||||
];
|
||||
if (codecs[0]) {
|
||||
list.add(MenuEntryRadioOption(text: 'H264', value: 'h264'));
|
||||
}
|
||||
if (codecs[1]) {
|
||||
list.add(MenuEntryRadioOption(text: 'H265', value: 'h265'));
|
||||
}
|
||||
return list;
|
||||
},
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetOption(
|
||||
id: widget.id, arg: 'codec-preference') ??
|
||||
'auto';
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionPeerOption(
|
||||
id: widget.id, name: "codec-preference", value: newValue);
|
||||
bind.sessionChangePreferCodec(id: widget.id);
|
||||
}));
|
||||
text: translate('Codec Preference'),
|
||||
optionsGetter: () {
|
||||
final list = [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Auto'),
|
||||
value: 'auto',
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: 'VP9',
|
||||
value: 'vp9',
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
];
|
||||
if (codecs[0]) {
|
||||
list.add(MenuEntryRadioOption(
|
||||
text: 'H264',
|
||||
value: 'h264',
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
if (codecs[1]) {
|
||||
list.add(MenuEntryRadioOption(
|
||||
text: 'H265',
|
||||
value: 'h265',
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
return list;
|
||||
},
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetOption(
|
||||
id: widget.id, arg: 'codec-preference') ??
|
||||
'auto';
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionPeerOption(
|
||||
id: widget.id, name: "codec-preference", value: newValue);
|
||||
bind.sessionChangePreferCodec(id: widget.id);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -732,62 +801,74 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
displayMenu.add(() {
|
||||
final state = ShowRemoteCursorState.find(widget.id);
|
||||
return MenuEntrySwitch2<String>(
|
||||
text: translate('Show remote cursor'),
|
||||
getter: () {
|
||||
return state;
|
||||
},
|
||||
setter: (bool v) async {
|
||||
state.value = v;
|
||||
await bind.sessionToggleOption(
|
||||
id: widget.id, value: 'show-remote-cursor');
|
||||
});
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Show remote cursor'),
|
||||
getter: () {
|
||||
return state;
|
||||
},
|
||||
setter: (bool v) async {
|
||||
state.value = v;
|
||||
await bind.sessionToggleOption(
|
||||
id: widget.id, value: 'show-remote-cursor');
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
);
|
||||
}());
|
||||
|
||||
/// Show quality monitor
|
||||
displayMenu.add(MenuEntrySwitch<String>(
|
||||
text: translate('Show quality monitor'),
|
||||
getter: () async {
|
||||
return bind.sessionGetToggleOptionSync(
|
||||
id: widget.id, arg: 'show-quality-monitor');
|
||||
},
|
||||
setter: (bool v) async {
|
||||
await bind.sessionToggleOption(
|
||||
id: widget.id, value: 'show-quality-monitor');
|
||||
widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
|
||||
}));
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Show quality monitor'),
|
||||
getter: () async {
|
||||
return bind.sessionGetToggleOptionSync(
|
||||
id: widget.id, arg: 'show-quality-monitor');
|
||||
},
|
||||
setter: (bool v) async {
|
||||
await bind.sessionToggleOption(
|
||||
id: widget.id, value: 'show-quality-monitor');
|
||||
widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
|
||||
final perms = widget.ffi.ffiModel.permissions;
|
||||
final pi = widget.ffi.ffiModel.pi;
|
||||
|
||||
if (perms['audio'] != false) {
|
||||
displayMenu.add(_createSwitchMenuEntry('Mute', 'disable-audio'));
|
||||
displayMenu
|
||||
.add(_createSwitchMenuEntry('Mute', 'disable-audio', padding, true));
|
||||
}
|
||||
|
||||
if (Platform.isWindows &&
|
||||
pi.platform == 'Windows' &&
|
||||
perms['file'] != false) {
|
||||
displayMenu.add(_createSwitchMenuEntry(
|
||||
'Allow file copy and paste', 'enable-file-transfer'));
|
||||
'Allow file copy and paste', 'enable-file-transfer', padding, true));
|
||||
}
|
||||
|
||||
if (perms['keyboard'] != false) {
|
||||
if (perms['clipboard'] != false) {
|
||||
displayMenu.add(
|
||||
_createSwitchMenuEntry('Disable clipboard', 'disable-clipboard'));
|
||||
displayMenu.add(_createSwitchMenuEntry(
|
||||
'Disable clipboard', 'disable-clipboard', padding, true));
|
||||
}
|
||||
displayMenu.add(_createSwitchMenuEntry(
|
||||
'Lock after session end', 'lock-after-session-end'));
|
||||
'Lock after session end', 'lock-after-session-end', padding, true));
|
||||
if (pi.platform == 'Windows') {
|
||||
displayMenu.add(MenuEntrySwitch2<String>(
|
||||
dismissOnClicked: true,
|
||||
text: translate('Privacy mode'),
|
||||
getter: () {
|
||||
return PrivacyModeState.find(widget.id);
|
||||
},
|
||||
setter: (bool v) async {
|
||||
await bind.sessionToggleOption(
|
||||
id: widget.id, value: 'privacy-mode');
|
||||
}));
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate('Privacy mode'),
|
||||
getter: () {
|
||||
return PrivacyModeState.find(widget.id);
|
||||
},
|
||||
setter: (bool v) async {
|
||||
await bind.sessionToggleOption(
|
||||
id: widget.id, value: 'privacy-mode');
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
}
|
||||
return displayMenu;
|
||||
@ -796,34 +877,39 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
List<MenuEntryBase<String>> _getKeyboardMenu() {
|
||||
final keyboardMenu = [
|
||||
MenuEntryRadios<String>(
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Legacy mode'), value: 'legacy'),
|
||||
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetKeyboardName(id: widget.id);
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionSetKeyboardMode(
|
||||
id: widget.id, keyboardMode: newValue);
|
||||
widget.ffi.canvasModel.updateViewStyle();
|
||||
})
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () => [
|
||||
MenuEntryRadioOption(text: translate('Legacy mode'), value: 'legacy'),
|
||||
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetKeyboardName(id: widget.id);
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionSetKeyboardMode(
|
||||
id: widget.id, keyboardMode: newValue);
|
||||
widget.ffi.canvasModel.updateViewStyle();
|
||||
},
|
||||
)
|
||||
];
|
||||
|
||||
return keyboardMenu;
|
||||
}
|
||||
|
||||
MenuEntrySwitch<String> _createSwitchMenuEntry(String text, String option) {
|
||||
MenuEntrySwitch<String> _createSwitchMenuEntry(
|
||||
String text, String option, EdgeInsets? padding, bool dismissOnClicked) {
|
||||
return MenuEntrySwitch<String>(
|
||||
text: translate(text),
|
||||
getter: () async {
|
||||
return bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
|
||||
},
|
||||
setter: (bool v) async {
|
||||
await bind.sessionToggleOption(id: widget.id, value: option);
|
||||
});
|
||||
switchType: SwitchType.scheckbox,
|
||||
text: translate(text),
|
||||
getter: () async {
|
||||
return bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
|
||||
},
|
||||
setter: (bool v) async {
|
||||
await bind.sessionToggleOption(id: widget.id, value: option);
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: dismissOnClicked,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -490,6 +490,15 @@ class _ListView extends StatelessWidget {
|
||||
this.tabBuilder,
|
||||
this.labelGetter});
|
||||
|
||||
/// Check whether to show ListView
|
||||
///
|
||||
/// Conditions:
|
||||
/// - hide single item when only has one item (home) on [DesktopTabPage].
|
||||
bool isHideSingleItem() {
|
||||
return state.value.tabs.length == 1 &&
|
||||
controller.tabType == DesktopTabType.main;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() => ListView(
|
||||
@ -497,38 +506,41 @@ class _ListView extends StatelessWidget {
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
children: state.value.tabs.asMap().entries.map((e) {
|
||||
final index = e.key;
|
||||
final tab = e.value;
|
||||
return _Tab(
|
||||
index: index,
|
||||
label: labelGetter == null
|
||||
? Rx<String>(tab.label)
|
||||
: labelGetter!(tab.label),
|
||||
selectedIcon: tab.selectedIcon,
|
||||
unselectedIcon: tab.unselectedIcon,
|
||||
closable: tab.closable,
|
||||
selected: state.value.selected,
|
||||
onClose: () {
|
||||
if (tab.onTabCloseButton != null) {
|
||||
tab.onTabCloseButton!();
|
||||
} else {
|
||||
controller.remove(index);
|
||||
}
|
||||
},
|
||||
onSelected: () => controller.jumpTo(index),
|
||||
tabBuilder: tabBuilder == null
|
||||
? null
|
||||
: (Widget icon, Widget labelWidget, TabThemeConf themeConf) {
|
||||
return tabBuilder!(
|
||||
tab.label,
|
||||
icon,
|
||||
labelWidget,
|
||||
themeConf,
|
||||
);
|
||||
children: isHideSingleItem()
|
||||
? List.empty()
|
||||
: state.value.tabs.asMap().entries.map((e) {
|
||||
final index = e.key;
|
||||
final tab = e.value;
|
||||
return _Tab(
|
||||
index: index,
|
||||
label: labelGetter == null
|
||||
? Rx<String>(tab.label)
|
||||
: labelGetter!(tab.label),
|
||||
selectedIcon: tab.selectedIcon,
|
||||
unselectedIcon: tab.unselectedIcon,
|
||||
closable: tab.closable,
|
||||
selected: state.value.selected,
|
||||
onClose: () {
|
||||
if (tab.onTabCloseButton != null) {
|
||||
tab.onTabCloseButton!();
|
||||
} else {
|
||||
controller.remove(index);
|
||||
}
|
||||
},
|
||||
);
|
||||
}).toList()));
|
||||
onSelected: () => controller.jumpTo(index),
|
||||
tabBuilder: tabBuilder == null
|
||||
? null
|
||||
: (Widget icon, Widget labelWidget,
|
||||
TabThemeConf themeConf) {
|
||||
return tabBuilder!(
|
||||
tab.label,
|
||||
icon,
|
||||
labelWidget,
|
||||
themeConf,
|
||||
);
|
||||
},
|
||||
);
|
||||
}).toList()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,6 +103,7 @@ void runMainApp(bool startService) async {
|
||||
restoreWindowPosition(WindowType.Main);
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
await windowManager.setOpacity(1);
|
||||
});
|
||||
}
|
||||
|
||||
@ -190,8 +191,13 @@ void runPortForwardScreen(Map<String, dynamic> argument) async {
|
||||
void runConnectionManagerScreen() async {
|
||||
// initialize window
|
||||
WindowOptions windowOptions =
|
||||
getHiddenTitleBarWindowOptions(size: const Size(300, 400));
|
||||
await initEnv(kAppTypeMain);
|
||||
getHiddenTitleBarWindowOptions(size: kConnectionManagerWindowSize);
|
||||
// ensure initial window size to be changed
|
||||
await windowManager.setSize(kConnectionManagerWindowSize);
|
||||
await Future.wait([
|
||||
windowManager.setAlignment(Alignment.topRight),
|
||||
initEnv(kAppTypeMain)
|
||||
]);
|
||||
runApp(GetMaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: MyTheme.lightTheme,
|
||||
@ -206,17 +212,17 @@ void runConnectionManagerScreen() async {
|
||||
home: const DesktopServerPage(),
|
||||
builder: _keepScaleBuilder()));
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
await windowManager.setAlignment(Alignment.topRight);
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
await windowManager.setAlignment(Alignment.topRight); // ensure
|
||||
windowManager.show();
|
||||
windowManager.focus();
|
||||
windowManager.setOpacity(1);
|
||||
windowManager.setAlignment(Alignment.topRight); // ensure
|
||||
});
|
||||
}
|
||||
|
||||
WindowOptions getHiddenTitleBarWindowOptions({Size? size}) {
|
||||
return WindowOptions(
|
||||
size: size,
|
||||
center: true,
|
||||
center: false,
|
||||
backgroundColor: Colors.transparent,
|
||||
skipTaskbar: false,
|
||||
titleBarStyle: TitleBarStyle.hidden,
|
||||
|
@ -45,7 +45,7 @@ class ChatPage extends StatelessWidget implements PageShape {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: chatModel,
|
||||
child: Container(
|
||||
color: MyTheme.color(context).grayBg,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Consumer<ChatModel>(builder: (context, chatModel, child) {
|
||||
final currentUser = chatModel.currentUser;
|
||||
return Stack(
|
||||
@ -62,11 +62,18 @@ class ChatPage extends StatelessWidget implements PageShape {
|
||||
inputOptions: InputOptions(
|
||||
sendOnEnter: true,
|
||||
inputDecoration: defaultInputDecoration(
|
||||
fillColor: MyTheme.color(context).bg),
|
||||
hintText: "${translate('Write a message')}...",
|
||||
fillColor: Theme.of(context).backgroundColor),
|
||||
sendButtonBuilder: defaultSendButton(
|
||||
color: MyTheme.color(context).text!),
|
||||
inputTextStyle:
|
||||
TextStyle(color: MyTheme.color(context).text)),
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge!
|
||||
.color!),
|
||||
inputTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.color)),
|
||||
messageOptions: MessageOptions(
|
||||
showOtherUsersAvatar: false,
|
||||
showTime: true,
|
||||
@ -81,7 +88,8 @@ class ChatPage extends StatelessWidget implements PageShape {
|
||||
)),
|
||||
);
|
||||
}),
|
||||
chatModel.currentID == ChatModel.clientModeID
|
||||
desktopType == DesktopType.cm ||
|
||||
chatModel.currentID == ChatModel.clientModeID
|
||||
? SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
||||
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@ -10,12 +9,11 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../common.dart';
|
||||
import '../../common/widgets/address_book.dart';
|
||||
import '../../common/widgets/peer_tab_page.dart';
|
||||
import '../../common/widgets/peer_widget.dart';
|
||||
import '../../common/widgets/peers_view.dart';
|
||||
import '../../consts.dart';
|
||||
import '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import 'home_page.dart';
|
||||
import 'remote_page.dart';
|
||||
import 'scan_page.dart';
|
||||
import 'settings_page.dart';
|
||||
|
||||
@ -84,9 +82,9 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
translate('Address Book')
|
||||
],
|
||||
children: [
|
||||
RecentPeerWidget(),
|
||||
FavoritePeerWidget(),
|
||||
DiscoveredPeerWidget(),
|
||||
RecentPeersView(),
|
||||
FavoritePeersView(),
|
||||
DiscoveredPeersView(),
|
||||
const AddressBook(),
|
||||
],
|
||||
)),
|
||||
@ -97,38 +95,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
/// Connects to the selected peer.
|
||||
void onConnect() {
|
||||
var id = _idController.id;
|
||||
connect(id);
|
||||
}
|
||||
|
||||
/// Connect to a peer with [id].
|
||||
/// If [isFileTransfer], starts a session only for file transfer.
|
||||
void connect(String id, {bool isFileTransfer = false}) async {
|
||||
if (id == '') return;
|
||||
id = id.replaceAll(' ', '');
|
||||
if (isFileTransfer) {
|
||||
if (!await PermissionManager.check("file")) {
|
||||
if (!await PermissionManager.request("file")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => FileManagerPage(id: id),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => RemotePage(id: id),
|
||||
),
|
||||
);
|
||||
}
|
||||
FocusScopeNode currentFocus = FocusScope.of(context);
|
||||
if (!currentFocus.hasPrimaryFocus) {
|
||||
currentFocus.unfocus();
|
||||
}
|
||||
connect(context, id);
|
||||
}
|
||||
|
||||
/// UI for software update.
|
||||
@ -161,8 +128,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 2),
|
||||
child: Ink(
|
||||
decoration: const BoxDecoration(
|
||||
color: MyTheme.white,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.all(Radius.circular(13)),
|
||||
),
|
||||
child: Row(
|
||||
|
@ -58,7 +58,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
return false;
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: MyTheme.grayBg,
|
||||
// backgroundColor: MyTheme.grayBg,
|
||||
appBar: AppBar(
|
||||
leading: Row(children: [
|
||||
IconButton(
|
||||
@ -69,7 +69,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
title: ToggleSwitch(
|
||||
initialLabelIndex: model.isLocal ? 0 : 1,
|
||||
activeBgColor: [MyTheme.idColor],
|
||||
inactiveBgColor: MyTheme.grayBg,
|
||||
// inactiveBgColor: MyTheme.grayBg,
|
||||
inactiveFgColor: Colors.black54,
|
||||
totalSwitches: 2,
|
||||
minWidth: 100,
|
||||
@ -465,6 +465,9 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
||||
);
|
||||
case JobState.none:
|
||||
break;
|
||||
case JobState.paused:
|
||||
// TODO: Handle this case.
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -530,8 +533,7 @@ class BottomSheetBody extends StatelessWidget {
|
||||
children: [
|
||||
Text(title, style: TextStyle(fontSize: 18)),
|
||||
Text(text,
|
||||
style: TextStyle(
|
||||
fontSize: 14, color: MyTheme.grayBg))
|
||||
style: TextStyle(fontSize: 14)) // TODO color
|
||||
],
|
||||
)
|
||||
],
|
||||
@ -548,7 +550,7 @@ class BottomSheetBody extends StatelessWidget {
|
||||
));
|
||||
},
|
||||
onClosing: () {},
|
||||
backgroundColor: MyTheme.grayBg,
|
||||
// backgroundColor: MyTheme.grayBg,
|
||||
enableDrag: false,
|
||||
);
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ class _HomePageState extends State<HomePage> {
|
||||
return false;
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: MyTheme.grayBg,
|
||||
// backgroundColor: MyTheme.grayBg,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text("RustDesk"),
|
||||
@ -73,7 +73,7 @@ class _HomePageState extends State<HomePage> {
|
||||
.toList(),
|
||||
currentIndex: _selectedIndex,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedItemColor: MyTheme.accent,
|
||||
selectedItemColor: MyTheme.accent, //
|
||||
unselectedItemColor: MyTheme.darkGray,
|
||||
onTap: (index) => setState(() {
|
||||
// close chat overlay when go chat page
|
||||
@ -95,7 +95,7 @@ class WebHomePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: MyTheme.grayBg,
|
||||
// backgroundColor: MyTheme.grayBg,
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text("RustDesk" + (isWeb ? " (Beta) " : "")),
|
||||
|
@ -325,17 +325,13 @@ class _RemotePageState extends State<RemotePage> {
|
||||
},
|
||||
onPointerSignal: (e) {
|
||||
if (e is PointerScrollEvent) {
|
||||
var dx = e.scrollDelta.dx;
|
||||
var dy = e.scrollDelta.dy;
|
||||
if (dx > 0)
|
||||
dx = -1;
|
||||
else if (dx < 0) dx = 1;
|
||||
if (dy > 0)
|
||||
var dy = 0;
|
||||
if (e.scrollDelta.dy > 0) {
|
||||
dy = -1;
|
||||
else if (dy < 0) dy = 1;
|
||||
bind.sessionSendMouse(
|
||||
id: widget.id,
|
||||
msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
|
||||
} else if (e.scrollDelta.dy < 0) {
|
||||
dy = 1;
|
||||
}
|
||||
gFFI.scroll(dy);
|
||||
}
|
||||
},
|
||||
child: MouseRegion(
|
||||
@ -752,7 +748,7 @@ class _RemotePageState extends State<RemotePage> {
|
||||
void changeTouchMode() {
|
||||
setState(() => _showEdit = false);
|
||||
showModalBottomSheet(
|
||||
backgroundColor: MyTheme.grayBg,
|
||||
// backgroundColor: MyTheme.grayBg,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
@ -968,7 +964,9 @@ class ImagePainter extends CustomPainter {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO global widget
|
||||
class QualityMonitor extends StatelessWidget {
|
||||
static final textColor = Colors.grey.shade200;
|
||||
@override
|
||||
Widget build(BuildContext context) => ChangeNotifierProvider.value(
|
||||
value: gFFI.qualityMonitorModel,
|
||||
@ -985,23 +983,23 @@ class QualityMonitor extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
"Speed: ${qualityMonitorModel.data.speed ?? ''}",
|
||||
style: TextStyle(color: MyTheme.grayBg),
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
Text(
|
||||
"FPS: ${qualityMonitorModel.data.fps ?? ''}",
|
||||
style: TextStyle(color: MyTheme.grayBg),
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
Text(
|
||||
"Delay: ${qualityMonitorModel.data.delay ?? ''} ms",
|
||||
style: TextStyle(color: MyTheme.grayBg),
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
Text(
|
||||
"Target Bitrate: ${qualityMonitorModel.data.targetBitrate ?? ''}kb",
|
||||
style: TextStyle(color: MyTheme.grayBg),
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
Text(
|
||||
"Codec: ${qualityMonitorModel.data.codecFormat ?? ''}",
|
||||
style: TextStyle(color: MyTheme.grayBg),
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -170,7 +170,7 @@ class ServerInfo extends StatelessWidget {
|
||||
icon: const Icon(Icons.perm_identity),
|
||||
labelText: translate("ID"),
|
||||
labelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.bold, color: MyTheme.accent50),
|
||||
fontWeight: FontWeight.bold, color: MyTheme.accent80),
|
||||
),
|
||||
onSaved: (String? value) {},
|
||||
),
|
||||
@ -185,7 +185,7 @@ class ServerInfo extends StatelessWidget {
|
||||
icon: const Icon(Icons.lock),
|
||||
labelText: translate("Password"),
|
||||
labelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.bold, color: MyTheme.accent50),
|
||||
fontWeight: FontWeight.bold, color: MyTheme.accent80),
|
||||
suffix: isPermanent
|
||||
? null
|
||||
: IconButton(
|
||||
@ -213,7 +213,7 @@ class ServerInfo extends StatelessWidget {
|
||||
fontFamily: 'WorkSans',
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
color: MyTheme.accent80,
|
||||
color: MyTheme.accent,
|
||||
),
|
||||
))
|
||||
],
|
||||
@ -304,7 +304,8 @@ class _PermissionCheckerState extends State<PermissionChecker> {
|
||||
softWrap: true,
|
||||
style: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: MyTheme.accent50)))
|
||||
fontWeight: FontWeight.w500,
|
||||
color: MyTheme.accent80)))
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink())
|
||||
@ -333,8 +334,13 @@ class PermissionRow extends StatelessWidget {
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16.0, color: MyTheme.accent50)))),
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight:
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? FontWeight.bold
|
||||
: null,
|
||||
color: MyTheme.accent80)))),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: FittedBox(
|
||||
@ -398,7 +404,7 @@ class ConnectionManager extends StatelessWidget {
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.chat,
|
||||
color: MyTheme.accent80,
|
||||
color: MyTheme.accent,
|
||||
)))
|
||||
],
|
||||
),
|
||||
@ -406,7 +412,9 @@ class ConnectionManager extends StatelessWidget {
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
translate("android_new_connection_tip"),
|
||||
style: const TextStyle(color: Colors.black54),
|
||||
style: Theme.of(globalKey.currentContext!)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
),
|
||||
client.authorized
|
||||
? ElevatedButton.icon(
|
||||
@ -460,8 +468,8 @@ class PaddingCard extends StatelessWidget {
|
||||
titleIcon != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: Icon(titleIcon,
|
||||
color: MyTheme.accent80, size: 30))
|
||||
child:
|
||||
Icon(titleIcon, color: MyTheme.accent, size: 30))
|
||||
: const SizedBox.shrink(),
|
||||
Text(
|
||||
title!,
|
||||
@ -469,7 +477,7 @@ class PaddingCard extends StatelessWidget {
|
||||
fontFamily: 'WorkSans',
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20,
|
||||
color: MyTheme.accent80,
|
||||
color: MyTheme.accent,
|
||||
),
|
||||
)
|
||||
],
|
||||
|
@ -2,12 +2,14 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:settings_ui/settings_ui.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../common/widgets/dialog.dart';
|
||||
import '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import '../widgets/dialog.dart';
|
||||
@ -25,14 +27,15 @@ class SettingsPage extends StatefulWidget implements PageShape {
|
||||
final appBarActions = [ScanButton()];
|
||||
|
||||
@override
|
||||
_SettingsState createState() => _SettingsState();
|
||||
State<SettingsPage> createState() => _SettingsState();
|
||||
}
|
||||
|
||||
const url = 'https://rustdesk.com/';
|
||||
final _hasIgnoreBattery = androidVersion >= 26;
|
||||
var _ignoreBatteryOpt = false;
|
||||
var _enableAbr = false;
|
||||
var _isDarkMode = false;
|
||||
var _denyLANDiscovery = false;
|
||||
var _onlyWhiteList = false;
|
||||
|
||||
class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
String? username;
|
||||
@ -60,7 +63,19 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
_enableAbr = enableAbrRes;
|
||||
}
|
||||
|
||||
// _isDarkMode = MyTheme.currentDarkMode(); // TODO
|
||||
final denyLanDiscovery = !option2bool('enable-lan-discovery',
|
||||
await bind.mainGetOption(key: 'enable-lan-discovery'));
|
||||
if (denyLanDiscovery != _denyLANDiscovery) {
|
||||
update = true;
|
||||
_denyLANDiscovery = denyLanDiscovery;
|
||||
}
|
||||
|
||||
final onlyWhiteList =
|
||||
(await bind.mainGetOption(key: 'whitelist')).isNotEmpty;
|
||||
if (onlyWhiteList != _onlyWhiteList) {
|
||||
update = true;
|
||||
_onlyWhiteList = onlyWhiteList;
|
||||
}
|
||||
|
||||
if (update) {
|
||||
setState(() {});
|
||||
@ -100,16 +115,57 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
Provider.of<FfiModel>(context);
|
||||
final enhancementsTiles = [
|
||||
SettingsTile.switchTile(
|
||||
title: Text(translate('Adaptive Bitrate') + ' (beta)'),
|
||||
title: Text('${translate('Adaptive Bitrate')} (beta)'),
|
||||
initialValue: _enableAbr,
|
||||
onToggle: (v) {
|
||||
bind.mainSetOption(key: "enable-abr", value: v ? "" : "N");
|
||||
onToggle: (v) async {
|
||||
await bind.mainSetOption(key: "enable-abr", value: v ? "" : "N");
|
||||
final newValue = await bind.mainGetOption(key: "enable-abr") != "N";
|
||||
setState(() {
|
||||
_enableAbr = !_enableAbr;
|
||||
_enableAbr = newValue;
|
||||
});
|
||||
},
|
||||
)
|
||||
];
|
||||
final shareScreenTiles = [
|
||||
SettingsTile.switchTile(
|
||||
title: Text(translate('Deny LAN Discovery')),
|
||||
initialValue: _denyLANDiscovery,
|
||||
onToggle: (v) async {
|
||||
await bind.mainSetOption(
|
||||
key: "enable-lan-discovery",
|
||||
value: bool2option("enable-lan-discovery", !v));
|
||||
final newValue = !option2bool('enable-lan-discovery',
|
||||
await bind.mainGetOption(key: 'enable-lan-discovery'));
|
||||
setState(() {
|
||||
_denyLANDiscovery = newValue;
|
||||
});
|
||||
},
|
||||
),
|
||||
SettingsTile.switchTile(
|
||||
title: Row(children: [
|
||||
Text(translate('Use IP Whitelisting')),
|
||||
Offstage(
|
||||
offstage: !_onlyWhiteList,
|
||||
child: const Icon(Icons.warning_amber_rounded,
|
||||
color: Color.fromARGB(255, 255, 204, 0)))
|
||||
.marginOnly(left: 5)
|
||||
]),
|
||||
initialValue: _onlyWhiteList,
|
||||
onToggle: (_) async {
|
||||
update() async {
|
||||
final onlyWhiteList =
|
||||
(await bind.mainGetOption(key: 'whitelist')).isNotEmpty;
|
||||
if (onlyWhiteList != _onlyWhiteList) {
|
||||
setState(() {
|
||||
_onlyWhiteList = onlyWhiteList;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
changeWhiteList(callback: update);
|
||||
},
|
||||
)
|
||||
];
|
||||
if (_hasIgnoreBattery) {
|
||||
enhancementsTiles.insert(
|
||||
0,
|
||||
@ -152,7 +208,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
SettingsTile.navigation(
|
||||
title: Text(username == null
|
||||
? translate("Login")
|
||||
: translate("Logout") + ' ($username)'),
|
||||
: '${translate("Logout")} ($username)'),
|
||||
leading: Icon(Icons.person),
|
||||
onPressed: (context) {
|
||||
if (username == null) {
|
||||
@ -177,18 +233,18 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
onPressed: (context) {
|
||||
showLanguageSettings(gFFI.dialogManager);
|
||||
}),
|
||||
SettingsTile.switchTile(
|
||||
SettingsTile.navigation(
|
||||
title: Text(translate('Dark Theme')),
|
||||
leading: Icon(Icons.dark_mode),
|
||||
initialValue: _isDarkMode,
|
||||
onToggle: (v) {
|
||||
setState(() {
|
||||
_isDarkMode = !_isDarkMode;
|
||||
// MyTheme.changeDarkMode(_isDarkMode); // TODO
|
||||
});
|
||||
onPressed: (context) {
|
||||
showThemeSettings(gFFI.dialogManager);
|
||||
},
|
||||
)
|
||||
]),
|
||||
SettingsSection(
|
||||
title: Text(translate("Share Screen")),
|
||||
tiles: shareScreenTiles,
|
||||
),
|
||||
SettingsSection(
|
||||
title: Text(translate("Enhancements")),
|
||||
tiles: enhancementsTiles,
|
||||
@ -232,7 +288,7 @@ void showLanguageSettings(OverlayDialogManager dialogManager) async {
|
||||
final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>;
|
||||
var lang = await bind.mainGetLocalOption(key: "lang");
|
||||
dialogManager.show((setState, close) {
|
||||
final setLang = (v) {
|
||||
setLang(v) {
|
||||
if (lang != v) {
|
||||
setState(() {
|
||||
lang = v;
|
||||
@ -241,7 +297,8 @@ void showLanguageSettings(OverlayDialogManager dialogManager) async {
|
||||
HomePage.homeKey.currentState?.refreshPages();
|
||||
Future.delayed(Duration(milliseconds: 200), close);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: SizedBox.shrink(),
|
||||
content: Column(
|
||||
@ -257,13 +314,41 @@ void showLanguageSettings(OverlayDialogManager dialogManager) async {
|
||||
),
|
||||
actions: []);
|
||||
}, backDismiss: true, clickMaskDismiss: true);
|
||||
} catch (_e) {}
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
void showThemeSettings(OverlayDialogManager dialogManager) async {
|
||||
var themeMode = MyTheme.getThemeModePreference();
|
||||
|
||||
dialogManager.show((setState, close) {
|
||||
setTheme(v) {
|
||||
if (themeMode != v) {
|
||||
setState(() {
|
||||
themeMode = v;
|
||||
});
|
||||
MyTheme.changeDarkMode(themeMode);
|
||||
Future.delayed(Duration(milliseconds: 200), close);
|
||||
}
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: SizedBox.shrink(),
|
||||
contentPadding: 10,
|
||||
content: Column(children: [
|
||||
getRadio('Light', ThemeMode.light, themeMode, setTheme),
|
||||
getRadio('Dark', ThemeMode.dark, themeMode, setTheme),
|
||||
getRadio('Follow System', ThemeMode.system, themeMode, setTheme)
|
||||
]),
|
||||
actions: []);
|
||||
}, backDismiss: true, clickMaskDismiss: true);
|
||||
}
|
||||
|
||||
void showAbout(OverlayDialogManager dialogManager) {
|
||||
dialogManager.show((setState, close) {
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate('About') + ' RustDesk'),
|
||||
title: Text('${translate('About')} RustDesk'),
|
||||
content: Wrap(direction: Axis.vertical, spacing: 12, children: [
|
||||
Text('Version: $version'),
|
||||
InkWell(
|
||||
@ -429,7 +514,7 @@ void showLogin(OverlayDialogManager dialogManager) {
|
||||
),
|
||||
controller: nameController,
|
||||
),
|
||||
PasswordWidget(controller: passwordController),
|
||||
PasswordWidget(controller: passwordController, autoFocus: false),
|
||||
]),
|
||||
actions: (loading
|
||||
? <Widget>[CircularProgressIndicator()]
|
||||
|
@ -6,8 +6,7 @@ import '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
|
||||
void clientClose(OverlayDialogManager dialogManager) {
|
||||
msgBox('', 'Close', 'Are you sure to close the connection?',
|
||||
dialogManager);
|
||||
msgBox('', 'Close', 'Are you sure to close the connection?', dialogManager);
|
||||
}
|
||||
|
||||
void showSuccess() {
|
||||
@ -131,7 +130,7 @@ void setTemporaryPasswordLengthDialog(
|
||||
if (index < 0) index = 0;
|
||||
length = lengths[index];
|
||||
dialogManager.show((setState, close) {
|
||||
final setLength = (newValue) {
|
||||
setLength(newValue) {
|
||||
final oldValue = length;
|
||||
if (oldValue == newValue) return;
|
||||
setState(() {
|
||||
@ -143,7 +142,8 @@ void setTemporaryPasswordLengthDialog(
|
||||
close();
|
||||
showSuccess();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Set temporary password length")),
|
||||
content: Column(
|
||||
@ -230,12 +230,14 @@ void wrongPasswordDialog(String id, OverlayDialogManager dialogManager) {
|
||||
}
|
||||
|
||||
class PasswordWidget extends StatefulWidget {
|
||||
PasswordWidget({Key? key, required this.controller}) : super(key: key);
|
||||
PasswordWidget({Key? key, required this.controller, this.autoFocus = true})
|
||||
: super(key: key);
|
||||
|
||||
final TextEditingController controller;
|
||||
final bool autoFocus;
|
||||
|
||||
@override
|
||||
_PasswordWidgetState createState() => _PasswordWidgetState();
|
||||
State<PasswordWidget> createState() => _PasswordWidgetState();
|
||||
}
|
||||
|
||||
class _PasswordWidgetState extends State<PasswordWidget> {
|
||||
@ -245,7 +247,9 @@ class _PasswordWidgetState extends State<PasswordWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
|
||||
if (widget.autoFocus) {
|
||||
Timer(Duration(milliseconds: 50), () => _focusNode.requestFocus());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -166,10 +166,12 @@ class ChatModel with ChangeNotifier {
|
||||
if (_isShowChatPage) {
|
||||
_isShowChatPage = !_isShowChatPage;
|
||||
notifyListeners();
|
||||
await windowManager.setSize(Size(400, 600));
|
||||
await windowManager.setSize(Size(300, 400));
|
||||
await windowManager.setAlignment(Alignment.topRight);
|
||||
} else {
|
||||
await windowManager.setSize(Size(800, 600));
|
||||
await windowManager.setSize(Size(600, 400));
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
await windowManager.setAlignment(Alignment.topRight);
|
||||
_isShowChatPage = !_isShowChatPage;
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -436,7 +436,7 @@ class ServerModel with ChangeNotifier {
|
||||
clientInfo(client),
|
||||
Text(
|
||||
translate("android_new_connection_tip"),
|
||||
style: const TextStyle(color: Colors.black54),
|
||||
style: Theme.of(globalKey.currentContext!).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -53,13 +53,19 @@ static void my_application_activate(GApplication* application) {
|
||||
// bdw->setCustomFrame(true); // <-- add this line
|
||||
gtk_window_set_default_size(window, 800, 600); // <-- comment this line
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
gtk_widget_set_opacity(GTK_WIDGET(window), 0);
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||
|
||||
FlView* view = fl_view_new(project);
|
||||
gtk_widget_show(GTK_WIDGET(view));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||
|
||||
auto border_frame = gtk_frame_new(nullptr);
|
||||
gtk_frame_set_shadow_type(GTK_FRAME(border_frame), GTK_SHADOW_ETCHED_IN);
|
||||
gtk_container_add(GTK_CONTAINER(border_frame), GTK_WIDGET(view));
|
||||
gtk_widget_show(GTK_WIDGET(border_frame));
|
||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(border_frame));
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 354 B |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 624 B After Width: | Height: | Size: 569 B |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 1023 B After Width: | Height: | Size: 909 B |
@ -243,8 +243,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: fee851fa43116e0b91c39acd0ec37063dc6015f8
|
||||
resolved-ref: fee851fa43116e0b91c39acd0ec37063dc6015f8
|
||||
ref: "74c10aa49fecc088992a9edef1f6da39f83df505"
|
||||
resolved-ref: "74c10aa49fecc088992a9edef1f6da39f83df505"
|
||||
url: "https://github.com/Kingtous/rustdesk_desktop_multi_window"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
|
@ -64,7 +64,7 @@ dependencies:
|
||||
desktop_multi_window:
|
||||
git:
|
||||
url: https://github.com/Kingtous/rustdesk_desktop_multi_window
|
||||
ref: fee851fa43116e0b91c39acd0ec37063dc6015f8
|
||||
ref: 74c10aa49fecc088992a9edef1f6da39f83df505
|
||||
freezed_annotation: ^2.0.3
|
||||
tray_manager:
|
||||
git:
|
||||
|
@ -90,12 +90,12 @@ BEGIN
|
||||
BLOCK "040904e4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "com.carriez" "\0"
|
||||
VALUE "FileDescription", "flutter_hbb" "\0"
|
||||
VALUE "FileDescription", "rustdesk" "\0"
|
||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||
VALUE "InternalName", "flutter_hbb" "\0"
|
||||
VALUE "InternalName", "rustdesk" "\0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2022 com.carriez. All rights reserved." "\0"
|
||||
VALUE "OriginalFilename", "flutter_hbb.exe" "\0"
|
||||
VALUE "ProductName", "flutter_hbb" "\0"
|
||||
VALUE "OriginalFilename", "rustdesk.exe" "\0"
|
||||
VALUE "ProductName", "rustdesk" "\0"
|
||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||
END
|
||||
END
|
||||
|
@ -53,7 +53,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(800, 600);
|
||||
if (!window.CreateAndShow(L"flutter_hbb", origin, size))
|
||||
if (!window.CreateAndShow(L"rustdesk", origin, size))
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
BIN
res/icon-margin.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -36,7 +36,7 @@ def expand():
|
||||
lang = os.path.basename(fn)[:-3]
|
||||
if lang in ['en','cn']: continue
|
||||
dict = get_lang(lang)
|
||||
fw = open("%s.rs"%lang, "wt")
|
||||
fw = open("./src/lang/%s.rs"%lang, "wt")
|
||||
for line in open('./src/lang/cn.rs'):
|
||||
line_strip = line.strip()
|
||||
if line_strip.startswith('("'):
|
||||
|
@ -15,18 +15,7 @@ use hbb_common::{message_proto::Hash, ResultType};
|
||||
|
||||
use crate::flutter::{self, SESSIONS};
|
||||
use crate::start_server;
|
||||
use crate::ui_interface;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::ui_interface::get_sound_inputs;
|
||||
use crate::ui_interface::{
|
||||
change_id, check_mouse_time, check_super_user_permission, default_video_save_directory,
|
||||
discover, forget_password, get_api_server, get_app_name, get_async_job_status,
|
||||
get_connect_status, get_fav, get_id, get_lan_peers, get_langs, get_license, get_local_option,
|
||||
get_mouse_time, get_option, get_options, get_peer, get_peer_option, get_socks, get_uuid,
|
||||
get_version, has_hwcodec, has_rendezvous_service, post_request, send_to_cm, set_local_option,
|
||||
set_option, set_options, set_peer_option, set_permanent_password, set_socks, store_fav,
|
||||
test_if_valid_server, update_temporary_password, using_public_server,
|
||||
};
|
||||
use crate::ui_interface::{self, *};
|
||||
use crate::{
|
||||
client::file_trait::FileManager,
|
||||
flutter::{make_fd_to_json, session_add, session_start_},
|
||||
@ -482,6 +471,9 @@ pub fn main_get_app_name() -> String {
|
||||
get_app_name()
|
||||
}
|
||||
|
||||
pub fn main_get_app_name_sync() -> SyncReturn<String> {
|
||||
SyncReturn(get_app_name())
|
||||
}
|
||||
pub fn main_get_license() -> String {
|
||||
get_license()
|
||||
}
|
||||
@ -563,14 +555,27 @@ pub fn main_get_peer_option(id: String, key: String) -> String {
|
||||
get_peer_option(id, key)
|
||||
}
|
||||
|
||||
pub fn main_get_peer_option_sync(id: String, key: String) -> SyncReturn<String> {
|
||||
SyncReturn(get_peer_option(id, key))
|
||||
}
|
||||
|
||||
pub fn main_set_peer_option(id: String, key: String, value: String) {
|
||||
set_peer_option(id, key, value)
|
||||
}
|
||||
|
||||
pub fn main_set_peer_option_sync(id: String, key: String, value: String) -> SyncReturn<bool> {
|
||||
set_peer_option(id, key, value);
|
||||
SyncReturn(true)
|
||||
}
|
||||
|
||||
pub fn main_forget_password(id: String) {
|
||||
forget_password(id)
|
||||
}
|
||||
|
||||
pub fn main_peer_has_password(id: String) -> bool {
|
||||
peer_has_password(id)
|
||||
}
|
||||
|
||||
pub fn main_get_recent_peers() -> String {
|
||||
if !config::APP_DIR.read().unwrap().is_empty() {
|
||||
let peers: Vec<HashMap<&str, String>> = PeerConfig::peers()
|
||||
@ -699,10 +704,7 @@ fn main_broadcast_message(data: &HashMap<&str, &str>) {
|
||||
}
|
||||
|
||||
pub fn main_change_theme(dark: String) {
|
||||
main_broadcast_message(&HashMap::from([
|
||||
("name", "theme"),
|
||||
("dark", &dark),
|
||||
]));
|
||||
main_broadcast_message(&HashMap::from([("name", "theme"), ("dark", &dark)]));
|
||||
send_to_cm(&crate::ipc::Data::Theme(dark));
|
||||
}
|
||||
|
||||
@ -982,6 +984,48 @@ pub fn query_onlines(ids: Vec<String>) {
|
||||
crate::rendezvous_mediator::query_online_states(ids, handle_query_onlines)
|
||||
}
|
||||
|
||||
pub fn main_is_installed() -> SyncReturn<bool> {
|
||||
SyncReturn(is_installed())
|
||||
}
|
||||
|
||||
pub fn main_is_installed_lower_version() -> SyncReturn<bool> {
|
||||
SyncReturn(is_installed_lower_version())
|
||||
}
|
||||
|
||||
pub fn main_is_installed_daemon(prompt: bool) -> SyncReturn<bool> {
|
||||
SyncReturn(is_installed_daemon(prompt))
|
||||
}
|
||||
|
||||
pub fn main_is_process_trusted(prompt: bool) -> SyncReturn<bool> {
|
||||
SyncReturn(is_process_trusted(prompt))
|
||||
}
|
||||
|
||||
pub fn main_is_can_screen_recording(prompt: bool) -> SyncReturn<bool> {
|
||||
SyncReturn(is_can_screen_recording(prompt))
|
||||
}
|
||||
|
||||
pub fn main_is_share_rdp() -> SyncReturn<bool> {
|
||||
SyncReturn(is_share_rdp())
|
||||
}
|
||||
|
||||
pub fn main_is_rdp_service_open() -> SyncReturn<bool> {
|
||||
SyncReturn(is_rdp_service_open())
|
||||
}
|
||||
|
||||
pub fn main_goto_install() -> SyncReturn<bool> {
|
||||
goto_install();
|
||||
SyncReturn(true)
|
||||
}
|
||||
|
||||
pub fn main_get_new_version() -> SyncReturn<String> {
|
||||
SyncReturn(get_new_version())
|
||||
}
|
||||
|
||||
pub fn main_update_me() -> SyncReturn<bool> {
|
||||
update_me("".to_owned());
|
||||
SyncReturn(true)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub mod server_side {
|
||||
use jni::{
|
||||
|
@ -30,7 +30,8 @@ pub(super) fn start_listening() -> ResultType<()> {
|
||||
if let Ok(msg_in) = Message::parse_from_bytes(&buf[0..len]) {
|
||||
match msg_in.union {
|
||||
Some(rendezvous_message::Union::PeerDiscovery(p)) => {
|
||||
if p.cmd == "ping" {
|
||||
if p.cmd == "ping" && Config::get_option("enable-lan-discovery").is_empty()
|
||||
{
|
||||
if let Some(self_addr) = get_ipaddr_by_peer(&addr) {
|
||||
let mut msg_out = Message::new();
|
||||
let peer = PeerDiscovery {
|
||||
|
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "适应窗口"),
|
||||
("General", "常规"),
|
||||
("Security", "安全"),
|
||||
("Acount", "账户"),
|
||||
("Account", "账户"),
|
||||
("Theme", "主题"),
|
||||
("Dark Theme", "暗黑主题"),
|
||||
("Dark", "黑暗"),
|
||||
("Light", "明亮"),
|
||||
("Follow System", "跟随系统"),
|
||||
("Enable hardware codec", "使用硬件编解码"),
|
||||
("Unlock Security Settings", "解锁安全设置"),
|
||||
("Enable Audio", "允许传输音频"),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", "结束录屏"),
|
||||
("Enable Recording Session", "允许录制会话"),
|
||||
("Allow recording session", "允许录制会话"),
|
||||
("Enable LAN Discovery", "允许局域网发现"),
|
||||
("Deny LAN Discovery", "拒绝局域网发现"),
|
||||
("Write a message", "输入聊天消息"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Je třeba restartovat"),
|
||||
("Unsupported display server ", "Nepodporovaný zobrazovací server"),
|
||||
("x11 expected", "očekávány x11"),
|
||||
("Port", "Číslo portu"),
|
||||
("Port", ""),
|
||||
("Settings", "Nastavení"),
|
||||
("Username", "Uživatelské jméno"),
|
||||
("Invalid port", "Neplatné číslo portu"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "Zastavení služby automaticky ukončí veškerá navázaná spojení."),
|
||||
("android_version_audio_tip", "Vámi nyní používaná verze systému Android nepodporuje zachytávání zvuku – přejděte na Android 10 nebo novější."),
|
||||
("android_start_service_tip", "Službu pro sdílení obrazovky spustíte klepnutím na [Spustit službu] nebo UDĚLTE pověření pro [Zachytávání obsahu obrazovky]."),
|
||||
("Account", "Účet"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Přepsat"),
|
||||
("This file exists, skip or overwrite this file?", "Tento soubor existuje – přeskočit ho nebo přepsat?"),
|
||||
("Quit", "Ukončit"),
|
||||
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Měřítko adaptivní"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Genstart krævet"),
|
||||
("Unsupported display server ", "Ikke-understøttet displayserver"),
|
||||
("x11 expected", "X11 Forventet"),
|
||||
("Port", "Port"),
|
||||
("Port", ""),
|
||||
("Settings", "Indstillinger"),
|
||||
("Username", " Brugernavn"),
|
||||
("Invalid port", "Ugyldig port"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "Ved at lukke tjenesten lukkes alle fremstillede forbindelser automatisk."),
|
||||
("android_version_audio_tip", "Den aktuelle Android -version understøtter ikke lydoptagelse, skal du opdatere om Android 10 eller højere."),
|
||||
("android_start_service_tip", "Tryk på [Start Service] eller åbn autorisationen [skærmoptagelse] for at starte skærmudgivelsen."),
|
||||
("Account", "Konto"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Overskriv"),
|
||||
("This file exists, skip or overwrite this file?", "Denne fil findes, springer over denne fil eller overskriver?"),
|
||||
("Quit", "Afslut"),
|
||||
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Skala adaptiv"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Neustart erforderlich"),
|
||||
("Unsupported display server ", "Nicht unterstützter Display-Server"),
|
||||
("x11 expected", "X11 erwartet"),
|
||||
("Port", "Port"),
|
||||
("Port", ""),
|
||||
("Settings", "Einstellungen"),
|
||||
("Username", " Benutzername"),
|
||||
("Invalid port", "Ungültiger Port"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "Durch das Deaktivieren des Dienstes werden automatisch alle hergestellten Verbindungen getrennt."),
|
||||
("android_version_audio_tip", "Ihre Android-Version unterstützt keine Audioaufnahme, bitte aktualisieren Sie auf Android 10 oder höher, falls möglich."),
|
||||
("android_start_service_tip", "Tippen Sie auf [Dienst aktivieren] oder aktivieren Sie die Berechtigung [Bildschirmzugr.], um den Bildschirmfreigabedienst zu starten."),
|
||||
("Account", "Konto"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Überschreiben"),
|
||||
("This file exists, skip or overwrite this file?", "Diese Datei existiert; überspringen oder überschreiben?"),
|
||||
("Quit", "Beenden"),
|
||||
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Adaptiv skalieren"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Restarto deviga"),
|
||||
("Unsupported display server ", "La aktuala bilda servilo ne estas subtenita"),
|
||||
("x11 expected", "Bonvolu uzi x11"),
|
||||
("Port", "Pordo"),
|
||||
("Port", ""),
|
||||
("Settings", "Agordoj"),
|
||||
("Username", " Uzanta nomo"),
|
||||
("Invalid port", "Pordo nevalida"),
|
||||
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Skalo adapta"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Reinicio requerido"),
|
||||
("Unsupported display server ", "Servidor de visualización no compatible"),
|
||||
("x11 expected", "x11 necesario"),
|
||||
("Port", "Puerto"),
|
||||
("Port", ""),
|
||||
("Settings", "Ajustes"),
|
||||
("Username", " Nombre de usuario"),
|
||||
("Invalid port", "Puerto inválido"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "Cerrar el servicio cerrará automáticamente todas las conexiones establecidas."),
|
||||
("android_version_audio_tip", "La versión actual de Android no admite la captura de audio, actualice a Android 10 o posterior."),
|
||||
("android_start_service_tip", "Toque el permiso [Iniciar servicio] o ABRIR [Captura de pantalla] para iniciar el servicio de uso compartido de pantalla."),
|
||||
("Account", "Cuenta"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Sobrescribir"),
|
||||
("This file exists, skip or overwrite this file?", "Este archivo existe, ¿omitir o sobrescribir este archivo?"),
|
||||
("Quit", "Salir"),
|
||||
@ -289,23 +289,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
|
||||
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
("Connection not allowed", "Conexión no disponible"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
("Translate mode", ""),
|
||||
("Connection not allowed", "Conexión no disponible"),
|
||||
("Use temporary password", "Usar contraseña temporal"),
|
||||
("Use permanent password", "Usar contraseña permamente"),
|
||||
("Use both passwords", "Usar ambas comtraseñas"),
|
||||
@ -337,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Adaptable a escala"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -367,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Redémarrage pour prendre effet"),
|
||||
("Unsupported display server ", "Le serveur d'affichage actuel n'est pas pris en charge"),
|
||||
("x11 expected", "Veuillez passer à x11"),
|
||||
("Port", "Port"),
|
||||
("Port", ""),
|
||||
("Settings", "Paramètres"),
|
||||
("Username", " Nom d'utilisateur"),
|
||||
("Invalid port", "Port invalide"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "La fermeture du service fermera automatiquement toutes les connexions établies."),
|
||||
("android_version_audio_tip", "La version actuelle d'Android ne prend pas en charge la capture audio, veuillez passer à Android 10 ou supérieur."),
|
||||
("android_start_service_tip", "Appuyez sur [Démarrer le service] ou sur l'autorisation OUVRIR [Capture d'écran] pour démarrer le service de partage d'écran."),
|
||||
("Account", "Compte"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Écraser"),
|
||||
("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"),
|
||||
("Quit", "Quitter"),
|
||||
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Échelle adaptative"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Újraindítás szükséges"),
|
||||
("Unsupported display server ", "Nem támogatott kijelző szerver"),
|
||||
("x11 expected", "x11-re számítottt"),
|
||||
("Port", "Port"),
|
||||
("Port", ""),
|
||||
("Settings", "Beállítások"),
|
||||
("Username", "Felhasználónév"),
|
||||
("Invalid port", "Érvénytelen port"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "A szolgáltatás bezárása automatikusan szétkapcsol minden létező kapcsolatot."),
|
||||
("android_version_audio_tip", "A jelenlegi Android verzió nem támogatja a hangrögzítést, kérlek frissíts legalább Android 10-re, vagy egy újabb verzióra."),
|
||||
("android_start_service_tip", "Nyomj a [Szolgáltatás Indítása] opcióra, vagy adj [Képernyőrözítési] jogot az applikációnak hogy elindítsd a képernyőmegosztó szolgáltatást."),
|
||||
("Account", "Fiók"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Felülírás"),
|
||||
("This file exists, skip or overwrite this file?", "Ez a fájl már létezik, skippeljünk, vagy felülírjuk ezt a fájlt?"),
|
||||
("Quit", "Kilépés"),
|
||||
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Skála adaptív"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Diperlukan boot ulang"),
|
||||
("Unsupported display server ", "Server tampilan tidak didukung "),
|
||||
("x11 expected", "x11 diharapkan"),
|
||||
("Port", "Port"),
|
||||
("Port", ""),
|
||||
("Settings", "Pengaturan"),
|
||||
("Username", "Username"),
|
||||
("Invalid port", "Kesalahan port"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "Menutup layanan akan secara otomatis menutup semua koneksi yang dibuat."),
|
||||
("android_version_audio_tip", "Versi Android saat ini tidak mendukung pengambilan audio, harap tingkatkan ke Android 10 atau lebih tinggi."),
|
||||
("android_start_service_tip", "Ketuk izin [Mulai Layanan] atau BUKA [Tangkapan Layar] untuk memulai layanan berbagi layar."),
|
||||
("Account", "Akun"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Timpa"),
|
||||
("This file exists, skip or overwrite this file?", "File ini sudah ada, lewati atau timpa file ini?"),
|
||||
("Quit", "Keluar"),
|
||||
@ -289,23 +289,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"),
|
||||
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
("Connection not allowed", "Koneksi tidak dijinkan"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
("Translate mode", ""),
|
||||
("Connection not allowed", "Koneksi tidak dijinkan"),
|
||||
("Use temporary password", "Gunakan kata sandi sementara"),
|
||||
("Use permanent password", "Gunakan kata sandi permanaen"),
|
||||
("Use both passwords", "Gunakan kedua kata sandi "),
|
||||
@ -337,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Skala adaptif"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -367,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Riavvio necessario"),
|
||||
("Unsupported display server ", "Display server non supportato"),
|
||||
("x11 expected", "x11 necessario"),
|
||||
("Port", "Porta"),
|
||||
("Port", ""),
|
||||
("Settings", "Impostazioni"),
|
||||
("Username", " Nome utente"),
|
||||
("Invalid port", "Porta non valida"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "La chiusura del servizio chiuderà automaticamente tutte le connessioni stabilite."),
|
||||
("android_version_audio_tip", "L'attuale versione di Android non supporta l'acquisizione audio, esegui l'upgrade ad Android 10 o versioni successive."),
|
||||
("android_start_service_tip", "Toccare [Avvia servizio] o APRI l'autorizzazione [Cattura schermo] per avviare il servizio di condivisione dello schermo."),
|
||||
("Account", "Account"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Sovrascrivi"),
|
||||
("This file exists, skip or overwrite this file?", "Questo file esiste, saltare o sovrascrivere questo file?"),
|
||||
("Quit", "Esci"),
|
||||
@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Connection not allowed", "Connessione non consentita"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
("Translate mode", ""),
|
||||
("Use temporary password", "Usa password temporanea"),
|
||||
("Use permanent password", "Usa password permanente"),
|
||||
("Use both passwords", "Usa entrambe le password"),
|
||||
@ -301,6 +304,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to restart", "Sei sicuro di voler riavviare?"),
|
||||
("Restarting Remote Device", "Il dispositivo remoto si sta riavviando"),
|
||||
("remote_restarting_tip", "Riavviare il dispositivo remoto"),
|
||||
("Copied", ""),
|
||||
("Exit Fullscreen", "Esci dalla modalità schermo intero"),
|
||||
("Fullscreen", "A schermo intero"),
|
||||
("Mobile Actions", "Azioni mobili"),
|
||||
@ -318,14 +322,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Insecure Connection", "Connessione insicura"),
|
||||
("Scale original", "Scala originale"),
|
||||
("Scale adaptive", "Scala adattiva"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
("Translate mode", ""),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -353,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "オリジナル"),
|
||||
("Shrink", "縮小"),
|
||||
("Stretch", "伸縮"),
|
||||
("Scrollbar", ""),
|
||||
("ScrollAuto", ""),
|
||||
("Good image quality", "画質優先"),
|
||||
("Balanced", "バランス"),
|
||||
("Optimize reaction time", "速度優先"),
|
||||
@ -185,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "再起動が必要"),
|
||||
("Unsupported display server ", "サポートされていないディスプレイサーバー"),
|
||||
("x11 expected", "X11 が必要です"),
|
||||
("Port", "ポート"),
|
||||
("Port", ""),
|
||||
("Settings", "設定"),
|
||||
("Username", "ユーザー名"),
|
||||
("Invalid port", "無効なポート"),
|
||||
@ -266,11 +268,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "サービスを停止すると、現在確立されている接続が全て自動的に閉じられます。"),
|
||||
("android_version_audio_tip", "現在のAndroidバージョンでは音声キャプチャはサポートされていません。Android 10以降にアップグレードしてください。"),
|
||||
("android_start_service_tip", "「サービスを開始」をタップするか「画面キャプチャ」を開くと、画面共有サービスが開始されます。"),
|
||||
("Account", "アカウント"),
|
||||
("Account", ""),
|
||||
("Overwrite", "上書き"),
|
||||
("This file exists, skip or overwrite this file?", "このファイルは存在しています。スキップするか上書きしますか?"),
|
||||
("Quit", "終了"),
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), // @TODO: Update url when someone translates the document
|
||||
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), // @TODO: Update url when someone translates the docum"),
|
||||
("Help", "ヘルプ"),
|
||||
("Failed", "失敗"),
|
||||
("Succeeded", "成功"),
|
||||
@ -302,6 +304,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to restart", "本当に再起動しますか"),
|
||||
("Restarting Remote Device", "リモート端末を再起動中"),
|
||||
("remote_restarting_tip", "リモート端末は再起動中です。このメッセージボックスを閉じて、しばらくした後に固定のパスワードを使用して再接続してください。"),
|
||||
("Copied", ""),
|
||||
("Exit Fullscreen", "全画面表示を終了"),
|
||||
("Fullscreen", "全画面表示"),
|
||||
("Mobile Actions", "モバイル アクション"),
|
||||
@ -321,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "フィットウィンドウ"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -351,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "원본"),
|
||||
("Shrink", "축소"),
|
||||
("Stretch", "확대"),
|
||||
("Scrollbar", ""),
|
||||
("ScrollAuto", ""),
|
||||
("Good image quality", "최적 이미지 품질"),
|
||||
("Balanced", "균형"),
|
||||
("Optimize reaction time", "반응 시간 최적화"),
|
||||
@ -185,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "재부팅이 필요합니다"),
|
||||
("Unsupported display server ", "지원하지 않는 디스플레이 서버"),
|
||||
("x11 expected", "x11 예상됨"),
|
||||
("Port", "포트"),
|
||||
("Port", ""),
|
||||
("Settings", "설정"),
|
||||
("Username", "사용자명"),
|
||||
("Invalid port", "유효하지 않은 포트"),
|
||||
@ -266,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "서비스를 종료하면 모든 연결이 자동으로 닫힙니다."),
|
||||
("android_version_audio_tip", "현재 Android 버전은 오디오 캡처를 지원하지 않습니다. Android 10 이상으로 업그레이드하십시오."),
|
||||
("android_start_service_tip", "[서비스 시작] 또는 [화면 캡처] 권한을 눌러 화면 공유 서비스를 시작합니다."),
|
||||
("Account", "계정"),
|
||||
("Account", ""),
|
||||
("Overwrite", "덮어쓰기"),
|
||||
("This file exists, skip or overwrite this file?", "해당 파일이 이미 존재합니다, 넘어가거나 덮어쓰시겠습니까?"),
|
||||
("Quit", "종료"),
|
||||
@ -288,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Ignore Battery Optimizations", "배터리 최적화 무시하기"),
|
||||
("android_open_battery_optimizations_tip", "해당 기능을 비활성화하려면 RustDesk 응용 프로그램 설정 페이지로 이동하여 [배터리]에서 [제한 없음] 선택을 해제하십시오."),
|
||||
("Connection not allowed", "연결이 허용되지 않음"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
("Translate mode", ""),
|
||||
("Use temporary password", "임시 비밀번호 사용"),
|
||||
("Use permanent password", "영구 비밀번호 사용"),
|
||||
("Use both passwords", "두 비밀번호 (임시/영구) 사용"),
|
||||
@ -299,6 +304,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to restart", "정말로 재시작 하시겠습니까"),
|
||||
("Restarting Remote Device", "원격 기기를 다시 시작하는중"),
|
||||
("remote_restarting_tip", "원격 장치를 다시 시작하는 중입니다. 이 메시지 상자를 닫고 잠시 후 영구 비밀번호로 다시 연결하십시오."),
|
||||
("Copied", ""),
|
||||
("Exit Fullscreen", "전체 화면 종료"),
|
||||
("Fullscreen", "전체화면"),
|
||||
("Mobile Actions", "모바일 액션"),
|
||||
@ -318,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "맞는 창"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -348,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -329,5 +329,35 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Account", "Есепкі"),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
("Temporary Password Length", ""),
|
||||
("Unlock Network Settings", ""),
|
||||
("Server", ""),
|
||||
("Direct IP Access", ""),
|
||||
("Proxy", ""),
|
||||
("Port", "Порт"),
|
||||
("Apply", ""),
|
||||
("Disconnect all devices?", ""),
|
||||
("Clear", ""),
|
||||
("Audio Input Device", ""),
|
||||
("Deny remote access", ""),
|
||||
("Use IP Whitelisting", ""),
|
||||
("Network", ""),
|
||||
("Enable RDP", ""),
|
||||
("Pin menubar", "Мәзір жолағын бекіту"),
|
||||
("Unpin menubar", "Мәзір жолағын босату"),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Wymagany restart"),
|
||||
("Unsupported display server ", "Nieobsługiwany serwer wyświetlania "),
|
||||
("x11 expected", "oczekiwane X11"),
|
||||
("Port", "Port"),
|
||||
("Port", ""),
|
||||
("Settings", "Ustawienia"),
|
||||
("Username", "Nazwa użytkownika"),
|
||||
("Invalid port", "Nieprawidłowy port"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "android_stop_service_tip"),
|
||||
("android_version_audio_tip", "android_version_audio_tip"),
|
||||
("android_start_service_tip", "android_start_service_tip"),
|
||||
("Account", "Konto"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Nadpisz"),
|
||||
("This file exists, skip or overwrite this file?", "Ten plik istnieje, pominąć czy nadpisać ten plik?"),
|
||||
("Quit", "Zrezygnuj"),
|
||||
@ -289,19 +289,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", "Zachowaj usługę w tle RustDesk"),
|
||||
("Ignore Battery Optimizations", "Ignoruj optymalizację baterii"),
|
||||
("android_open_battery_optimizations_tip", "android_open_battery_optimizations_tip"),
|
||||
("Random Password After Session", "Losowe hasło po sesji"),
|
||||
("Keep", "Zachowaj"),
|
||||
("Update", "Aktualizacja"),
|
||||
("Disable", "Wyłącz"),
|
||||
("Onetime Password", "Hasło jednorazowe"),
|
||||
("Verification Method", "Metoda weryfikacji"),
|
||||
("Enable security password", "Włącz hasło zabezpieczające"),
|
||||
("Enable random password", "Włącz losowe hasło"),
|
||||
("Enable onetime password", "Włącz hasło jednorazowe"),
|
||||
("Disable onetime password", "Wyłącz hasło jednorazowe"),
|
||||
("Activate onetime password", "Aktywuj hasło jednorazowe"),
|
||||
("Set security password", "Ustaw hasło zabezpieczające"),
|
||||
("Connection not allowed", "Połączenie niedozwolone"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
("Translate mode", ""),
|
||||
("Use temporary password", ""),
|
||||
("Use permanent password", ""),
|
||||
("Use both passwords", ""),
|
||||
("Set permanent password", ""),
|
||||
("Set temporary password length", ""),
|
||||
("Enable Remote Restart", ""),
|
||||
("Allow remote restart", ""),
|
||||
("Restart Remote Device", ""),
|
||||
("Are you sure you want to restart", ""),
|
||||
("Restarting Remote Device", ""),
|
||||
("remote_restarting_tip", ""),
|
||||
("Copied", ""),
|
||||
("Exit Fullscreen", "Wyłączyć tryb pełnoekranowy"),
|
||||
("Fullscreen", "Pełny ekran"),
|
||||
@ -322,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Skala adaptacyjna"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -352,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Original", "Original"),
|
||||
("Shrink", "Reduzir"),
|
||||
("Stretch", "Aumentar"),
|
||||
("Scrollbar", ""),
|
||||
("ScrollAuto", ""),
|
||||
("Good image quality", "Qualidade visual boa"),
|
||||
("Balanced", "Equilibrada"),
|
||||
("Optimize reaction time", "Optimizar tempo de reacção"),
|
||||
@ -127,7 +129,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Set Password", "Definir palavra-chave"),
|
||||
("OS Password", "Senha do SO"),
|
||||
("install_tip", "Devido ao UAC, o RustDesk não funciona correctamente em alguns casos. Para evitar o UAC, por favor clique no botão abaixo para instalar o RustDesk no sistema."),
|
||||
("Click to update", "Clique para fazer a actualização"),
|
||||
("Click to upgrade", ""),
|
||||
("Click to download", "Clique para carregar"),
|
||||
("Click to update", "Clique para fazer a actualização"),
|
||||
("Configure", "Configurar"),
|
||||
@ -185,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Reinicialização necessária"),
|
||||
("Unsupported display server ", "Servidor de display não suportado"),
|
||||
("x11 expected", "x11 em falha"),
|
||||
("Port", "Porta"),
|
||||
("Port", ""),
|
||||
("Settings", "Configurações"),
|
||||
("Username", "Nome de utilizador"),
|
||||
("Invalid port", "Porta inválida"),
|
||||
@ -266,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "Fechar o serviço irá automaticamente fechar todas as ligações estabelecidas."),
|
||||
("android_version_audio_tip", "A versão atual do Android não suporta captura de áudio, por favor actualize para o Android 10 ou maior."),
|
||||
("android_start_service_tip", "Toque [Iniciar Serviço] ou abra a permissão [Captura de Ecran] para iniciar o serviço de partilha de ecran."),
|
||||
("Account", "Conta"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Substituir"),
|
||||
("This file exists, skip or overwrite this file?", "Este ficheiro já existe, ignorar ou substituir este ficheiro?"),
|
||||
("Quit", "Saída"),
|
||||
@ -288,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Ignore Battery Optimizations", "Ignorar optimizações de Bateria"),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Connection not allowed", "Ligação não autorizada"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
("Translate mode", ""),
|
||||
("Use temporary password", "Utilizar palavra-chave temporária"),
|
||||
("Use permanent password", "Utilizar palavra-chave permanente"),
|
||||
("Use both passwords", "Utilizar ambas as palavras-chave"),
|
||||
@ -299,6 +304,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Are you sure you want to restart", "Tem a certeza que pretende reiniciar"),
|
||||
("Restarting Remote Device", "A reiniciar sistema remoto"),
|
||||
("remote_restarting_tip", ""),
|
||||
("Copied", ""),
|
||||
("Exit Fullscreen", "Sair da tela cheia"),
|
||||
("Fullscreen", "Tela cheia"),
|
||||
("Mobile Actions", "Ações para celular"),
|
||||
@ -318,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Escala adaptável"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -348,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Reinicialização necessária"),
|
||||
("Unsupported display server ", "Servidor de display não suportado"),
|
||||
("x11 expected", "x11 esperado"),
|
||||
("Port", "Porta"),
|
||||
("Port", ""),
|
||||
("Settings", "Configurações"),
|
||||
("Username", "Nome de usuário"),
|
||||
("Invalid port", "Porta inválida"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "Fechar o serviço irá automaticamente fechar todas as conexões estabelecidas."),
|
||||
("android_version_audio_tip", "A versão atual do Android não suporta captura de áudio, por favor atualize para o Android 10 ou maior."),
|
||||
("android_start_service_tip", "Toque [Iniciar Serviço] ou abra a permissão [Captura de Tela] para iniciar o serviço de compartilhamento de tela."),
|
||||
("Account", "Conta"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Substituir"),
|
||||
("This file exists, skip or overwrite this file?", "Este arquivo existe, pular ou substituir este arquivo?"),
|
||||
("Quit", "Saída"),
|
||||
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", ""),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Требуется перезагрузка"),
|
||||
("Unsupported display server ", "Неподдерживаемый сервер дисплея"),
|
||||
("x11 expected", "Ожидается X11"),
|
||||
("Port", "Порт"),
|
||||
("Port", ""),
|
||||
("Settings", "Настройки"),
|
||||
("Username", "Имя пользователя"),
|
||||
("Invalid port", "Неверный порт"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "Закрытие службы автоматически закроет все установленные соединения."),
|
||||
("android_version_audio_tip", "Текущая версия Android не поддерживает захват звука, обновите ее до Android 10 или выше."),
|
||||
("android_start_service_tip", "Нажмите [Запуск промежуточного сервера] или ОТКРЫТЬ разрешение [Захват экрана], чтобы запустить службу демонстрации экрана."),
|
||||
("Account", "Аккаунт"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Перезаписать"),
|
||||
("This file exists, skip or overwrite this file?", "Этот файл существует, пропустить или перезаписать файл?"),
|
||||
("Quit", "Выйти"),
|
||||
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Масштаб адаптивный"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Vyžaduje sa reštart"),
|
||||
("Unsupported display server ", "Nepodporovaný zobrazovací (display) server"),
|
||||
("x11 expected", "očakáva sa x11"),
|
||||
("Port", "Port"),
|
||||
("Port", ""),
|
||||
("Settings", "Nastavenia"),
|
||||
("Username", "Uživateľské meno"),
|
||||
("Invalid port", "Neplatný port"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "Zastavenie služby automaticky ukončí všetky naviazané spojenia."),
|
||||
("android_version_audio_tip", "Vaša verzia Androidu neumožňuje zaznamenávanie zvuku. Prejdite na verziu Android 10 alebo vyššiu."),
|
||||
("android_start_service_tip", "Klepnite na [Spustiť službu] alebo OTVORTE oprávnenie [Zachytávanie obsahu obrazovky], aby sa aktivovala služba zdieľania obrazovky."),
|
||||
("Account", "Účet"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Prepísať"),
|
||||
("This file exists, skip or overwrite this file?", "Preskočiť alebo prepísať existujúci súbor?"),
|
||||
("Quit", "Ukončiť"),
|
||||
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Prispôsobivá mierka"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", ""),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Yeniden başlatma gerekli"),
|
||||
("Unsupported display server ", "Desteklenmeyen görüntü sunucusu"),
|
||||
("x11 expected", "x11 bekleniyor"),
|
||||
("Port", "Port"),
|
||||
("Port", ""),
|
||||
("Settings", "Ayarlar"),
|
||||
("Username", "Kullanıcı Adı"),
|
||||
("Invalid port", "Geçersiz port"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "Hizmetin kapatılması, kurulan tüm bağlantıları otomatik olarak kapatacaktır."),
|
||||
("android_version_audio_tip", "Mevcut Android sürümü ses yakalamayı desteklemiyor, lütfen Android 10 veya sonraki bir sürüme yükseltin."),
|
||||
("android_start_service_tip", "Ekran paylaşım hizmetini başlatmak için [Hizmeti Başlat] veya AÇ [Ekran Yakalama] iznine dokunun."),
|
||||
("Account", "Hesap"),
|
||||
("Account", ""),
|
||||
("Overwrite", "üzerine yaz"),
|
||||
("This file exists, skip or overwrite this file?", "Bu dosya var, bu dosya atlansın veya üzerine yazılsın mı?"),
|
||||
("Quit", "Çıkış"),
|
||||
@ -289,23 +289,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Keep RustDesk background service", ""),
|
||||
("Ignore Battery Optimizations", ""),
|
||||
("android_open_battery_optimizations_tip", ""),
|
||||
("Random Password After Session", ""),
|
||||
("Keep", ""),
|
||||
("Update", ""),
|
||||
("Disable", ""),
|
||||
("Onetime Password", ""),
|
||||
("Verification Method", ""),
|
||||
("Enable security password", ""),
|
||||
("Enable random password", ""),
|
||||
("Enable onetime password", ""),
|
||||
("Disable onetime password", ""),
|
||||
("Activate onetime password", ""),
|
||||
("Set security password", ""),
|
||||
("Connection not allowed", ""),
|
||||
("Connection not allowed", "bağlantıya izin verilmedi"),
|
||||
("Legacy mode", ""),
|
||||
("Map mode", ""),
|
||||
("Translate mode", ""),
|
||||
("Connection not allowed", "bağlantıya izin verilmedi"),
|
||||
("Use temporary password", "Geçici şifre kullan"),
|
||||
("Use permanent password", "Kalıcı şifre kullan"),
|
||||
("Use both passwords", "İki şifreyide kullan"),
|
||||
@ -337,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Ölçek uyarlanabilir"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -367,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "需要重新啟動"),
|
||||
("Unsupported display server ", "不支援顯示伺服器"),
|
||||
("x11 expected", "預期 x11"),
|
||||
("Port", "連接埠"),
|
||||
("Port", "端口"),
|
||||
("Settings", "設定"),
|
||||
("Username", "使用者名稱"),
|
||||
("Invalid port", "連接埠無效"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "關閉服務將自動關閉所有已建立的連接。"),
|
||||
("android_version_audio_tip", "目前的 Android 版本不支持音訊錄製,請升級至 Android 10 或以上版本。"),
|
||||
("android_start_service_tip", "點擊 「啟動服務」 或啟用 「畫面錄製」 權限以開啟手機畫面共享服務。"),
|
||||
("Account", "帳戶"),
|
||||
("Account", "賬戶"),
|
||||
("Overwrite", "覆寫"),
|
||||
("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要跳過或是覆寫此檔案嗎?"),
|
||||
("Quit", "退出"),
|
||||
@ -324,14 +324,17 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "適應窗口"),
|
||||
("General", "常規"),
|
||||
("Security", "安全"),
|
||||
("Acount", "賬戶"),
|
||||
("Account", "賬戶"),
|
||||
("Theme", "主題"),
|
||||
("Dark Theme", "暗黑主題"),
|
||||
("Dark", "黑暗"),
|
||||
("Light", "明亮"),
|
||||
("Follow System", "跟隨系統"),
|
||||
("Enable hardware codec", "使用硬件編解碼"),
|
||||
("Unlock Security Settings", "解鎖安全設置"),
|
||||
("Enable Audio", "允許傳輸音頻"),
|
||||
("Temporary Password Length", "临时密码长度"),
|
||||
("Unlock Network Settings", "臨時密碼長度"),
|
||||
("Temporary Password Length", "臨時密碼長度"),
|
||||
("Unlock Network Settings", "解鎖網絡設置"),
|
||||
("Server", "服務器"),
|
||||
("Direct IP Access", "IP直接訪問"),
|
||||
("Proxy", "代理"),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", "結束錄屏"),
|
||||
("Enable Recording Session", "允許錄製會話"),
|
||||
("Allow recording session", "允許錄製會話"),
|
||||
("Enable LAN Discovery", "允許局域網發現"),
|
||||
("Deny LAN Discovery", "拒絕局域網發現"),
|
||||
("Write a message", "輸入聊天消息"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Reboot required", "Yêu cầu khởi động lại"),
|
||||
("Unsupported display server ", "Máy chủ hiển thị không đuợc hỗ trọ"),
|
||||
("x11 expected", "Cần x11"),
|
||||
("Port", "Cổng"),
|
||||
("Port", ""),
|
||||
("Settings", "Cài đặt"),
|
||||
("Username", "Tên người dùng"),
|
||||
("Invalid port", "Cổng không hợp lệ"),
|
||||
@ -268,7 +268,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_stop_service_tip", "Đóng dịch vụ sẽ tự động đóng tất cả các kết nối đã thiết lập."),
|
||||
("android_version_audio_tip", "Phiên bản Android hiện tại không hỗ trợ ghi âm, vui lòng nâng cấp lên Android 10 trở lên."),
|
||||
("android_start_service_tip", "Nhấn vào [Bắt đầu dịch vụ] hoặc MỞ quyền [Ghi màn hình] để bắt đầu dịch vụ chia sẻ màn hình."),
|
||||
("Account", "Tài khoản"),
|
||||
("Account", ""),
|
||||
("Overwrite", "Ghi đè"),
|
||||
("This file exists, skip or overwrite this file?", "Tệp tin này đã tồn tại, bạn có muốn bỏ qua hay ghi đè lên tệp tin này?"),
|
||||
("Quit", "Thoát"),
|
||||
@ -324,9 +324,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Scale adaptive", "Quy mô thích ứng"),
|
||||
("General", ""),
|
||||
("Security", ""),
|
||||
("Acount", ""),
|
||||
("Account", ""),
|
||||
("Theme", ""),
|
||||
("Dark Theme", ""),
|
||||
("Dark", ""),
|
||||
("Light", ""),
|
||||
("Follow System", ""),
|
||||
("Enable hardware codec", ""),
|
||||
("Unlock Security Settings", ""),
|
||||
("Enable Audio", ""),
|
||||
@ -354,5 +357,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Stop session recording", ""),
|
||||
("Enable Recording Session", ""),
|
||||
("Allow recording session", ""),
|
||||
("Enable LAN Discovery", ""),
|
||||
("Deny LAN Discovery", ""),
|
||||
("Write a message", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -297,6 +297,7 @@ class MyIdMenu: Reactor.Component {
|
||||
<li #enable-file-transfer><span>{svg_checkmark}</span>{translate('Enable File Transfer')}</li>
|
||||
<li #enable-remote-restart><span>{svg_checkmark}</span>{translate('Enable Remote Restart')}</li>
|
||||
<li #enable-tunnel><span>{svg_checkmark}</span>{translate('Enable TCP Tunneling')}</li>
|
||||
<li #enable-lan-discovery><span>{svg_checkmark}</span>{translate('Enable LAN Discovery')}</li>
|
||||
<AudioInputs />
|
||||
<Enhancements />
|
||||
<li #allow-remote-config-modification><span>{svg_checkmark}</span>{translate('Enable remote configuration modification')}</li>
|
||||
|
@ -338,10 +338,13 @@ pub fn set_socks(proxy: String, username: String, password: String) {
|
||||
.ok();
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[inline]
|
||||
pub fn is_installed() -> bool {
|
||||
crate::platform::is_installed()
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
return crate::platform::is_installed();
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|