mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
Merge branch 'rustdesk:master' into feat/optional_update_check
This commit is contained in:
BIN
flutter/assets/scam.png
Normal file
BIN
flutter/assets/scam.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 627 KiB |
@@ -101,6 +101,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
required this.highlight,
|
||||
required this.drag_indicator,
|
||||
required this.shadow,
|
||||
required this.errorBannerBg,
|
||||
});
|
||||
|
||||
final Color? border;
|
||||
@@ -108,6 +109,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
final Color? highlight;
|
||||
final Color? drag_indicator;
|
||||
final Color? shadow;
|
||||
final Color? errorBannerBg;
|
||||
|
||||
static final light = ColorThemeExtension(
|
||||
border: Color(0xFFCCCCCC),
|
||||
@@ -115,6 +117,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
highlight: Color(0xFFE5E5E5),
|
||||
drag_indicator: Colors.grey[800],
|
||||
shadow: Colors.black,
|
||||
errorBannerBg: Color(0xFFFDEEEB),
|
||||
);
|
||||
|
||||
static final dark = ColorThemeExtension(
|
||||
@@ -123,6 +126,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
highlight: Color(0xFF3F3F3F),
|
||||
drag_indicator: Colors.grey,
|
||||
shadow: Colors.grey,
|
||||
errorBannerBg: Color(0xFF470F2D),
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -132,6 +136,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
Color? highlight,
|
||||
Color? drag_indicator,
|
||||
Color? shadow,
|
||||
Color? errorBannerBg,
|
||||
}) {
|
||||
return ColorThemeExtension(
|
||||
border: border ?? this.border,
|
||||
@@ -139,6 +144,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
highlight: highlight ?? this.highlight,
|
||||
drag_indicator: drag_indicator ?? this.drag_indicator,
|
||||
shadow: shadow ?? this.shadow,
|
||||
errorBannerBg: errorBannerBg ?? this.errorBannerBg,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -154,6 +160,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||
highlight: Color.lerp(highlight, other.highlight, t),
|
||||
drag_indicator: Color.lerp(drag_indicator, other.drag_indicator, t),
|
||||
shadow: Color.lerp(shadow, other.shadow, t),
|
||||
errorBannerBg: Color.lerp(shadow, other.errorBannerBg, t),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -258,6 +265,14 @@ class MyTheme {
|
||||
? EdgeInsets.only(left: dialogPadding)
|
||||
: EdgeInsets.only(left: dialogPadding / 3);
|
||||
|
||||
static ScrollbarThemeData scrollbarTheme = ScrollbarThemeData(
|
||||
thickness: MaterialStateProperty.all(kScrollbarThickness),
|
||||
);
|
||||
|
||||
static ScrollbarThemeData scrollbarThemeDark = scrollbarTheme.copyWith(
|
||||
thumbColor: MaterialStateProperty.all(Colors.grey[500]),
|
||||
);
|
||||
|
||||
static ThemeData lightTheme = ThemeData(
|
||||
brightness: Brightness.light,
|
||||
hoverColor: Color.fromARGB(255, 224, 224, 224),
|
||||
@@ -273,6 +288,7 @@ class MyTheme {
|
||||
),
|
||||
),
|
||||
),
|
||||
scrollbarTheme: scrollbarTheme,
|
||||
inputDecorationTheme: isDesktop
|
||||
? InputDecorationTheme(
|
||||
fillColor: grayBg,
|
||||
@@ -357,6 +373,7 @@ class MyTheme {
|
||||
),
|
||||
),
|
||||
),
|
||||
scrollbarTheme: scrollbarThemeDark,
|
||||
inputDecorationTheme: isDesktop
|
||||
? InputDecorationTheme(
|
||||
fillColor: Color(0xFF24252B),
|
||||
@@ -383,9 +400,6 @@ class MyTheme {
|
||||
tabBarTheme: const TabBarTheme(
|
||||
labelColor: Colors.white70,
|
||||
),
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
thumbColor: MaterialStateProperty.all(Colors.grey[500]),
|
||||
),
|
||||
tooltipTheme: tooltipTheme(),
|
||||
splashColor: isDesktop ? Colors.transparent : null,
|
||||
highlightColor: isDesktop ? Colors.transparent : null,
|
||||
|
||||
@@ -76,7 +76,7 @@ class _AddressBookState extends State<AddressBook> {
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: height,
|
||||
color: Color.fromARGB(255, 253, 238, 235),
|
||||
color: MyTheme.color(context).errorBannerBg,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
|
||||
@@ -302,6 +302,53 @@ Future<String> changeDirectAccessPort(
|
||||
return controller.text;
|
||||
}
|
||||
|
||||
Future<String> changeAutoDisconnectTimeout(String old) async {
|
||||
final controller = TextEditingController(text: old);
|
||||
await gFFI.dialogManager.show((setState, close, context) {
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate("Timeout in minutes")),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8.0),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
maxLines: null,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: InputDecoration(
|
||||
hintText: '10',
|
||||
isCollapsed: true,
|
||||
suffix: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(Icons.clear, size: 16),
|
||||
onPressed: () => controller.clear())),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(
|
||||
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
|
||||
],
|
||||
controller: controller,
|
||||
autofocus: true),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
dialogButton("Cancel", onPressed: close, isOutline: true),
|
||||
dialogButton("OK", onPressed: () async {
|
||||
await bind.mainSetOption(
|
||||
key: 'auto-disconnect-timeout', value: controller.text);
|
||||
close();
|
||||
}),
|
||||
],
|
||||
onCancel: close,
|
||||
);
|
||||
});
|
||||
return controller.text;
|
||||
}
|
||||
|
||||
class DialogTextField extends StatelessWidget {
|
||||
final String title;
|
||||
final String? hintText;
|
||||
|
||||
@@ -13,6 +13,8 @@ const String kPeerPlatformLinux = "Linux";
|
||||
const String kPeerPlatformMacOS = "Mac OS";
|
||||
const String kPeerPlatformAndroid = "Android";
|
||||
|
||||
const double kScrollbarThickness = 12.0;
|
||||
|
||||
/// [kAppTypeMain] used by 'Desktop Main Page' , 'Mobile (Client and Server)', "Install Page"
|
||||
const String kAppTypeMain = "main";
|
||||
|
||||
|
||||
@@ -732,6 +732,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
reverse: true, enabled: enabled),
|
||||
...directIp(context),
|
||||
whitelist(),
|
||||
...autoDisconnect(context),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -910,6 +911,63 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
||||
));
|
||||
}));
|
||||
}
|
||||
|
||||
List<Widget> autoDisconnect(BuildContext context) {
|
||||
TextEditingController controller = TextEditingController();
|
||||
update() => setState(() {});
|
||||
RxBool applyEnabled = false.obs;
|
||||
final optionKey = 'allow-auto-disconnect';
|
||||
final timeoutKey = 'auto-disconnect-timeout';
|
||||
return [
|
||||
_OptionCheckBox(context, 'auto_disconnect_option_tip', optionKey,
|
||||
update: update, enabled: !locked),
|
||||
() {
|
||||
bool enabled =
|
||||
option2bool(optionKey, bind.mainGetOptionSync(key: optionKey));
|
||||
if (!enabled) applyEnabled.value = false;
|
||||
controller.text = bind.mainGetOptionSync(key: timeoutKey);
|
||||
return Offstage(
|
||||
offstage: !enabled,
|
||||
child: _SubLabeledWidget(
|
||||
context,
|
||||
'Timeout in minutes',
|
||||
Row(children: [
|
||||
SizedBox(
|
||||
width: 95,
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
enabled: enabled && !locked,
|
||||
onChanged: (_) => applyEnabled.value = true,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(
|
||||
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
hintText: '10',
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||
),
|
||||
).marginOnly(right: 15),
|
||||
),
|
||||
Obx(() => ElevatedButton(
|
||||
onPressed: applyEnabled.value && enabled && !locked
|
||||
? () async {
|
||||
applyEnabled.value = false;
|
||||
await bind.mainSetOption(
|
||||
key: timeoutKey, value: controller.text);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
translate('Apply'),
|
||||
),
|
||||
))
|
||||
]),
|
||||
enabled: enabled && !locked,
|
||||
),
|
||||
);
|
||||
}(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class _Network extends StatefulWidget {
|
||||
@@ -1541,9 +1599,14 @@ Widget _OptionCheckBox(BuildContext context, String label, String key,
|
||||
isServer
|
||||
? await mainSetBoolOption(key, option)
|
||||
: await mainSetLocalBoolOption(key, option);
|
||||
ref.value = isServer
|
||||
final readOption = isServer
|
||||
? mainGetBoolOptionSync(key)
|
||||
: mainGetLocalBoolOptionSync(key);
|
||||
if (reverse) {
|
||||
ref.value = !readOption;
|
||||
} else {
|
||||
ref.value = readOption;
|
||||
}
|
||||
update?.call();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,6 +694,7 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
enableCustomMouseWheelScrolling: cursorOverImage.isFalse,
|
||||
customMouseWheelScrollConfig: scrollConfig,
|
||||
child: RawScrollbar(
|
||||
thickness: kScrollbarThickness,
|
||||
thumbColor: Colors.grey,
|
||||
controller: _horizontal,
|
||||
thumbVisibility: false,
|
||||
@@ -711,6 +712,7 @@ class _ImagePaintState extends State<ImagePaint> {
|
||||
enableCustomMouseWheelScrolling: cursorOverImage.isFalse,
|
||||
customMouseWheelScrollConfig: scrollConfig,
|
||||
child: RawScrollbar(
|
||||
thickness: kScrollbarThickness,
|
||||
thumbColor: Colors.grey,
|
||||
controller: _vertical,
|
||||
thumbVisibility: false,
|
||||
|
||||
@@ -210,11 +210,199 @@ class ServiceNotRunningNotification extends StatelessWidget {
|
||||
.marginOnly(bottom: 8),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
onPressed: serverModel.toggleService,
|
||||
onPressed: () {
|
||||
if (gFFI.userModel.userName.value.isEmpty && bind.mainGetLocalOption(key: "show-scam-warning") != "N") {
|
||||
_showScamWarning(context, serverModel);
|
||||
} else {
|
||||
serverModel.toggleService();
|
||||
}
|
||||
},
|
||||
label: Text(translate("Start Service")))
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
void _showScamWarning(BuildContext context, ServerModel serverModel) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return ScamWarningDialog(serverModel: serverModel);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ScamWarningDialog extends StatefulWidget {
|
||||
final ServerModel serverModel;
|
||||
|
||||
ScamWarningDialog({required this.serverModel});
|
||||
|
||||
@override
|
||||
_ScamWarningDialogState createState() => _ScamWarningDialogState();
|
||||
}
|
||||
|
||||
class _ScamWarningDialogState extends State<ScamWarningDialog> {
|
||||
int _countdown = 12;
|
||||
bool show_warning = false;
|
||||
late Timer _timer;
|
||||
late ServerModel _serverModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_serverModel = widget.serverModel;
|
||||
startCountdown();
|
||||
}
|
||||
|
||||
void startCountdown() {
|
||||
const oneSecond = Duration(seconds: 1);
|
||||
_timer = Timer.periodic(oneSecond, (timer) {
|
||||
setState(() {
|
||||
_countdown--;
|
||||
if (_countdown <= 0) {
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isButtonLocked = _countdown > 0;
|
||||
|
||||
return AlertDialog(
|
||||
content: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topRight,
|
||||
end: Alignment.bottomLeft,
|
||||
colors: [
|
||||
Color(0xffe242bc),
|
||||
Color(0xfff4727c),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
),
|
||||
padding: EdgeInsets.all(25.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.warning_amber_sharp,
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
translate("Warning"),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Center(
|
||||
child: Image.asset('assets/scam.png',
|
||||
width: 180,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 18),
|
||||
Text(
|
||||
translate("scam_title"),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22.0,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 18),
|
||||
Text(
|
||||
translate("scam_text1")+"\n\n"
|
||||
+translate("scam_text2")+"\n",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Checkbox(
|
||||
value: show_warning,
|
||||
onChanged: (value) {
|
||||
setState((){
|
||||
show_warning = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
Text(
|
||||
translate("Don't show again"),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: isButtonLocked
|
||||
? null
|
||||
: () {
|
||||
Navigator.of(context).pop();
|
||||
_serverModel.toggleService();
|
||||
if (show_warning) {
|
||||
bind.mainSetLocalOption(key: "show-scam-warning", value: "N");
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.blueAccent,
|
||||
),
|
||||
child: Text(
|
||||
isButtonLocked ? translate("I Agree")+" (${_countdown}s)" : translate("I Agree"),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 15),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.blueAccent,
|
||||
),
|
||||
child: Text(
|
||||
translate("Decline"),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)])),
|
||||
contentPadding: EdgeInsets.all(0.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ServerInfo extends StatelessWidget {
|
||||
|
||||
@@ -45,10 +45,12 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
var _enableDirectIPAccess = false;
|
||||
var _enableRecordSession = false;
|
||||
var _autoRecordIncomingSession = false;
|
||||
var _allowAutoDisconnect = false;
|
||||
var _localIP = "";
|
||||
var _directAccessPort = "";
|
||||
var _fingerprint = "";
|
||||
var _buildDate = "";
|
||||
var _autoDisconnectTimeout = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -151,6 +153,20 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
_buildDate = buildDate;
|
||||
}
|
||||
|
||||
final allowAutoDisconnect = option2bool('allow-auto-disconnect',
|
||||
await bind.mainGetOption(key: 'allow-auto-disconnect'));
|
||||
if (allowAutoDisconnect != _allowAutoDisconnect) {
|
||||
update = true;
|
||||
_allowAutoDisconnect = allowAutoDisconnect;
|
||||
}
|
||||
|
||||
final autoDisconnectTimeout =
|
||||
await bind.mainGetOption(key: 'auto-disconnect-timeout');
|
||||
if (autoDisconnectTimeout != _autoDisconnectTimeout) {
|
||||
update = true;
|
||||
_autoDisconnectTimeout = autoDisconnectTimeout;
|
||||
}
|
||||
|
||||
if (update) {
|
||||
setState(() {});
|
||||
}
|
||||
@@ -306,6 +322,48 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
||||
await bind.mainSetOption(key: 'direct-server', value: value);
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
SettingsTile.switchTile(
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(translate("auto_disconnect_option_tip")),
|
||||
Offstage(
|
||||
offstage: !_allowAutoDisconnect,
|
||||
child: Text(
|
||||
'${_autoDisconnectTimeout.isEmpty ? '10' : _autoDisconnectTimeout} min',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
)),
|
||||
])),
|
||||
Offstage(
|
||||
offstage: !_allowAutoDisconnect,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Icon(
|
||||
Icons.edit,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () async {
|
||||
final timeout = await changeAutoDisconnectTimeout(
|
||||
_autoDisconnectTimeout);
|
||||
setState(() {
|
||||
_autoDisconnectTimeout = timeout;
|
||||
});
|
||||
}))
|
||||
]),
|
||||
initialValue: _allowAutoDisconnect,
|
||||
onToggle: (_) async {
|
||||
_allowAutoDisconnect = !_allowAutoDisconnect;
|
||||
String value =
|
||||
bool2option('allow-auto-disconnect', _allowAutoDisconnect);
|
||||
await bind.mainSetOption(key: 'allow-auto-disconnect', value: value);
|
||||
setState(() {});
|
||||
},
|
||||
)
|
||||
];
|
||||
if (_hasIgnoreBattery) {
|
||||
|
||||
Reference in New Issue
Block a user