Merge pull request #1445 from 21pages/optimize

id format && dialog key and focus handling
This commit is contained in:
RustDesk 2022-09-05 08:23:48 +07:00 committed by GitHub
commit f385127f3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1245 additions and 1056 deletions

View File

@ -7,6 +7,7 @@ import 'package:back_button_interceptor/back_button_interceptor.dart';
import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/peer_model.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -154,7 +155,7 @@ class MyTheme {
brightness: Brightness.light, brightness: Brightness.light,
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: TabBarTheme( tabBarTheme: const TabBarTheme(
labelColor: Colors.black87, labelColor: Colors.black87,
), ),
splashColor: Colors.transparent, splashColor: Colors.transparent,
@ -162,13 +163,14 @@ class MyTheme {
).copyWith( ).copyWith(
extensions: <ThemeExtension<dynamic>>[ extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.light, ColorThemeExtension.light,
TabbarTheme.light,
], ],
); );
static ThemeData darkTheme = ThemeData( static ThemeData darkTheme = ThemeData(
brightness: Brightness.dark, brightness: Brightness.dark,
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
tabBarTheme: TabBarTheme( tabBarTheme: const TabBarTheme(
labelColor: Colors.white70, labelColor: Colors.white70,
), ),
splashColor: Colors.transparent, splashColor: Colors.transparent,
@ -176,12 +178,17 @@ class MyTheme {
).copyWith( ).copyWith(
extensions: <ThemeExtension<dynamic>>[ extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.dark, ColorThemeExtension.dark,
TabbarTheme.dark,
], ],
); );
static ColorThemeExtension color(BuildContext context) { static ColorThemeExtension color(BuildContext context) {
return Theme.of(context).extension<ColorThemeExtension>()!; return Theme.of(context).extension<ColorThemeExtension>()!;
} }
static TabbarTheme tabbar(BuildContext context) {
return Theme.of(context).extension<TabbarTheme>()!;
}
} }
bool isDarkTheme() { bool isDarkTheme() {
@ -340,34 +347,41 @@ class OverlayDialogManager {
{bool clickMaskDismiss = false, {bool clickMaskDismiss = false,
bool showCancel = true, bool showCancel = true,
VoidCallback? onCancel}) { VoidCallback? onCancel}) {
show((setState, close) => CustomAlertDialog( show((setState, close) {
cancel() {
dismissAll();
if (onCancel != null) {
onCancel();
}
}
return CustomAlertDialog(
content: Container( content: Container(
constraints: BoxConstraints(maxWidth: 240), constraints: const BoxConstraints(maxWidth: 240),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 30), const SizedBox(height: 30),
Center(child: CircularProgressIndicator()), const Center(child: CircularProgressIndicator()),
SizedBox(height: 20), const SizedBox(height: 20),
Center( Center(
child: Text(translate(text), child: Text(translate(text),
style: TextStyle(fontSize: 15))), style: const TextStyle(fontSize: 15))),
SizedBox(height: 20), const SizedBox(height: 20),
Offstage( Offstage(
offstage: !showCancel, offstage: !showCancel,
child: Center( child: Center(
child: TextButton( child: TextButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () { onPressed: cancel,
dismissAll();
if (onCancel != null) {
onCancel();
}
},
child: Text(translate('Cancel'), child: Text(translate('Cancel'),
style: TextStyle(color: MyTheme.accent))))) style:
])))); const TextStyle(color: MyTheme.accent)))))
])),
onCancel: showCancel ? cancel : null,
);
});
} }
} }
@ -377,18 +391,18 @@ void showToast(String text, {Duration timeout = const Duration(seconds: 2)}) {
final entry = OverlayEntry(builder: (_) { final entry = OverlayEntry(builder: (_) {
return IgnorePointer( return IgnorePointer(
child: Align( child: Align(
alignment: Alignment(0.0, 0.8), alignment: const Alignment(0.0, 0.8),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withOpacity(0.6), color: Colors.black.withOpacity(0.6),
borderRadius: BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.circular(20), Radius.circular(20),
), ),
), ),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
child: Text( child: Text(
text, text,
style: TextStyle( style: const TextStyle(
decoration: TextDecoration.none, decoration: TextDecoration.none,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
fontSize: 18, fontSize: 18,
@ -403,23 +417,54 @@ void showToast(String text, {Duration timeout = const Duration(seconds: 2)}) {
} }
class CustomAlertDialog extends StatelessWidget { class CustomAlertDialog extends StatelessWidget {
CustomAlertDialog( const CustomAlertDialog(
{this.title, required this.content, this.actions, this.contentPadding}); {Key? key,
this.title,
required this.content,
this.actions,
this.contentPadding,
this.onSubmit,
this.onCancel})
: super(key: key);
final Widget? title; final Widget? title;
final Widget content; final Widget content;
final List<Widget>? actions; final List<Widget>? actions;
final double? contentPadding; final double? contentPadding;
final Function()? onSubmit;
final Function()? onCancel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( FocusNode focusNode = FocusNode();
scrollable: true, // request focus if there is no focused FocusNode in the dialog
title: title, Future.delayed(Duration.zero, () {
contentPadding: if (!focusNode.hasFocus) focusNode.requestFocus();
EdgeInsets.symmetric(horizontal: contentPadding ?? 25, vertical: 10), });
content: content, return Focus(
actions: actions, focusNode: focusNode,
autofocus: true,
onKey: (node, key) {
if (key.logicalKey == LogicalKeyboardKey.escape) {
if (key is RawKeyDownEvent) {
onCancel?.call();
}
return KeyEventResult.handled; // avoid TextField exception on escape
} else if (onSubmit != null &&
key.logicalKey == LogicalKeyboardKey.enter) {
if (key is RawKeyDownEvent) onSubmit?.call();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: AlertDialog(
scrollable: true,
title: title,
contentPadding: EdgeInsets.symmetric(
horizontal: contentPadding ?? 25, vertical: 10),
content: content,
actions: actions,
),
); );
} }
} }
@ -429,26 +474,28 @@ void msgBox(
{bool? hasCancel}) { {bool? hasCancel}) {
dialogManager.dismissAll(); dialogManager.dismissAll();
List<Widget> buttons = []; List<Widget> buttons = [];
bool hasOk = false;
submit() {
dialogManager.dismissAll();
// https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263
if (!type.contains("custom")) {
closeConnection();
}
}
cancel() {
dialogManager.dismissAll();
}
if (type != "connecting" && type != "success" && !type.contains("nook")) { if (type != "connecting" && type != "success" && !type.contains("nook")) {
buttons.insert( hasOk = true;
0, buttons.insert(0, msgBoxButton(translate('OK'), submit));
msgBoxButton(translate('OK'), () {
dialogManager.dismissAll();
// https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263
if (!type.contains("custom")) {
closeConnection();
}
}));
} }
hasCancel ??= !type.contains("error") && hasCancel ??= !type.contains("error") &&
!type.contains("nocancel") && !type.contains("nocancel") &&
type != "restarting"; type != "restarting";
if (hasCancel) { if (hasCancel) {
buttons.insert( buttons.insert(0, msgBoxButton(translate('Cancel'), cancel));
0,
msgBoxButton(translate('Cancel'), () {
dialogManager.dismissAll();
}));
} }
// TODO: test this button // TODO: test this button
if (type.contains("hasclose")) { if (type.contains("hasclose")) {
@ -459,9 +506,12 @@ void msgBox(
})); }));
} }
dialogManager.show((setState, close) => CustomAlertDialog( dialogManager.show((setState, close) => CustomAlertDialog(
title: _msgBoxTitle(title), title: _msgBoxTitle(title),
content: Text(translate(text), style: TextStyle(fontSize: 15)), content: Text(translate(text), style: const TextStyle(fontSize: 15)),
actions: buttons)); actions: buttons,
onSubmit: hasOk ? submit : null,
onCancel: hasCancel == true ? cancel : null,
));
} }
Widget msgBoxButton(String text, void Function() onPressed) { Widget msgBoxButton(String text, void Function() onPressed) {
@ -479,15 +529,19 @@ Widget msgBoxButton(String text, void Function() onPressed) {
Text(translate(text), style: TextStyle(color: MyTheme.accent)))); Text(translate(text), style: TextStyle(color: MyTheme.accent))));
} }
Widget _msgBoxTitle(String title) => Text(translate(title), style: TextStyle(fontSize: 21)); Widget _msgBoxTitle(String title) =>
Text(translate(title), style: TextStyle(fontSize: 21));
void msgBoxCommon(OverlayDialogManager dialogManager, String title, void msgBoxCommon(OverlayDialogManager dialogManager, String title,
Widget content, List<Widget> buttons) { Widget content, List<Widget> buttons,
{bool hasCancel = true}) {
dialogManager.dismissAll(); dialogManager.dismissAll();
dialogManager.show((setState, close) => CustomAlertDialog( dialogManager.show((setState, close) => CustomAlertDialog(
title: _msgBoxTitle(title), title: _msgBoxTitle(title),
content: content, content: content,
actions: buttons)); actions: buttons,
onCancel: hasCancel ? close : null,
));
} }
Color str2color(String str, [alpha = 0xFF]) { Color str2color(String str, [alpha = 0xFF]) {

View File

@ -1,4 +1,52 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// TODO: Divide every 3 number to display ID class IDTextEditingController extends TextEditingController {
class IdFormController extends TextEditingController {} IDTextEditingController({String? text}) : super(text: text);
String get id => trimID(value.text);
set id(String newID) => text = formatID(newID);
}
class IDTextInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
if (newValue.text.isEmpty) {
return newValue.copyWith(text: '');
} else if (newValue.text.compareTo(oldValue.text) == 0) {
return newValue;
} else {
int selectionIndexFromTheRight =
newValue.text.length - newValue.selection.extentOffset;
String newID = formatID(newValue.text);
return TextEditingValue(
text: newID,
selection: TextSelection.collapsed(
offset: newID.length - selectionIndexFromTheRight,
),
);
}
}
}
String formatID(String id) {
String id2 = id.replaceAll(' ', '');
String newID = '';
if (id2.length <= 3) {
newID = id2;
} else {
var n = id2.length;
var a = n % 3 != 0 ? n % 3 : 3;
newID = id2.substring(0, a);
for (var i = a; i < n; i += 3) {
newID += " ${id2.substring(i, i + 3)}";
}
}
return newID;
}
String trimID(String id) {
return id.replaceAll(' ', '');
}

View File

@ -12,6 +12,7 @@ import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import '../../common.dart'; import '../../common.dart';
import '../../common/formatter/id_formatter.dart';
import '../../mobile/pages/scan_page.dart'; import '../../mobile/pages/scan_page.dart';
import '../../mobile/pages/settings_page.dart'; import '../../mobile/pages/settings_page.dart';
import '../../models/model.dart'; import '../../models/model.dart';
@ -30,7 +31,7 @@ class ConnectionPage extends StatefulWidget {
/// State for the connection page. /// State for the connection page.
class _ConnectionPageState extends State<ConnectionPage> { class _ConnectionPageState extends State<ConnectionPage> {
/// Controller for the id input bar. /// Controller for the id input bar.
final _idController = TextEditingController(); final _idController = IDTextEditingController();
/// Update url. If it's not null, means an update is available. /// Update url. If it's not null, means an update is available.
final _updateUrl = ''; final _updateUrl = '';
@ -43,9 +44,9 @@ class _ConnectionPageState extends State<ConnectionPage> {
if (_idController.text.isEmpty) { if (_idController.text.isEmpty) {
() async { () async {
final lastRemoteId = await bind.mainGetLastRemoteId(); final lastRemoteId = await bind.mainGetLastRemoteId();
if (lastRemoteId != _idController.text) { if (lastRemoteId != _idController.id) {
setState(() { setState(() {
_idController.text = lastRemoteId; _idController.id = lastRemoteId;
}); });
} }
}(); }();
@ -110,7 +111,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
/// Callback for the connect button. /// Callback for the connect button.
/// Connects to the selected peer. /// Connects to the selected peer.
void onConnect({bool isFileTransfer = false}) { void onConnect({bool isFileTransfer = false}) {
final id = _idController.text.trim(); final id = _idController.id;
connect(id, isFileTransfer: isFileTransfer); connect(id, isFileTransfer: isFileTransfer);
} }
@ -166,7 +167,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
}); });
var w = Container( var w = Container(
width: 320 + 20 * 2, width: 320 + 20 * 2,
padding: EdgeInsets.fromLTRB(20, 24, 20, 22), padding: const EdgeInsets.fromLTRB(20, 24, 20, 22),
decoration: BoxDecoration( decoration: BoxDecoration(
color: MyTheme.color(context).bg, color: MyTheme.color(context).bg,
borderRadius: const BorderRadius.all(Radius.circular(13)), borderRadius: const BorderRadius.all(Radius.circular(13)),
@ -178,42 +179,54 @@ class _ConnectionPageState extends State<ConnectionPage> {
children: [ children: [
Text( Text(
translate('Control Remote Desktop'), translate('Control Remote Desktop'),
style: TextStyle(fontSize: 19, height: 1), style: const TextStyle(fontSize: 19, height: 1),
), ),
], ],
).marginOnly(bottom: 15), ).marginOnly(bottom: 15),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: TextField( child: Obx(
autocorrect: false, () => TextField(
enableSuggestions: false, autocorrect: false,
keyboardType: TextInputType.visiblePassword, enableSuggestions: false,
style: TextStyle( keyboardType: TextInputType.visiblePassword,
fontFamily: 'WorkSans', focusNode: focusNode,
fontSize: 22, style: const TextStyle(
height: 1, fontFamily: 'WorkSans',
), fontSize: 22,
decoration: InputDecoration( height: 1,
hintText: translate('Enter Remote ID'), ),
hintStyle: TextStyle( maxLines: 1,
color: MyTheme.color(context).placeholder), cursorColor: MyTheme.color(context).text!,
border: OutlineInputBorder( decoration: InputDecoration(
hintText: inputFocused.value
? null
: translate('Enter Remote ID'),
hintStyle: TextStyle(
color: MyTheme.color(context).placeholder),
border: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderSide: BorderSide(
color: MyTheme.color(context).border!)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderSide: BorderSide(
color: MyTheme.color(context).border!)),
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.zero, borderRadius: BorderRadius.zero,
borderSide: BorderSide( borderSide:
color: MyTheme.color(context).placeholder!)), BorderSide(color: MyTheme.button, width: 3),
focusedBorder: OutlineInputBorder( ),
borderRadius: BorderRadius.zero, isDense: true,
borderSide: contentPadding: const EdgeInsets.symmetric(
BorderSide(color: MyTheme.button, width: 3), horizontal: 10, vertical: 12)),
), controller: _idController,
isDense: true, inputFormatters: [IDTextInputFormatter()],
contentPadding: onSubmitted: (s) {
EdgeInsets.symmetric(horizontal: 10, vertical: 12)), onConnect();
controller: _idController, },
onSubmitted: (s) { ),
onConnect();
},
), ),
), ),
], ],
@ -259,7 +272,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
).marginSymmetric(horizontal: 12), ).marginSymmetric(horizontal: 12),
), ),
)), )),
SizedBox( const SizedBox(
width: 17, width: 17,
), ),
Obx( Obx(
@ -304,7 +317,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
), ),
); );
return Center( return Center(
child: Container(constraints: BoxConstraints(maxWidth: 600), child: w)); child: Container(
constraints: const BoxConstraints(maxWidth: 600), child: w));
} }
@override @override
@ -654,71 +668,69 @@ class _ConnectionPageState extends State<ConnectionPage> {
var field = ""; var field = "";
var msg = ""; var msg = "";
var isInProgress = false; var isInProgress = false;
TextEditingController controller = TextEditingController(text: field);
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async {
setState(() {
msg = "";
isInProgress = true;
});
field = controller.text.trim();
if (field.isEmpty) {
// pass
} else {
final ids = field.trim().split(RegExp(r"[\s,;\n]+"));
field = ids.join(',');
for (final newId in ids) {
if (gFFI.abModel.idContainBy(newId)) {
continue;
}
gFFI.abModel.addId(newId);
}
await gFFI.abModel.updateAb();
this.setState(() {});
// final currentPeers
}
close();
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("Add ID")), title: Text(translate("Add ID")),
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(translate("whitelist_sep")), Text(translate("whitelist_sep")),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
onChanged: (s) { maxLines: null,
field = s; decoration: InputDecoration(
}, border: const OutlineInputBorder(),
maxLines: null, errorText: msg.isEmpty ? null : translate(msg),
decoration: InputDecoration( ),
border: OutlineInputBorder(), controller: controller,
errorText: msg.isEmpty ? null : translate(msg), focusNode: FocusNode()..requestFocus()),
),
controller: TextEditingController(text: field),
),
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 4.0, height: 4.0,
), ),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
], ],
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () { TextButton(onPressed: submit, child: Text(translate("OK"))),
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() {
msg = "";
isInProgress = true;
});
field = field.trim();
if (field.isEmpty) {
// pass
} else {
final ids = field.trim().split(RegExp(r"[\s,;\n]+"));
field = ids.join(',');
for (final newId in ids) {
if (gFFI.abModel.idContainBy(newId)) {
continue;
}
gFFI.abModel.addId(newId);
}
await gFFI.abModel.updateAb();
this.setState(() {});
// final currentPeers
}
close();
},
child: Text(translate("OK"))),
], ],
onSubmit: submit,
onCancel: close,
); );
}); });
} }
@ -727,67 +739,65 @@ class _ConnectionPageState extends State<ConnectionPage> {
var field = ""; var field = "";
var msg = ""; var msg = "";
var isInProgress = false; var isInProgress = false;
TextEditingController controller = TextEditingController(text: field);
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async {
setState(() {
msg = "";
isInProgress = true;
});
field = controller.text.trim();
if (field.isEmpty) {
// pass
} else {
final tags = field.trim().split(RegExp(r"[\s,;\n]+"));
field = tags.join(',');
for (final tag in tags) {
gFFI.abModel.addTag(tag);
}
await gFFI.abModel.updateAb();
// final currentPeers
}
close();
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("Add Tag")), title: Text(translate("Add Tag")),
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(translate("whitelist_sep")), Text(translate("whitelist_sep")),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
onChanged: (s) {
field = s;
},
maxLines: null, maxLines: null,
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg), errorText: msg.isEmpty ? null : translate(msg),
), ),
controller: TextEditingController(text: field), controller: controller,
focusNode: FocusNode()..requestFocus(),
), ),
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 4.0, height: 4.0,
), ),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
], ],
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () { TextButton(onPressed: submit, child: Text(translate("OK"))),
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() {
msg = "";
isInProgress = true;
});
field = field.trim();
if (field.isEmpty) {
// pass
} else {
final tags = field.trim().split(RegExp(r"[\s,;\n]+"));
field = tags.join(',');
for (final tag in tags) {
gFFI.abModel.addTag(tag);
}
await gFFI.abModel.updateAb();
// final currentPeers
}
close();
},
child: Text(translate("OK"))),
], ],
onSubmit: submit,
onCancel: close,
); );
}); });
} }
@ -799,13 +809,23 @@ class _ConnectionPageState extends State<ConnectionPage> {
var selectedTag = gFFI.abModel.getPeerTags(id).obs; var selectedTag = gFFI.abModel.getPeerTags(id).obs;
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async {
setState(() {
isInProgress = true;
});
gFFI.abModel.changeTagForPeer(id, selectedTag);
await gFFI.abModel.updateAb();
close();
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("Edit Tag")), title: Text(translate("Edit Tag")),
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Wrap( child: Wrap(
children: tags children: tags
.map((e) => buildTag(e, selectedTag, onTap: () { .map((e) => buildTag(e, selectedTag, onTap: () {
@ -818,26 +838,16 @@ class _ConnectionPageState extends State<ConnectionPage> {
.toList(growable: false), .toList(growable: false),
), ),
), ),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
], ],
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () { TextButton(onPressed: submit, child: Text(translate("OK"))),
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() {
isInProgress = true;
});
gFFI.abModel.changeTagForPeer(id, selectedTag);
await gFFI.abModel.updateAb();
close();
},
child: Text(translate("OK"))),
], ],
onSubmit: submit,
onCancel: close,
); );
}); });
} }

View File

@ -83,7 +83,6 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = isDarkTheme() ? TarBarTheme.dark() : TarBarTheme.light();
final RxBool fullscreen = Get.find(tag: 'fullscreen'); final RxBool fullscreen = Get.find(tag: 'fullscreen');
return Obx(() => SubWindowDragToResizeArea( return Obx(() => SubWindowDragToResizeArea(
resizeEdgeSize: fullscreen.value ? 1.0 : 8.0, resizeEdgeSize: fullscreen.value ? 1.0 : 8.0,
@ -95,14 +94,11 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
backgroundColor: MyTheme.color(context).bg, backgroundColor: MyTheme.color(context).bg,
body: Obx(() => DesktopTab( body: Obx(() => DesktopTab(
controller: tabController, controller: tabController,
theme: theme,
showTabBar: fullscreen.isFalse, showTabBar: fullscreen.isFalse,
onClose: () { onClose: () {
tabController.clear(); tabController.clear();
}, },
tail: AddButton( tail: AddButton().paddingOnly(left: 10),
theme: theme,
).paddingOnly(left: 10),
pageViewBuilder: (pageView) { pageViewBuilder: (pageView) {
WindowController.fromWindowId(windowId()) WindowController.fromWindowId(windowId())
.setFullscreen(fullscreen.isTrue); .setFullscreen(fullscreen.isTrue);

View File

@ -55,7 +55,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
return Row( return Row(
children: [ children: [
buildServerInfo(context), buildServerInfo(context),
VerticalDivider( const VerticalDivider(
width: 1, width: 1,
thickness: 1, thickness: 1,
), ),
@ -93,23 +93,23 @@ class _DesktopHomePageState extends State<DesktopHomePage>
buildIDBoard(BuildContext context) { buildIDBoard(BuildContext context) {
final model = gFFI.serverModel; final model = gFFI.serverModel;
return Container( return Container(
margin: EdgeInsets.only(left: 20, right: 16), margin: const EdgeInsets.only(left: 20, right: 11),
height: 52, height: 57,
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.baseline, crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic, textBaseline: TextBaseline.alphabetic,
children: [ children: [
Container( Container(
width: 2, width: 2,
decoration: BoxDecoration(color: MyTheme.accent), decoration: const BoxDecoration(color: MyTheme.accent),
), ).marginOnly(top: 5),
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 7),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( SizedBox(
height: 25, height: 25,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -120,7 +120,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: MyTheme.color(context).lightText), color: MyTheme.color(context).lightText),
), ).marginOnly(top: 5),
buildPopupMenu(context) buildPopupMenu(context)
], ],
), ),
@ -135,11 +135,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
child: TextFormField( child: TextFormField(
controller: model.serverId, controller: model.serverId,
readOnly: true, readOnly: true,
decoration: InputDecoration( decoration: const InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.only(bottom: 18), contentPadding: EdgeInsets.only(bottom: 20),
), ),
style: TextStyle( style: const TextStyle(
fontSize: 22, fontSize: 22,
), ),
), ),
@ -244,7 +244,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}, },
child: Obx( child: Obx(
() => CircleAvatar( () => CircleAvatar(
radius: 12, radius: 15,
backgroundColor: hover.value backgroundColor: hover.value
? MyTheme.color(context).grayBg! ? MyTheme.color(context).grayBg!
: MyTheme.color(context).bg!, : MyTheme.color(context).bg!,
@ -277,7 +277,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
), ),
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 7),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -303,7 +303,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
readOnly: true, readOnly: true,
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,
contentPadding: EdgeInsets.only(bottom: 8), contentPadding: EdgeInsets.only(bottom: 2),
), ),
style: TextStyle(fontSize: 15), style: TextStyle(fontSize: 15),
), ),
@ -317,7 +317,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
? MyTheme.color(context).text ? MyTheme.color(context).text
: Color(0xFFDDDDDD), : Color(0xFFDDDDDD),
size: 22, size: 22,
).marginOnly(right: 10, bottom: 8), ).marginOnly(right: 8, bottom: 2),
), ),
onTap: () => bind.mainUpdateTemporaryPassword(), onTap: () => bind.mainUpdateTemporaryPassword(),
onHover: (value) => refreshHover.value = value, onHover: (value) => refreshHover.value = value,
@ -425,13 +425,13 @@ class _DesktopHomePageState extends State<DesktopHomePage>
color: editHover.value color: editHover.value
? MyTheme.color(context).text ? MyTheme.color(context).text
: Color(0xFFDDDDDD)) : Color(0xFFDDDDDD))
.marginOnly(bottom: 8))); .marginOnly(bottom: 2)));
} }
buildTip(BuildContext context) { buildTip(BuildContext context) {
return Padding( return Padding(
padding: padding:
const EdgeInsets.only(left: 20.0, right: 16, top: 16.0, bottom: 14), const EdgeInsets.only(left: 20.0, right: 16, top: 16.0, bottom: 5),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -642,76 +642,76 @@ class _DesktopHomePageState extends State<DesktopHomePage>
var newId = ""; var newId = "";
var msg = ""; var msg = "";
var isInProgress = false; var isInProgress = false;
TextEditingController controller = TextEditingController();
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async {
newId = controller.text.trim();
setState(() {
msg = "";
isInProgress = true;
bind.mainChangeId(newId: newId);
});
var status = await bind.mainGetAsyncStatus();
while (status == " ") {
await Future.delayed(const Duration(milliseconds: 100));
status = await bind.mainGetAsyncStatus();
}
if (status.isEmpty) {
// ok
close();
return;
}
setState(() {
isInProgress = false;
msg = translate(status);
});
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("Change ID")), title: Text(translate("Change ID")),
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(translate("id_change_tip")), Text(translate("id_change_tip")),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
Text("ID:").marginOnly(bottom: 16.0), const Text("ID:").marginOnly(bottom: 16.0),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
onChanged: (s) {
newId = s;
},
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg)), errorText: msg.isEmpty ? null : translate(msg)),
inputFormatters: [ inputFormatters: [
LengthLimitingTextInputFormatter(16), LengthLimitingTextInputFormatter(16),
// FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true) // FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
], ],
maxLength: 16, maxLength: 16,
controller: controller,
focusNode: FocusNode()..requestFocus(),
), ),
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 4.0, height: 4.0,
), ),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
], ],
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () { TextButton(onPressed: submit, child: Text(translate("OK"))),
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() {
msg = "";
isInProgress = true;
bind.mainChangeId(newId: newId);
});
var status = await bind.mainGetAsyncStatus();
while (status == " ") {
await Future.delayed(Duration(milliseconds: 100));
status = await bind.mainGetAsyncStatus();
}
if (status.isEmpty) {
// ok
close();
return;
}
setState(() {
isInProgress = false;
msg = translate(status);
});
},
child: Text(translate("OK"))),
], ],
onSubmit: submit,
onCancel: close,
); );
}); });
} }
@ -720,16 +720,16 @@ class _DesktopHomePageState extends State<DesktopHomePage>
final appName = await bind.mainGetAppName(); final appName = await bind.mainGetAppName();
final license = await bind.mainGetLicense(); final license = await bind.mainGetLicense();
final version = await bind.mainGetVersion(); final version = await bind.mainGetVersion();
final linkStyle = TextStyle(decoration: TextDecoration.underline); const linkStyle = TextStyle(decoration: TextDecoration.underline);
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
return CustomAlertDialog( return CustomAlertDialog(
title: Text("About $appName"), title: Text("About $appName"),
content: ConstrainedBox( content: ConstrainedBox(
constraints: BoxConstraints(minWidth: 500), constraints: const BoxConstraints(minWidth: 500),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Text("Version: $version").marginSymmetric(vertical: 4.0), Text("Version: $version").marginSymmetric(vertical: 4.0),
@ -737,7 +737,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
onTap: () { onTap: () {
launchUrlString("https://rustdesk.com/privacy"); launchUrlString("https://rustdesk.com/privacy");
}, },
child: Text( child: const Text(
"Privacy Statement", "Privacy Statement",
style: linkStyle, style: linkStyle,
).marginSymmetric(vertical: 4.0)), ).marginSymmetric(vertical: 4.0)),
@ -745,13 +745,14 @@ class _DesktopHomePageState extends State<DesktopHomePage>
onTap: () { onTap: () {
launchUrlString("https://rustdesk.com"); launchUrlString("https://rustdesk.com");
}, },
child: Text( child: const Text(
"Website", "Website",
style: linkStyle, style: linkStyle,
).marginSymmetric(vertical: 4.0)), ).marginSymmetric(vertical: 4.0)),
Container( Container(
decoration: BoxDecoration(color: Color(0xFF2c8cff)), decoration: const BoxDecoration(color: Color(0xFF2c8cff)),
padding: EdgeInsets.symmetric(vertical: 24, horizontal: 8), padding:
const EdgeInsets.symmetric(vertical: 24, horizontal: 8),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@ -760,9 +761,9 @@ class _DesktopHomePageState extends State<DesktopHomePage>
children: [ children: [
Text( Text(
"Copyright &copy; 2022 Purslane Ltd.\n$license", "Copyright &copy; 2022 Purslane Ltd.\n$license",
style: TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
), ),
Text( const Text(
"Made with heart in this chaotic world!", "Made with heart in this chaotic world!",
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
@ -778,12 +779,10 @@ class _DesktopHomePageState extends State<DesktopHomePage>
), ),
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("OK"))),
onPressed: () async {
close();
},
child: Text(translate("OK"))),
], ],
onSubmit: close,
onCancel: close,
); );
}); });
} }
@ -815,118 +814,124 @@ Future<bool> loginDialog() async {
var isInProgress = false; var isInProgress = false;
var completer = Completer<bool>(); var completer = Completer<bool>();
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async {
setState(() {
userNameMsg = "";
passMsg = "";
isInProgress = true;
});
cancel() {
setState(() {
isInProgress = false;
});
}
userName = userContontroller.text;
pass = pwdController.text;
if (userName.isEmpty) {
userNameMsg = translate("Username missed");
cancel();
return;
}
if (pass.isEmpty) {
passMsg = translate("Password missed");
cancel();
return;
}
try {
final resp = await gFFI.userModel.login(userName, pass);
if (resp.containsKey('error')) {
passMsg = resp['error'];
cancel();
return;
}
// {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w,
// token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}}
debugPrint("$resp");
completer.complete(true);
} catch (err) {
// ignore: avoid_print
print(err.toString());
cancel();
return;
}
close();
}
cancel() {
completer.complete(false);
close();
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("Login")), title: Text(translate("Login")),
content: ConstrainedBox( content: ConstrainedBox(
constraints: BoxConstraints(minWidth: 500), constraints: const BoxConstraints(minWidth: 500),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text( child: Text(
"${translate('Username')}:", "${translate('Username')}:",
textAlign: TextAlign.start, textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)), ).marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: userNameMsg.isNotEmpty ? userNameMsg : null), errorText: userNameMsg.isNotEmpty ? userNameMsg : null),
controller: userContontroller, controller: userContontroller,
focusNode: FocusNode()..requestFocus(),
), ),
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:") child: Text("${translate('Password')}:")
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: passMsg.isNotEmpty ? passMsg : null), errorText: passMsg.isNotEmpty ? passMsg : null),
controller: pwdController, controller: pwdController,
), ),
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 4.0, height: 4.0,
), ),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
], ],
), ),
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: cancel, child: Text(translate("Cancel"))),
onPressed: () { TextButton(onPressed: submit, child: Text(translate("OK"))),
completer.complete(false);
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() {
userNameMsg = "";
passMsg = "";
isInProgress = true;
});
final cancel = () {
setState(() {
isInProgress = false;
});
};
userName = userContontroller.text;
pass = pwdController.text;
if (userName.isEmpty) {
userNameMsg = translate("Username missed");
cancel();
return;
}
if (pass.isEmpty) {
passMsg = translate("Password missed");
cancel();
return;
}
try {
final resp = await gFFI.userModel.login(userName, pass);
if (resp.containsKey('error')) {
passMsg = resp['error'];
cancel();
return;
}
// {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w,
// token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}}
debugPrint("$resp");
completer.complete(true);
} catch (err) {
print(err.toString());
cancel();
return;
}
close();
},
child: Text(translate("OK"))),
], ],
onSubmit: submit,
onCancel: cancel,
); );
}); });
return completer.future; return completer.future;
@ -940,55 +945,78 @@ void setPasswordDialog() async {
var errMsg1 = ""; var errMsg1 = "";
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() {
setState(() {
errMsg0 = "";
errMsg1 = "";
});
final pass = p0.text.trim();
if (pass.length < 6) {
setState(() {
errMsg0 = translate("Too short, at least 6 characters.");
});
return;
}
if (p1.text.trim() != pass) {
setState(() {
errMsg1 = translate("The confirmation is not identical.");
});
return;
}
bind.mainSetPermanentPassword(password: pass);
close();
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("Set Password")), title: Text(translate("Set Password")),
content: ConstrainedBox( content: ConstrainedBox(
constraints: BoxConstraints(minWidth: 500), constraints: const BoxConstraints(minWidth: 500),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text( child: Text(
"${translate('Password')}:", "${translate('Password')}:",
textAlign: TextAlign.start, textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)), ).marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: errMsg0.isNotEmpty ? errMsg0 : null), errorText: errMsg0.isNotEmpty ? errMsg0 : null),
controller: p0, controller: p0,
focusNode: FocusNode()..requestFocus(),
), ),
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Confirmation')}:") child: Text("${translate('Confirmation')}:")
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: errMsg1.isNotEmpty ? errMsg1 : null), errorText: errMsg1.isNotEmpty ? errMsg1 : null),
controller: p1, controller: p1,
), ),
@ -999,35 +1027,11 @@ void setPasswordDialog() async {
), ),
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () { TextButton(onPressed: submit, child: Text(translate("OK"))),
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () {
setState(() {
errMsg0 = "";
errMsg1 = "";
});
final pass = p0.text.trim();
if (pass.length < 6) {
setState(() {
errMsg0 = translate("Too short, at least 6 characters.");
});
return;
}
if (p1.text.trim() != pass) {
setState(() {
errMsg1 = translate("The confirmation is not identical.");
});
return;
}
bind.mainSetPermanentPassword(password: pass);
close();
},
child: Text(translate("OK"))),
], ],
onSubmit: submit,
onCancel: close,
); );
}); });
} }

View File

@ -49,7 +49,7 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
'Display', Icons.desktop_windows_outlined, Icons.desktop_windows_sharp), 'Display', Icons.desktop_windows_outlined, Icons.desktop_windows_sharp),
_TabInfo('Audio', Icons.volume_up_outlined, Icons.volume_up_sharp), _TabInfo('Audio', Icons.volume_up_outlined, Icons.volume_up_sharp),
_TabInfo('Connection', Icons.link_outlined, Icons.link_sharp), _TabInfo('Connection', Icons.link_outlined, Icons.link_sharp),
_TabInfo('About RustDesk', Icons.info_outline, Icons.info_sharp) _TabInfo('About', Icons.info_outline, Icons.info_sharp)
]; ];
late PageController controller; late PageController controller;
@ -714,7 +714,7 @@ class _AboutState extends State<_About> {
], ],
).marginOnly(left: _kContentHMargin) ).marginOnly(left: _kContentHMargin)
]), ]),
]).marginOnly(left: _kCardLeftMargin); ]);
}); });
} }
} }
@ -1038,52 +1038,117 @@ void changeServer() async {
var keyController = TextEditingController(text: key); var keyController = TextEditingController(text: key);
var isInProgress = false; var isInProgress = false;
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async {
setState(() {
[idServerMsg, relayServerMsg, apiServerMsg].forEach((element) {
element = "";
});
isInProgress = true;
});
cancel() {
setState(() {
isInProgress = false;
});
}
idServer = idController.text.trim();
relayServer = relayController.text.trim();
apiServer = apiController.text.trim().toLowerCase();
key = keyController.text.trim();
if (idServer.isNotEmpty) {
idServerMsg =
translate(await bind.mainTestIfValidServer(server: idServer));
if (idServerMsg.isEmpty) {
oldOptions['custom-rendezvous-server'] = idServer;
} else {
cancel();
return;
}
} else {
oldOptions['custom-rendezvous-server'] = "";
}
if (relayServer.isNotEmpty) {
relayServerMsg =
translate(await bind.mainTestIfValidServer(server: relayServer));
if (relayServerMsg.isEmpty) {
oldOptions['relay-server'] = relayServer;
} else {
cancel();
return;
}
} else {
oldOptions['relay-server'] = "";
}
if (apiServer.isNotEmpty) {
if (apiServer.startsWith('http://') ||
apiServer.startsWith("https://")) {
oldOptions['api-server'] = apiServer;
return;
} else {
apiServerMsg = translate("invalid_http");
cancel();
return;
}
} else {
oldOptions['api-server'] = "";
}
// ok
oldOptions['key'] = key;
await bind.mainSetOptions(json: jsonEncode(oldOptions));
close();
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("ID/Relay Server")), title: Text(translate("ID/Relay Server")),
content: ConstrainedBox( content: ConstrainedBox(
constraints: BoxConstraints(minWidth: 500), constraints: const BoxConstraints(minWidth: 500),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('ID Server')}:") child: Text("${translate('ID Server')}:")
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: idServerMsg.isNotEmpty ? idServerMsg : null), errorText: idServerMsg.isNotEmpty ? idServerMsg : null),
controller: idController, controller: idController,
focusNode: FocusNode()..requestFocus(),
), ),
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Relay Server')}:") child: Text("${translate('Relay Server')}:")
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: errorText:
relayServerMsg.isNotEmpty ? relayServerMsg : null), relayServerMsg.isNotEmpty ? relayServerMsg : null),
controller: relayController, controller: relayController,
@ -1091,22 +1156,22 @@ void changeServer() async {
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('API Server')}:") child: Text("${translate('API Server')}:")
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: errorText:
apiServerMsg.isNotEmpty ? apiServerMsg : null), apiServerMsg.isNotEmpty ? apiServerMsg : null),
controller: apiController, controller: apiController,
@ -1114,21 +1179,21 @@ void changeServer() async {
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: child:
Text("${translate('Key')}:").marginOnly(bottom: 16.0)), Text("${translate('Key')}:").marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
decoration: InputDecoration( decoration: const InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
controller: keyController, controller: keyController,
@ -1136,83 +1201,20 @@ void changeServer() async {
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 4.0, height: 4.0,
), ),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
], ],
), ),
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () { TextButton(onPressed: submit, child: Text(translate("OK"))),
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() {
[idServerMsg, relayServerMsg, apiServerMsg].forEach((element) {
element = "";
});
isInProgress = true;
});
final cancel = () {
setState(() {
isInProgress = false;
});
};
idServer = idController.text.trim();
relayServer = relayController.text.trim();
apiServer = apiController.text.trim().toLowerCase();
key = keyController.text.trim();
if (idServer.isNotEmpty) {
idServerMsg = translate(
await bind.mainTestIfValidServer(server: idServer));
if (idServerMsg.isEmpty) {
oldOptions['custom-rendezvous-server'] = idServer;
} else {
cancel();
return;
}
} else {
oldOptions['custom-rendezvous-server'] = "";
}
if (relayServer.isNotEmpty) {
relayServerMsg = translate(
await bind.mainTestIfValidServer(server: relayServer));
if (relayServerMsg.isEmpty) {
oldOptions['relay-server'] = relayServer;
} else {
cancel();
return;
}
} else {
oldOptions['relay-server'] = "";
}
if (apiServer.isNotEmpty) {
if (apiServer.startsWith('http://') ||
apiServer.startsWith("https://")) {
oldOptions['api-server'] = apiServer;
return;
} else {
apiServerMsg = translate("invalid_http");
cancel();
return;
}
} else {
oldOptions['api-server'] = "";
}
// ok
oldOptions['key'] = key;
await bind.mainSetOptions(json: jsonEncode(oldOptions));
close();
},
child: Text(translate("OK"))),
], ],
onSubmit: submit,
onCancel: close,
); );
}); });
} }
@ -1231,27 +1233,28 @@ void changeWhiteList() async {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(translate("whitelist_sep")), Text(translate("whitelist_sep")),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
maxLines: null, maxLines: null,
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg), errorText: msg.isEmpty ? null : translate(msg),
), ),
controller: controller, controller: controller,
), focusNode: FocusNode()..requestFocus()),
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 4.0, height: 4.0,
), ),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
], ],
), ),
actions: [ actions: [
@ -1277,7 +1280,7 @@ void changeWhiteList() async {
final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$"); final ipMatch = RegExp(r"^\d+\.\d+\.\d+\.\d+$");
for (final ip in ips) { for (final ip in ips) {
if (!ipMatch.hasMatch(ip)) { if (!ipMatch.hasMatch(ip)) {
msg = translate("Invalid IP") + " $ip"; msg = "${translate("Invalid IP")} $ip";
setState(() { setState(() {
isInProgress = false; isInProgress = false;
}); });
@ -1292,6 +1295,7 @@ void changeWhiteList() async {
}, },
child: Text(translate("OK"))), child: Text(translate("OK"))),
], ],
onCancel: close,
); );
}); });
} }
@ -1314,50 +1318,80 @@ void changeSocks5Proxy() async {
var isInProgress = false; var isInProgress = false;
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async {
setState(() {
proxyMsg = "";
isInProgress = true;
});
cancel() {
setState(() {
isInProgress = false;
});
}
proxy = proxyController.text.trim();
username = userController.text.trim();
password = pwdController.text.trim();
if (proxy.isNotEmpty) {
proxyMsg = translate(await bind.mainTestIfValidServer(server: proxy));
if (proxyMsg.isEmpty) {
// ignore
} else {
cancel();
return;
}
}
await bind.mainSetSocks(
proxy: proxy, username: username, password: password);
close();
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("Socks5 Proxy")), title: Text(translate("Socks5 Proxy")),
content: ConstrainedBox( content: ConstrainedBox(
constraints: BoxConstraints(minWidth: 500), constraints: const BoxConstraints(minWidth: 500),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Hostname')}:") child: Text("${translate('Hostname')}:")
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: proxyMsg.isNotEmpty ? proxyMsg : null), errorText: proxyMsg.isNotEmpty ? proxyMsg : null),
controller: proxyController, controller: proxyController,
focusNode: FocusNode()..requestFocus(),
), ),
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Username')}:") child: Text("${translate('Username')}:")
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
decoration: InputDecoration( decoration: const InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
controller: userController, controller: userController,
@ -1365,21 +1399,21 @@ void changeSocks5Proxy() async {
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:") child: Text("${translate('Password')}:")
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
decoration: InputDecoration( decoration: const InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
controller: pwdController, controller: pwdController,
@ -1387,50 +1421,20 @@ void changeSocks5Proxy() async {
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
], ],
), ),
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () { TextButton(onPressed: submit, child: Text(translate("OK"))),
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() {
proxyMsg = "";
isInProgress = true;
});
final cancel = () {
setState(() {
isInProgress = false;
});
};
proxy = proxyController.text.trim();
username = userController.text.trim();
password = pwdController.text.trim();
if (proxy.isNotEmpty) {
proxyMsg =
translate(await bind.mainTestIfValidServer(server: proxy));
if (proxyMsg.isEmpty) {
// ignore
} else {
cancel();
return;
}
}
await bind.mainSetSocks(
proxy: proxy, username: username, password: password);
close();
},
child: Text(translate("OK"))),
], ],
onSubmit: submit,
onCancel: close,
); );
}); });
} }

View File

@ -37,25 +37,27 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
RxBool fullscreen = false.obs; RxBool fullscreen = false.obs;
Get.put(fullscreen, tag: 'fullscreen'); Get.put(fullscreen, tag: 'fullscreen');
return Obx(() => DragToResizeArea( return Obx(() => DragToResizeArea(
resizeEdgeSize: fullscreen.value ? 1.0 : 8.0, resizeEdgeSize: fullscreen.value ? 1.0 : 8.0,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: MyTheme.color(context).border!)), border: Border.all(color: MyTheme.color(context).border!)),
child: Scaffold( child: Overlay(initialEntries: [
backgroundColor: MyTheme.color(context).bg, OverlayEntry(builder: (context) {
body: DesktopTab( gFFI.dialogManager.setOverlayState(Overlay.of(context));
controller: tabController, return Scaffold(
theme: dark ? TarBarTheme.dark() : TarBarTheme.light(), backgroundColor: MyTheme.color(context).bg,
tail: ActionIcon( body: DesktopTab(
message: 'Settings', controller: tabController,
icon: IconFont.menu, tail: ActionIcon(
theme: dark ? TarBarTheme.dark() : TarBarTheme.light(), message: 'Settings',
onTap: onAddSetting, icon: IconFont.menu,
is_close: false, onTap: onAddSetting,
), isClose: false,
)), ),
), ));
)); })
]),
)));
} }
void onAddSetting() { void onAddSetting() {

View File

@ -642,47 +642,51 @@ class _FileManagerPageState extends State<FileManagerPage>
IconButton( IconButton(
onPressed: () { onPressed: () {
final name = TextEditingController(); final name = TextEditingController();
_ffi.dialogManager _ffi.dialogManager.show((setState, close) {
.show((setState, close) => CustomAlertDialog( submit() {
title: Text(translate("Create Folder")), if (name.value.text.isNotEmpty) {
content: Column( model.createDir(
mainAxisSize: MainAxisSize.min, PathUtil.join(
children: [ model.getCurrentDir(isLocal).path,
TextFormField( name.value.text,
decoration: InputDecoration( model.getCurrentIsWindows(isLocal)),
labelText: translate( isLocal: isLocal);
"Please enter the folder name"), close();
), }
controller: name, }
),
], cancel() => close(false);
), return CustomAlertDialog(
actions: [ title: Text(translate("Create Folder")),
TextButton( content: Column(
style: flatButtonStyle, mainAxisSize: MainAxisSize.min,
onPressed: () => close(false), children: [
child: Text(translate("Cancel"))), TextFormField(
ElevatedButton( decoration: InputDecoration(
style: flatButtonStyle, labelText: translate(
onPressed: () { "Please enter the folder name"),
if (name.value.text.isNotEmpty) { ),
model.createDir( controller: name,
PathUtil.join( focusNode: FocusNode()..requestFocus(),
model ),
.getCurrentDir( ],
isLocal) ),
.path, actions: [
name.value.text, TextButton(
model.getCurrentIsWindows( style: flatButtonStyle,
isLocal)), onPressed: cancel,
isLocal: isLocal); child: Text(translate("Cancel"))),
close(); ElevatedButton(
} style: flatButtonStyle,
}, onPressed: submit,
child: Text(translate("OK"))) child: Text(translate("OK")))
])); ],
onSubmit: submit,
onCancel: cancel,
);
});
}, },
icon: Icon(Icons.create_new_folder_outlined)), icon: const Icon(Icons.create_new_folder_outlined)),
IconButton( IconButton(
onPressed: () async { onPressed: () async {
final items = isLocal final items = isLocal

View File

@ -62,8 +62,6 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme =
isDarkTheme() ? const TarBarTheme.dark() : const TarBarTheme.light();
return SubWindowDragToResizeArea( return SubWindowDragToResizeArea(
windowId: windowId(), windowId: windowId(),
child: Container( child: Container(
@ -73,13 +71,10 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
backgroundColor: MyTheme.color(context).bg, backgroundColor: MyTheme.color(context).bg,
body: DesktopTab( body: DesktopTab(
controller: tabController, controller: tabController,
theme: theme,
onClose: () { onClose: () {
tabController.clear(); tabController.clear();
}, },
tail: AddButton( tail: AddButton().paddingOnly(left: 10),
theme: theme,
).paddingOnly(left: 10),
)), )),
), ),
); );

View File

@ -70,38 +70,45 @@ class _PortForwardPageState extends State<PortForwardPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Scaffold( return Overlay(initialEntries: [
backgroundColor: MyTheme.color(context).grayBg, OverlayEntry(builder: (context) {
body: FutureBuilder(future: () async { _ffi.dialogManager.setOverlayState(Overlay.of(context));
if (!isRdp) { return Scaffold(
refreshTunnelConfig(); backgroundColor: MyTheme.color(context).grayBg,
} body: FutureBuilder(future: () async {
}(), builder: (context, snapshot) { if (!isRdp) {
if (snapshot.connectionState == ConnectionState.done) { refreshTunnelConfig();
return Container( }
decoration: BoxDecoration( }(), builder: (context, snapshot) {
border: Border.all( if (snapshot.connectionState == ConnectionState.done) {
width: 20, color: MyTheme.color(context).grayBg!)), return Container(
child: Column( decoration: BoxDecoration(
crossAxisAlignment: CrossAxisAlignment.stretch, border: Border.all(
children: [ width: 20, color: MyTheme.color(context).grayBg!)),
buildPrompt(context), child: Column(
Flexible( crossAxisAlignment: CrossAxisAlignment.stretch,
child: Container( children: [
decoration: BoxDecoration( buildPrompt(context),
color: MyTheme.color(context).bg, Flexible(
border: Border.all(width: 1, color: MyTheme.border)), child: Container(
child: decoration: BoxDecoration(
widget.isRDP ? buildRdp(context) : buildTunnel(context), color: MyTheme.color(context).bg,
), border:
Border.all(width: 1, color: MyTheme.border)),
child: widget.isRDP
? buildRdp(context)
: buildTunnel(context),
),
),
],
), ),
], );
), }
); return const Offstage();
} }),
return const Offstage(); );
}), })
); ]);
} }
buildPrompt(BuildContext context) { buildPrompt(BuildContext context) {

View File

@ -69,7 +69,6 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = isDarkTheme() ? TarBarTheme.dark() : TarBarTheme.light();
return SubWindowDragToResizeArea( return SubWindowDragToResizeArea(
windowId: windowId(), windowId: windowId(),
child: Container( child: Container(
@ -79,13 +78,10 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
backgroundColor: MyTheme.color(context).bg, backgroundColor: MyTheme.color(context).bg,
body: DesktopTab( body: DesktopTab(
controller: tabController, controller: tabController,
theme: theme,
onClose: () { onClose: () {
tabController.clear(); tabController.clear();
}, },
tail: AddButton( tail: AddButton().paddingOnly(left: 10),
theme: theme,
).paddingOnly(left: 10),
)), )),
), ),
); );

View File

@ -118,7 +118,6 @@ class ConnectionManagerState extends State<ConnectionManager> {
], ],
) )
: DesktopTab( : DesktopTab(
theme: isDarkTheme() ? TarBarTheme.dark() : TarBarTheme.light(),
showTitle: false, showTitle: false,
showMaximize: false, showMaximize: false,
showMinimize: true, showMinimize: true,

View File

@ -5,6 +5,7 @@ import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../common.dart'; import '../../common.dart';
import '../../common/formatter/id_formatter.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/peer_model.dart'; import '../../models/peer_model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
@ -47,7 +48,7 @@ class _PeerCard extends StatefulWidget {
class _PeerCardState extends State<_PeerCard> class _PeerCardState extends State<_PeerCard>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
var _menuPos = RelativeRect.fill; var _menuPos = RelativeRect.fill;
final double _cardRadis = 20; final double _cardRadis = 16;
final double _borderWidth = 2; final double _borderWidth = 2;
final RxBool _iconMoreHover = false.obs; final RxBool _iconMoreHover = false.obs;
@ -119,7 +120,7 @@ class _PeerCardState extends State<_PeerCard>
? Colors.green ? Colors.green
: Colors.yellow)), : Colors.yellow)),
Text( Text(
'${peer.id}', formatID('${peer.id}'),
style: TextStyle(fontWeight: FontWeight.w400), style: TextStyle(fontWeight: FontWeight.w400),
), ),
]), ]),
@ -240,7 +241,7 @@ class _PeerCardState extends State<_PeerCard>
backgroundColor: peer.online backgroundColor: peer.online
? Colors.green ? Colors.green
: Colors.yellow)), : Colors.yellow)),
Text(peer.id) Text(formatID(peer.id))
]).paddingSymmetric(vertical: 8), ]).paddingSymmetric(vertical: 8),
_actionMore(peer), _actionMore(peer),
], ],
@ -562,47 +563,47 @@ abstract class BasePeerCard extends StatelessWidget {
} }
} }
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async {
isInProgress.value = true;
name = controller.text;
await bind.mainSetPeerOption(id: id, key: 'alias', value: name);
if (isAddressBook) {
gFFI.abModel.setPeerOption(id, 'alias', name);
await gFFI.abModel.updateAb();
}
alias.value = await bind.mainGetPeerOption(id: peer.id, key: 'alias');
close();
isInProgress.value = false;
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate('Rename')), title: Text(translate('Rename')),
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Form( child: Form(
child: TextFormField( child: TextFormField(
controller: controller, controller: controller,
decoration: InputDecoration(border: OutlineInputBorder()), focusNode: FocusNode()..requestFocus(),
decoration:
const InputDecoration(border: OutlineInputBorder()),
), ),
), ),
), ),
Obx(() => Offstage( Obx(() => Offstage(
offstage: isInProgress.isFalse, offstage: isInProgress.isFalse,
child: LinearProgressIndicator())), child: const LinearProgressIndicator())),
], ],
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () { TextButton(onPressed: submit, child: Text(translate("OK"))),
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
isInProgress.value = true;
name = controller.text;
await bind.mainSetPeerOption(id: id, key: 'alias', value: name);
if (isAddressBook) {
gFFI.abModel.setPeerOption(id, 'alias', name);
await gFFI.abModel.updateAb();
}
alias.value =
await bind.mainGetPeerOption(id: peer.id, key: 'alias');
close();
isInProgress.value = false;
},
child: Text(translate("OK"))),
], ],
onSubmit: submit,
onCancel: close,
); );
}); });
} }
@ -749,13 +750,23 @@ class AddressBookPeerCard extends BasePeerCard {
var selectedTag = gFFI.abModel.getPeerTags(id).obs; var selectedTag = gFFI.abModel.getPeerTags(id).obs;
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async {
setState(() {
isInProgress = true;
});
gFFI.abModel.changeTagForPeer(id, selectedTag);
await gFFI.abModel.updateAb();
close();
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate("Edit Tag")), title: Text(translate("Edit Tag")),
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Wrap( child: Wrap(
children: tags children: tags
.map((e) => _buildTag(e, selectedTag, onTap: () { .map((e) => _buildTag(e, selectedTag, onTap: () {
@ -768,26 +779,16 @@ class AddressBookPeerCard extends BasePeerCard {
.toList(growable: false), .toList(growable: false),
), ),
), ),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator()) Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
], ],
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () { TextButton(onPressed: submit, child: Text(translate("OK"))),
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() {
isInProgress = true;
});
gFFI.abModel.changeTagForPeer(id, selectedTag);
await gFFI.abModel.updateAb();
close();
},
child: Text(translate("OK"))),
], ],
onSubmit: submit,
onCancel: close,
); );
}); });
} }
@ -870,25 +871,35 @@ void _rdpDialog(String id) async {
RxBool secure = true.obs; RxBool secure = true.obs;
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
submit() async {
await bind.mainSetPeerOption(
id: id, key: 'rdp_port', value: portController.text.trim());
await bind.mainSetPeerOption(
id: id, key: 'rdp_username', value: userController.text);
await bind.mainSetPeerOption(
id: id, key: 'rdp_password', value: passwordContorller.text);
close();
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text('RDP ' + translate('Settings')), title: Text('RDP ${translate('Settings')}'),
content: ConstrainedBox( content: ConstrainedBox(
constraints: BoxConstraints(minWidth: 500), constraints: const BoxConstraints(minWidth: 500),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text( child: Text(
"${translate('Port')}:", "${translate('Port')}:",
textAlign: TextAlign.start, textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)), ).marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
@ -897,52 +908,54 @@ void _rdpDialog(String id) async {
FilteringTextInputFormatter.allow(RegExp( 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])$')) 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: InputDecoration( decoration: const InputDecoration(
border: OutlineInputBorder(), hintText: '3389'), border: OutlineInputBorder(), hintText: '3389'),
controller: portController, controller: portController,
focusNode: FocusNode()..requestFocus(),
), ),
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text( child: Text(
"${translate('Username')}:", "${translate('Username')}:",
textAlign: TextAlign.start, textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)), ).marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: TextField( child: TextField(
decoration: InputDecoration(border: OutlineInputBorder()), decoration:
const InputDecoration(border: OutlineInputBorder()),
controller: userController, controller: userController,
), ),
), ),
], ],
), ),
SizedBox( const SizedBox(
height: 8.0, height: 8.0,
), ),
Row( Row(
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints(minWidth: 100), constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:") child: Text("${translate('Password')}:")
.marginOnly(bottom: 16.0)), .marginOnly(bottom: 16.0)),
SizedBox( const SizedBox(
width: 24.0, width: 24.0,
), ),
Expanded( Expanded(
child: Obx(() => TextField( child: Obx(() => TextField(
obscureText: secure.value, obscureText: secure.value,
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
suffixIcon: IconButton( suffixIcon: IconButton(
onPressed: () => secure.value = !secure.value, onPressed: () => secure.value = !secure.value,
icon: Icon(secure.value icon: Icon(secure.value
@ -957,23 +970,11 @@ void _rdpDialog(String id) async {
), ),
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () { TextButton(onPressed: submit, child: Text(translate("OK"))),
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
await bind.mainSetPeerOption(
id: id, key: 'rdp_port', value: portController.text.trim());
await bind.mainSetPeerOption(
id: id, key: 'rdp_username', value: userController.text);
await bind.mainSetPeerOption(
id: id, key: 'rdp_password', value: passwordContorller.text);
close();
},
child: Text(translate("OK"))),
], ],
onSubmit: submit,
onCancel: close,
); );
}); });
} }

View File

@ -1,6 +1,7 @@
import 'dart:core'; import 'dart:core';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import './material_mod_popup_menu.dart' as mod_menu; import './material_mod_popup_menu.dart' as mod_menu;
@ -174,8 +175,8 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
children: [ children: [
Text( Text(
opt.text, opt.text,
style: const TextStyle( style: TextStyle(
color: Colors.black, color: MyTheme.color(context).text,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal), fontWeight: FontWeight.normal),
), ),
@ -256,8 +257,8 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
children: [ children: [
Text( Text(
opt.text, opt.text,
style: const TextStyle( style: TextStyle(
color: Colors.black, color: MyTheme.color(context).text,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal), fontWeight: FontWeight.normal),
), ),
@ -300,8 +301,8 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
const SizedBox(width: MenuConfig.midPadding), const SizedBox(width: MenuConfig.midPadding),
Text( Text(
text, text,
style: const TextStyle( style: TextStyle(
color: Colors.black, color: MyTheme.color(context).text,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal), fontWeight: FontWeight.normal),
), ),
@ -346,8 +347,8 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
// const SizedBox(width: MenuConfig.midPadding), // const SizedBox(width: MenuConfig.midPadding),
Text( Text(
text, text,
style: const TextStyle( style: TextStyle(
color: Colors.black, color: MyTheme.color(context).text,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal), fontWeight: FontWeight.normal),
), ),
@ -450,8 +451,8 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
const SizedBox(width: MenuConfig.midPadding), const SizedBox(width: MenuConfig.midPadding),
Text( Text(
text, text,
style: const TextStyle( style: TextStyle(
color: Colors.black, color: MyTheme.color(context).text,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal), fontWeight: FontWeight.normal),
), ),
@ -491,8 +492,8 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height), constraints: BoxConstraints(minHeight: conf.height),
child: childBuilder( child: childBuilder(
const TextStyle( TextStyle(
color: Colors.black, color: MyTheme.color(context).text,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal), fontWeight: FontWeight.normal),
)), )),

View File

@ -596,46 +596,49 @@ void showSetOSPassword(
var autoLogin = await bind.sessionGetOption(id: id, arg: "auto-login") != ""; var autoLogin = await bind.sessionGetOption(id: id, arg: "auto-login") != "";
controller.text = password; controller.text = password;
dialogManager.show((setState, close) { dialogManager.show((setState, close) {
submit() {
var text = controller.text.trim();
bind.sessionPeerOption(id: id, name: "os-password", value: text);
bind.sessionPeerOption(
id: id, name: "auto-login", value: autoLogin ? 'Y' : '');
if (text != "" && login) {
bind.sessionInputOsPassword(id: id, value: text);
}
close();
}
return CustomAlertDialog( return CustomAlertDialog(
title: Text(translate('OS Password')), title: Text(translate('OS Password')),
content: Column(mainAxisSize: MainAxisSize.min, children: [ content: Column(mainAxisSize: MainAxisSize.min, children: [
PasswordWidget(controller: controller), PasswordWidget(controller: controller),
CheckboxListTile( CheckboxListTile(
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
dense: true, dense: true,
controlAffinity: ListTileControlAffinity.leading, controlAffinity: ListTileControlAffinity.leading,
title: Text( title: Text(
translate('Auto Login'), translate('Auto Login'),
),
value: autoLogin,
onChanged: (v) {
if (v == null) return;
setState(() => autoLogin = v);
},
), ),
]), value: autoLogin,
actions: [ onChanged: (v) {
TextButton( if (v == null) return;
style: flatButtonStyle, setState(() => autoLogin = v);
onPressed: () { },
close(); ),
}, ]),
child: Text(translate('Cancel')), actions: [
), TextButton(
TextButton( style: flatButtonStyle,
style: flatButtonStyle, onPressed: close,
onPressed: () { child: Text(translate('Cancel')),
var text = controller.text.trim(); ),
bind.sessionPeerOption(id: id, name: "os-password", value: text); TextButton(
bind.sessionPeerOption( style: flatButtonStyle,
id: id, name: "auto-login", value: autoLogin ? 'Y' : ''); onPressed: submit,
if (text != "" && login) { child: Text(translate('OK')),
bind.sessionInputOsPassword(id: id, value: text); ),
} ],
close(); onSubmit: submit,
}, onCancel: close,
child: Text(translate('OK')), );
),
]);
}); });
} }

View File

@ -3,7 +3,7 @@ import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' hide TabBarTheme;
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/main.dart';
@ -158,8 +158,7 @@ class DesktopTabController {
class TabThemeConf { class TabThemeConf {
double iconSize; double iconSize;
TarBarTheme theme; TabThemeConf({required this.iconSize});
TabThemeConf({required this.iconSize, required this.theme});
} }
typedef TabBuilder = Widget Function( typedef TabBuilder = Widget Function(
@ -168,7 +167,6 @@ typedef LabelGetter = Rx<String> Function(String key);
class DesktopTab extends StatelessWidget { class DesktopTab extends StatelessWidget {
final Function(String)? onTabClose; final Function(String)? onTabClose;
final TarBarTheme theme;
final bool showTabBar; final bool showTabBar;
final bool showLogo; final bool showLogo;
final bool showTitle; final bool showTitle;
@ -189,7 +187,6 @@ class DesktopTab extends StatelessWidget {
DesktopTab({ DesktopTab({
Key? key, Key? key,
required this.controller, required this.controller,
this.theme = const TarBarTheme.light(),
this.onTabClose, this.onTabClose,
this.showTabBar = true, this.showTabBar = true,
this.showLogo = true, this.showLogo = true,
@ -213,15 +210,15 @@ class DesktopTab extends StatelessWidget {
return Column(children: [ return Column(children: [
Offstage( Offstage(
offstage: !showTabBar, offstage: !showTabBar,
child: Container( child: SizedBox(
height: _kTabBarHeight, height: _kTabBarHeight,
child: Column( child: Column(
children: [ children: [
Container( SizedBox(
height: _kTabBarHeight - 1, height: _kTabBarHeight - 1,
child: _buildBar(), child: _buildBar(),
), ),
Divider( const Divider(
height: 1, height: 1,
thickness: 1, thickness: 1,
), ),
@ -300,7 +297,7 @@ class DesktopTab extends StatelessWidget {
)), )),
Offstage( Offstage(
offstage: !showTitle, offstage: !showTitle,
child: Text( child: const Text(
"RustDesk", "RustDesk",
style: TextStyle(fontSize: 13), style: TextStyle(fontSize: 13),
).marginOnly(left: 2)) ).marginOnly(left: 2))
@ -321,7 +318,6 @@ class DesktopTab extends StatelessWidget {
child: _ListView( child: _ListView(
controller: controller, controller: controller,
onTabClose: onTabClose, onTabClose: onTabClose,
theme: theme,
tabBuilder: tabBuilder, tabBuilder: tabBuilder,
labelGetter: labelGetter, labelGetter: labelGetter,
)), )),
@ -334,7 +330,6 @@ class DesktopTab extends StatelessWidget {
mainTab: isMainWindow, mainTab: isMainWindow,
tabType: tabType, tabType: tabType,
state: state, state: state,
theme: theme,
showMinimize: showMinimize, showMinimize: showMinimize,
showMaximize: showMaximize, showMaximize: showMaximize,
showClose: showClose, showClose: showClose,
@ -349,7 +344,6 @@ class WindowActionPanel extends StatelessWidget {
final bool mainTab; final bool mainTab;
final DesktopTabType tabType; final DesktopTabType tabType;
final Rx<DesktopTabState> state; final Rx<DesktopTabState> state;
final TarBarTheme theme;
final bool showMinimize; final bool showMinimize;
final bool showMaximize; final bool showMaximize;
@ -361,7 +355,6 @@ class WindowActionPanel extends StatelessWidget {
required this.mainTab, required this.mainTab,
required this.tabType, required this.tabType,
required this.state, required this.state,
required this.theme,
this.showMinimize = true, this.showMinimize = true,
this.showMaximize = true, this.showMaximize = true,
this.showClose = true, this.showClose = true,
@ -377,7 +370,6 @@ class WindowActionPanel extends StatelessWidget {
child: ActionIcon( child: ActionIcon(
message: 'Minimize', message: 'Minimize',
icon: IconFont.min, icon: IconFont.min,
theme: theme,
onTap: () { onTap: () {
if (mainTab) { if (mainTab) {
windowManager.minimize(); windowManager.minimize();
@ -385,31 +377,30 @@ class WindowActionPanel extends StatelessWidget {
WindowController.fromWindowId(windowId!).minimize(); WindowController.fromWindowId(windowId!).minimize();
} }
}, },
is_close: false, isClose: false,
)), )),
// TODO: drag makes window restore // TODO: drag makes window restore
Offstage( Offstage(
offstage: !showMaximize, offstage: !showMaximize,
child: FutureBuilder(builder: (context, snapshot) { child: FutureBuilder(builder: (context, snapshot) {
RxBool is_maximized = false.obs; RxBool isMaximized = false.obs;
if (mainTab) { if (mainTab) {
windowManager.isMaximized().then((maximized) { windowManager.isMaximized().then((maximized) {
is_maximized.value = maximized; isMaximized.value = maximized;
}); });
} else { } else {
final wc = WindowController.fromWindowId(windowId!); final wc = WindowController.fromWindowId(windowId!);
wc.isMaximized().then((maximized) { wc.isMaximized().then((maximized) {
is_maximized.value = maximized; isMaximized.value = maximized;
}); });
} }
return Obx( return Obx(
() => ActionIcon( () => ActionIcon(
message: is_maximized.value ? "Restore" : "Maximize", message: isMaximized.value ? "Restore" : "Maximize",
icon: is_maximized.value ? IconFont.restore : IconFont.max, icon: isMaximized.value ? IconFont.restore : IconFont.max,
theme: theme,
onTap: () { onTap: () {
if (mainTab) { if (mainTab) {
if (is_maximized.value) { if (isMaximized.value) {
windowManager.unmaximize(); windowManager.unmaximize();
} else { } else {
windowManager.maximize(); windowManager.maximize();
@ -417,15 +408,15 @@ class WindowActionPanel extends StatelessWidget {
} else { } else {
// TODO: subwindow is maximized but first query result is not maximized. // TODO: subwindow is maximized but first query result is not maximized.
final wc = WindowController.fromWindowId(windowId!); final wc = WindowController.fromWindowId(windowId!);
if (is_maximized.value) { if (isMaximized.value) {
wc.unmaximize(); wc.unmaximize();
} else { } else {
wc.maximize(); wc.maximize();
} }
} }
is_maximized.value = !is_maximized.value; isMaximized.value = !isMaximized.value;
}, },
is_close: false, isClose: false,
), ),
); );
})), })),
@ -434,7 +425,6 @@ class WindowActionPanel extends StatelessWidget {
child: ActionIcon( child: ActionIcon(
message: 'Close', message: 'Close',
icon: IconFont.close, icon: IconFont.close,
theme: theme,
onTap: () async { onTap: () async {
action() { action() {
if (mainTab) { if (mainTab) {
@ -455,29 +445,31 @@ class WindowActionPanel extends StatelessWidget {
action(); action();
} }
}, },
is_close: true, isClose: true,
)), )),
], ],
); );
} }
closeConfirmDialog(Function() callback) async { closeConfirmDialog(Function() callback) async {
final res = await gFFI.dialogManager final res = await gFFI.dialogManager.show<bool>((setState, close) {
.show<bool>((setState, close) => CustomAlertDialog( submit() => close(true);
title: Row(children: [ return CustomAlertDialog(
Icon(Icons.warning_amber_sharp, title: Row(children: [
color: Colors.redAccent, size: 28), const Icon(Icons.warning_amber_sharp,
SizedBox(width: 10), color: Colors.redAccent, size: 28),
Text(translate("Warning")), const SizedBox(width: 10),
]), Text(translate("Warning")),
content: Text(translate("Disconnect all devices?")), ]),
actions: [ content: Text(translate("Disconnect all devices?")),
TextButton( actions: [
onPressed: () => close(), child: Text(translate("Cancel"))), TextButton(onPressed: close, child: Text(translate("Cancel"))),
ElevatedButton( ElevatedButton(onPressed: submit, child: Text(translate("OK"))),
onPressed: () => close(true), child: Text(translate("OK"))), ],
], onSubmit: submit,
)); onCancel: close,
);
});
if (res == true) { if (res == true) {
callback(); callback();
} }
@ -488,17 +480,15 @@ class WindowActionPanel extends StatelessWidget {
class _ListView extends StatelessWidget { class _ListView extends StatelessWidget {
final DesktopTabController controller; final DesktopTabController controller;
final Function(String key)? onTabClose; final Function(String key)? onTabClose;
final TarBarTheme theme;
final TabBuilder? tabBuilder; final TabBuilder? tabBuilder;
final LabelGetter? labelGetter; final LabelGetter? labelGetter;
Rx<DesktopTabState> get state => controller.state; Rx<DesktopTabState> get state => controller.state;
_ListView( const _ListView(
{required this.controller, {required this.controller,
required this.onTabClose, required this.onTabClose,
required this.theme,
this.tabBuilder, this.tabBuilder,
this.labelGetter}); this.labelGetter});
@ -508,7 +498,7 @@ class _ListView extends StatelessWidget {
controller: state.value.scrollController, controller: state.value.scrollController,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
shrinkWrap: true, shrinkWrap: true,
physics: BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
children: state.value.tabs.asMap().entries.map((e) { children: state.value.tabs.asMap().entries.map((e) {
final index = e.key; final index = e.key;
final tab = e.value; final tab = e.value;
@ -523,7 +513,6 @@ class _ListView extends StatelessWidget {
selected: state.value.selected, selected: state.value.selected,
onClose: () => controller.remove(index), onClose: () => controller.remove(index),
onSelected: () => controller.jumpTo(index), onSelected: () => controller.jumpTo(index),
theme: theme,
tabBuilder: tabBuilder == null tabBuilder: tabBuilder == null
? null ? null
: (Widget icon, Widget labelWidget, TabThemeConf themeConf) { : (Widget icon, Widget labelWidget, TabThemeConf themeConf) {
@ -540,31 +529,29 @@ class _ListView extends StatelessWidget {
} }
class _Tab extends StatefulWidget { class _Tab extends StatefulWidget {
late final int index; final int index;
late final Rx<String> label; final Rx<String> label;
late final IconData? selectedIcon; final IconData? selectedIcon;
late final IconData? unselectedIcon; final IconData? unselectedIcon;
late final bool closable; final bool closable;
late final int selected; final int selected;
late final Function() onClose; final Function() onClose;
late final Function() onSelected; final Function() onSelected;
late final TarBarTheme theme;
final Widget Function(Widget icon, Widget label, TabThemeConf themeConf)? final Widget Function(Widget icon, Widget label, TabThemeConf themeConf)?
tabBuilder; tabBuilder;
_Tab( const _Tab({
{Key? key, Key? key,
required this.index, required this.index,
required this.label, required this.label,
this.selectedIcon, this.selectedIcon,
this.unselectedIcon, this.unselectedIcon,
this.tabBuilder, this.tabBuilder,
required this.closable, required this.closable,
required this.selected, required this.selected,
required this.onClose, required this.onClose,
required this.onSelected, required this.onSelected,
required this.theme}) }) : super(key: key);
: super(key: key);
@override @override
State<_Tab> createState() => _TabState(); State<_Tab> createState() => _TabState();
@ -584,8 +571,8 @@ class _TabState extends State<_Tab> with RestorationMixin {
isSelected ? widget.selectedIcon : widget.unselectedIcon, isSelected ? widget.selectedIcon : widget.unselectedIcon,
size: _kIconSize, size: _kIconSize,
color: isSelected color: isSelected
? widget.theme.selectedtabIconColor ? MyTheme.tabbar(context).selectedTabIconColor
: widget.theme.unSelectedtabIconColor, : MyTheme.tabbar(context).unSelectedTabIconColor,
).paddingOnly(right: 5)); ).paddingOnly(right: 5));
final labelWidget = Obx(() { final labelWidget = Obx(() {
return Text( return Text(
@ -593,8 +580,8 @@ class _TabState extends State<_Tab> with RestorationMixin {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: isSelected color: isSelected
? widget.theme.selectedTextColor ? MyTheme.tabbar(context).selectedTextColor
: widget.theme.unSelectedTextColor), : MyTheme.tabbar(context).unSelectedTextColor),
); );
}); });
@ -607,8 +594,8 @@ class _TabState extends State<_Tab> with RestorationMixin {
], ],
); );
} else { } else {
return widget.tabBuilder!(icon, labelWidget, return widget.tabBuilder!(
TabThemeConf(iconSize: _kIconSize, theme: widget.theme)); icon, labelWidget, TabThemeConf(iconSize: _kIconSize));
} }
} }
@ -637,7 +624,6 @@ class _TabState extends State<_Tab> with RestorationMixin {
visiable: hover.value && widget.closable, visiable: hover.value && widget.closable,
tabSelected: isSelected, tabSelected: isSelected,
onClose: () => widget.onClose(), onClose: () => widget.onClose(),
theme: widget.theme,
))) )))
])).paddingSymmetric(horizontal: 10), ])).paddingSymmetric(horizontal: 10),
Offstage( Offstage(
@ -646,7 +632,7 @@ class _TabState extends State<_Tab> with RestorationMixin {
width: 1, width: 1,
indent: _kDividerIndent, indent: _kDividerIndent,
endIndent: _kDividerIndent, endIndent: _kDividerIndent,
color: widget.theme.dividerColor, color: MyTheme.tabbar(context).dividerColor,
thickness: 1, thickness: 1,
), ),
) )
@ -669,14 +655,12 @@ class _CloseButton extends StatelessWidget {
final bool visiable; final bool visiable;
final bool tabSelected; final bool tabSelected;
final Function onClose; final Function onClose;
late final TarBarTheme theme;
_CloseButton({ const _CloseButton({
Key? key, Key? key,
required this.visiable, required this.visiable,
required this.tabSelected, required this.tabSelected,
required this.onClose, required this.onClose,
required this.theme,
}) : super(key: key); }) : super(key: key);
@override @override
@ -692,8 +676,8 @@ class _CloseButton extends StatelessWidget {
Icons.close, Icons.close,
size: _kIconSize, size: _kIconSize,
color: tabSelected color: tabSelected
? theme.selectedIconColor ? MyTheme.tabbar(context).selectedIconColor
: theme.unSelectedIconColor, : MyTheme.tabbar(context).unSelectedIconColor,
), ),
), ),
)).paddingOnly(left: 5); )).paddingOnly(left: 5);
@ -703,16 +687,14 @@ class _CloseButton extends StatelessWidget {
class ActionIcon extends StatelessWidget { class ActionIcon extends StatelessWidget {
final String message; final String message;
final IconData icon; final IconData icon;
final TarBarTheme theme;
final Function() onTap; final Function() onTap;
final bool is_close; final bool isClose;
const ActionIcon({ const ActionIcon({
Key? key, Key? key,
required this.message, required this.message,
required this.icon, required this.icon,
required this.theme,
required this.onTap, required this.onTap,
required this.is_close, required this.isClose,
}) : super(key: key); }) : super(key: key);
@override @override
@ -720,34 +702,32 @@ class ActionIcon extends StatelessWidget {
RxBool hover = false.obs; RxBool hover = false.obs;
return Obx(() => Tooltip( return Obx(() => Tooltip(
message: translate(message), message: translate(message),
waitDuration: Duration(seconds: 1), waitDuration: const Duration(seconds: 1),
child: InkWell( child: InkWell(
hoverColor: hoverColor: isClose
is_close ? Color.fromARGB(255, 196, 43, 28) : theme.hoverColor, ? const Color.fromARGB(255, 196, 43, 28)
: MyTheme.tabbar(context).hoverColor,
onHover: (value) => hover.value = value, onHover: (value) => hover.value = value,
child: Container( onTap: onTap,
child: SizedBox(
height: _kTabBarHeight - 1, height: _kTabBarHeight - 1,
width: _kTabBarHeight - 1, width: _kTabBarHeight - 1,
child: Icon( child: Icon(
icon, icon,
color: hover.value && is_close color: hover.value && isClose
? Colors.white ? Colors.white
: theme.unSelectedIconColor, : MyTheme.tabbar(context).unSelectedIconColor,
size: _kActionIconSize, size: _kActionIconSize,
), ),
), ),
onTap: onTap,
), ),
)); ));
} }
} }
class AddButton extends StatelessWidget { class AddButton extends StatelessWidget {
late final TarBarTheme theme; const AddButton({
AddButton({
Key? key, Key? key,
required this.theme,
}) : super(key: key); }) : super(key: key);
@override @override
@ -755,41 +735,101 @@ class AddButton extends StatelessWidget {
return ActionIcon( return ActionIcon(
message: 'New Connection', message: 'New Connection',
icon: IconFont.add, icon: IconFont.add,
theme: theme,
onTap: () => onTap: () =>
rustDeskWinManager.call(WindowType.Main, "main_window_on_top", ""), rustDeskWinManager.call(WindowType.Main, "main_window_on_top", ""),
is_close: false); isClose: false);
} }
} }
class TarBarTheme { class TabbarTheme extends ThemeExtension<TabbarTheme> {
final Color unSelectedtabIconColor; final Color? selectedTabIconColor;
final Color selectedtabIconColor; final Color? unSelectedTabIconColor;
final Color selectedTextColor; final Color? selectedTextColor;
final Color unSelectedTextColor; final Color? unSelectedTextColor;
final Color selectedIconColor; final Color? selectedIconColor;
final Color unSelectedIconColor; final Color? unSelectedIconColor;
final Color dividerColor; final Color? dividerColor;
final Color hoverColor; final Color? hoverColor;
const TarBarTheme.light() const TabbarTheme(
: unSelectedtabIconColor = const Color.fromARGB(255, 162, 203, 241), {required this.selectedTabIconColor,
selectedtabIconColor = MyTheme.accent, required this.unSelectedTabIconColor,
selectedTextColor = const Color.fromARGB(255, 26, 26, 26), required this.selectedTextColor,
unSelectedTextColor = const Color.fromARGB(255, 96, 96, 96), required this.unSelectedTextColor,
selectedIconColor = const Color.fromARGB(255, 26, 26, 26), required this.selectedIconColor,
unSelectedIconColor = const Color.fromARGB(255, 96, 96, 96), required this.unSelectedIconColor,
dividerColor = const Color.fromARGB(255, 238, 238, 238), required this.dividerColor,
hoverColor = const Color.fromARGB( required this.hoverColor});
51, 158, 158, 158); // Colors.grey; //0xFF9E9E9E
const TarBarTheme.dark() static const light = TabbarTheme(
: unSelectedtabIconColor = const Color.fromARGB(255, 30, 65, 98), selectedTabIconColor: MyTheme.accent,
selectedtabIconColor = MyTheme.accent, unSelectedTabIconColor: Color.fromARGB(255, 162, 203, 241),
selectedTextColor = const Color.fromARGB(255, 255, 255, 255), selectedTextColor: Color.fromARGB(255, 26, 26, 26),
unSelectedTextColor = const Color.fromARGB(255, 207, 207, 207), unSelectedTextColor: Color.fromARGB(255, 96, 96, 96),
selectedIconColor = const Color.fromARGB(255, 215, 215, 215), selectedIconColor: Color.fromARGB(255, 26, 26, 26),
unSelectedIconColor = const Color.fromARGB(255, 255, 255, 255), unSelectedIconColor: Color.fromARGB(255, 96, 96, 96),
dividerColor = const Color.fromARGB(255, 64, 64, 64), dividerColor: Color.fromARGB(255, 238, 238, 238),
hoverColor = Colors.black26; hoverColor: Color.fromARGB(51, 158, 158, 158));
static const dark = TabbarTheme(
selectedTabIconColor: MyTheme.accent,
unSelectedTabIconColor: Color.fromARGB(255, 30, 65, 98),
selectedTextColor: Color.fromARGB(255, 255, 255, 255),
unSelectedTextColor: Color.fromARGB(255, 207, 207, 207),
selectedIconColor: Color.fromARGB(255, 215, 215, 215),
unSelectedIconColor: Color.fromARGB(255, 255, 255, 255),
dividerColor: Color.fromARGB(255, 64, 64, 64),
hoverColor: Colors.black26);
@override
ThemeExtension<TabbarTheme> copyWith({
Color? selectedTabIconColor,
Color? unSelectedTabIconColor,
Color? selectedTextColor,
Color? unSelectedTextColor,
Color? selectedIconColor,
Color? unSelectedIconColor,
Color? dividerColor,
Color? hoverColor,
}) {
return TabbarTheme(
selectedTabIconColor: selectedTabIconColor ?? this.selectedTabIconColor,
unSelectedTabIconColor:
unSelectedTabIconColor ?? this.unSelectedTabIconColor,
selectedTextColor: selectedTextColor ?? this.selectedTextColor,
unSelectedTextColor: unSelectedTextColor ?? this.unSelectedTextColor,
selectedIconColor: selectedIconColor ?? this.selectedIconColor,
unSelectedIconColor: unSelectedIconColor ?? this.unSelectedIconColor,
dividerColor: dividerColor ?? this.dividerColor,
hoverColor: hoverColor ?? this.hoverColor,
);
}
@override
ThemeExtension<TabbarTheme> lerp(
ThemeExtension<TabbarTheme>? other, double t) {
if (other is! TabbarTheme) {
return this;
}
return TabbarTheme(
selectedTabIconColor:
Color.lerp(selectedTabIconColor, other.selectedTabIconColor, t),
unSelectedTabIconColor:
Color.lerp(unSelectedTabIconColor, other.unSelectedTabIconColor, t),
selectedTextColor:
Color.lerp(selectedTextColor, other.selectedTextColor, t),
unSelectedTextColor:
Color.lerp(unSelectedTextColor, other.unSelectedTextColor, t),
selectedIconColor:
Color.lerp(selectedIconColor, other.selectedIconColor, t),
unSelectedIconColor:
Color.lerp(unSelectedIconColor, other.unSelectedIconColor, t),
dividerColor: Color.lerp(dividerColor, other.dividerColor, t),
hoverColor: Color.lerp(hoverColor, other.hoverColor, t),
);
}
static color(BuildContext context) {
return Theme.of(context).extension<ColorThemeExtension>()!;
}
} }

View File

@ -45,7 +45,7 @@ class ChatPage extends StatelessWidget implements PageShape {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: chatModel, value: chatModel,
child: Container( child: Container(
color: MyTheme.grayBg, color: MyTheme.color(context).grayBg,
child: Consumer<ChatModel>(builder: (context, chatModel, child) { child: Consumer<ChatModel>(builder: (context, chatModel, child) {
final currentUser = chatModel.currentUser; final currentUser = chatModel.currentUser;
return Stack( return Stack(
@ -59,7 +59,14 @@ class ChatPage extends StatelessWidget implements PageShape {
messages: chatModel messages: chatModel
.messages[chatModel.currentID]?.chatMessages ?? .messages[chatModel.currentID]?.chatMessages ??
[], [],
inputOptions: const InputOptions(sendOnEnter: true), inputOptions: InputOptions(
sendOnEnter: true,
inputDecoration: defaultInputDecoration(
fillColor: MyTheme.color(context).bg),
sendButtonBuilder: defaultSendButton(
color: MyTheme.color(context).text!),
inputTextStyle:
TextStyle(color: MyTheme.color(context).text)),
messageOptions: MessageOptions( messageOptions: MessageOptions(
showOtherUsersAvatar: false, showOtherUsersAvatar: false,
showTime: true, showTime: true,

View File

@ -559,49 +559,55 @@ class FileModel extends ChangeNotifier {
Future<bool?> showRemoveDialog( Future<bool?> showRemoveDialog(
String title, String content, bool showCheckbox) async { String title, String content, bool showCheckbox) async {
return await parent.target?.dialogManager.show<bool>( return await parent.target?.dialogManager.show<bool>(
(setState, Function(bool v) close) => CustomAlertDialog( (setState, Function(bool v) close) {
title: Row( cancel() => close(false);
children: [ submit() => close(true);
Icon(Icons.warning, color: Colors.red), return CustomAlertDialog(
SizedBox(width: 20), title: Row(
Text(title) children: [
], const Icon(Icons.warning, color: Colors.red),
), const SizedBox(width: 20),
content: Column( Text(title)
crossAxisAlignment: CrossAxisAlignment.start, ],
mainAxisSize: MainAxisSize.min, ),
children: [ content: Column(
Text(content), crossAxisAlignment: CrossAxisAlignment.start,
SizedBox(height: 5), mainAxisSize: MainAxisSize.min,
Text(translate("This is irreversible!"), children: [
style: TextStyle(fontWeight: FontWeight.bold)), Text(content),
showCheckbox const SizedBox(height: 5),
? CheckboxListTile( Text(translate("This is irreversible!"),
contentPadding: const EdgeInsets.all(0), style: const TextStyle(fontWeight: FontWeight.bold)),
dense: true, showCheckbox
controlAffinity: ListTileControlAffinity.leading, ? CheckboxListTile(
title: Text( contentPadding: const EdgeInsets.all(0),
translate("Do this for all conflicts"), dense: true,
), controlAffinity: ListTileControlAffinity.leading,
value: removeCheckboxRemember, title: Text(
onChanged: (v) { translate("Do this for all conflicts"),
if (v == null) return; ),
setState(() => removeCheckboxRemember = v); value: removeCheckboxRemember,
}, onChanged: (v) {
) if (v == null) return;
: SizedBox.shrink() setState(() => removeCheckboxRemember = v);
]), },
actions: [ )
TextButton( : const SizedBox.shrink()
style: flatButtonStyle, ]),
onPressed: () => close(false), actions: [
child: Text(translate("Cancel"))), TextButton(
TextButton( style: flatButtonStyle,
style: flatButtonStyle, onPressed: cancel,
onPressed: () => close(true), child: Text(translate("Cancel"))),
child: Text(translate("OK"))), TextButton(
]), style: flatButtonStyle,
useAnimation: false); onPressed: submit,
child: Text(translate("OK"))),
],
onSubmit: submit,
onCancel: cancel,
);
}, useAnimation: false);
} }
bool fileConfirmCheckboxRemember = false; bool fileConfirmCheckboxRemember = false;
@ -610,55 +616,59 @@ class FileModel extends ChangeNotifier {
String title, String content, bool showCheckbox) async { String title, String content, bool showCheckbox) async {
fileConfirmCheckboxRemember = false; fileConfirmCheckboxRemember = false;
return await parent.target?.dialogManager.show<bool?>( return await parent.target?.dialogManager.show<bool?>(
(setState, Function(bool? v) close) => CustomAlertDialog( (setState, Function(bool? v) close) {
title: Row( cancel() => close(false);
children: [ submit() => close(true);
Icon(Icons.warning, color: Colors.red), return CustomAlertDialog(
SizedBox(width: 20), title: Row(
Text(title) children: [
], const Icon(Icons.warning, color: Colors.red),
), const SizedBox(width: 20),
content: Column( Text(title)
crossAxisAlignment: CrossAxisAlignment.start, ],
mainAxisSize: MainAxisSize.min, ),
children: [ content: Column(
Text( crossAxisAlignment: CrossAxisAlignment.start,
translate( mainAxisSize: MainAxisSize.min,
"This file exists, skip or overwrite this file?"), children: [
style: TextStyle(fontWeight: FontWeight.bold)), Text(translate("This file exists, skip or overwrite this file?"),
SizedBox(height: 5), style: const TextStyle(fontWeight: FontWeight.bold)),
Text(content), const SizedBox(height: 5),
showCheckbox Text(content),
? CheckboxListTile( showCheckbox
contentPadding: const EdgeInsets.all(0), ? CheckboxListTile(
dense: true, contentPadding: const EdgeInsets.all(0),
controlAffinity: ListTileControlAffinity.leading, dense: true,
title: Text( controlAffinity: ListTileControlAffinity.leading,
translate("Do this for all conflicts"), title: Text(
), translate("Do this for all conflicts"),
value: fileConfirmCheckboxRemember, ),
onChanged: (v) { value: fileConfirmCheckboxRemember,
if (v == null) return; onChanged: (v) {
setState(() => fileConfirmCheckboxRemember = v); if (v == null) return;
}, setState(() => fileConfirmCheckboxRemember = v);
) },
: SizedBox.shrink() )
]), : const SizedBox.shrink()
actions: [ ]),
TextButton( actions: [
style: flatButtonStyle, TextButton(
onPressed: () => close(false), style: flatButtonStyle,
child: Text(translate("Cancel"))), onPressed: cancel,
TextButton( child: Text(translate("Cancel"))),
style: flatButtonStyle, TextButton(
onPressed: () => close(null), style: flatButtonStyle,
child: Text(translate("Skip"))), onPressed: () => close(null),
TextButton( child: Text(translate("Skip"))),
style: flatButtonStyle, TextButton(
onPressed: () => close(true), style: flatButtonStyle,
child: Text(translate("OK"))), onPressed: submit,
]), child: Text(translate("OK"))),
useAnimation: false); ],
onSubmit: submit,
onCancel: cancel,
);
}, useAnimation: false);
} }
sendRemoveFile(String path, int fileNum, bool isLocal) { sendRemoveFile(String path, int fileNum, bool isLocal) {

View File

@ -7,6 +7,7 @@ import 'package:flutter_hbb/models/platform_model.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
import '../common.dart'; import '../common.dart';
import '../common/formatter/id_formatter.dart';
import '../desktop/pages/server_page.dart' as Desktop; import '../desktop/pages/server_page.dart' as Desktop;
import '../desktop/widgets/tabbar_widget.dart'; import '../desktop/widgets/tabbar_widget.dart';
import '../mobile/pages/server_page.dart'; import '../mobile/pages/server_page.dart';
@ -29,7 +30,7 @@ class ServerModel with ChangeNotifier {
String _temporaryPasswordLength = ""; String _temporaryPasswordLength = "";
late String _emptyIdShow; late String _emptyIdShow;
late final TextEditingController _serverId; late final IDTextEditingController _serverId;
final _serverPasswd = TextEditingController(text: ""); final _serverPasswd = TextEditingController(text: "");
final tabController = DesktopTabController(tabType: DesktopTabType.cm); final tabController = DesktopTabController(tabType: DesktopTabType.cm);
@ -88,7 +89,7 @@ class ServerModel with ChangeNotifier {
ServerModel(this.parent) { ServerModel(this.parent) {
_emptyIdShow = translate("Generating ..."); _emptyIdShow = translate("Generating ...");
_serverId = TextEditingController(text: this._emptyIdShow); _serverId = IDTextEditingController(text: _emptyIdShow);
Timer.periodic(Duration(seconds: 1), (timer) async { Timer.periodic(Duration(seconds: 1), (timer) async {
var status = await bind.mainGetOnlineStatue(); var status = await bind.mainGetOnlineStatue();
@ -208,46 +209,48 @@ class ServerModel with ChangeNotifier {
/// Toggle the screen sharing service. /// Toggle the screen sharing service.
toggleService() async { toggleService() async {
if (_isStart) { if (_isStart) {
final res = await parent.target?.dialogManager final res =
.show<bool>((setState, close) => CustomAlertDialog( await parent.target?.dialogManager.show<bool>((setState, close) {
title: Row(children: [ submit() => close(true);
Icon(Icons.warning_amber_sharp, return CustomAlertDialog(
color: Colors.redAccent, size: 28), title: Row(children: [
SizedBox(width: 10), const Icon(Icons.warning_amber_sharp,
Text(translate("Warning")), color: Colors.redAccent, size: 28),
]), const SizedBox(width: 10),
content: Text(translate("android_stop_service_tip")), Text(translate("Warning")),
actions: [ ]),
TextButton( content: Text(translate("android_stop_service_tip")),
onPressed: () => close(), actions: [
child: Text(translate("Cancel"))), TextButton(onPressed: close, child: Text(translate("Cancel"))),
ElevatedButton( ElevatedButton(onPressed: submit, child: Text(translate("OK"))),
onPressed: () => close(true), ],
child: Text(translate("OK"))), onSubmit: submit,
], onCancel: close,
)); );
});
if (res == true) { if (res == true) {
stopService(); stopService();
} }
} else { } else {
final res = await parent.target?.dialogManager final res =
.show<bool>((setState, close) => CustomAlertDialog( await parent.target?.dialogManager.show<bool>((setState, close) {
title: Row(children: [ submit() => close(true);
Icon(Icons.warning_amber_sharp, return CustomAlertDialog(
color: Colors.redAccent, size: 28), title: Row(children: [
SizedBox(width: 10), const Icon(Icons.warning_amber_sharp,
Text(translate("Warning")), color: Colors.redAccent, size: 28),
]), const SizedBox(width: 10),
content: Text(translate("android_service_will_start_tip")), Text(translate("Warning")),
actions: [ ]),
TextButton( content: Text(translate("android_service_will_start_tip")),
onPressed: () => close(), actions: [
child: Text(translate("Cancel"))), TextButton(onPressed: close, child: Text(translate("Cancel"))),
ElevatedButton( ElevatedButton(onPressed: submit, child: Text(translate("OK"))),
onPressed: () => close(true), ],
child: Text(translate("OK"))), onSubmit: submit,
], onCancel: close,
)); );
});
if (res == true) { if (res == true) {
startService(); startService();
} }
@ -300,7 +303,7 @@ class ServerModel with ChangeNotifier {
} }
_fetchID() async { _fetchID() async {
final old = _serverId.text; final old = _serverId.id;
var count = 0; var count = 0;
const maxCount = 10; const maxCount = 10;
while (count < maxCount) { while (count < maxCount) {
@ -309,12 +312,12 @@ class ServerModel with ChangeNotifier {
if (id.isEmpty) { if (id.isEmpty) {
continue; continue;
} else { } else {
_serverId.text = id; _serverId.id = id;
} }
debugPrint("fetch id again at $count:id:${_serverId.text}"); debugPrint("fetch id again at $count:id:${_serverId.id}");
count++; count++;
if (_serverId.text != old) { if (_serverId.id != old) {
break; break;
} }
} }
@ -387,49 +390,49 @@ class ServerModel with ChangeNotifier {
} }
void showLoginDialog(Client client) { void showLoginDialog(Client client) {
parent.target?.dialogManager.show( parent.target?.dialogManager.show((setState, close) {
(setState, close) => CustomAlertDialog( cancel() {
title: Row( sendLoginResponse(client, false);
mainAxisAlignment: MainAxisAlignment.spaceBetween, close();
children: [ }
Text(translate(client.isFileTransfer
? "File Connection" submit() {
: "Screen Connection")), sendLoginResponse(client, true);
IconButton( close();
onPressed: () { }
close();
}, return CustomAlertDialog(
icon: Icon(Icons.close)) title:
]), Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
content: Column( Text(translate(
mainAxisSize: MainAxisSize.min, client.isFileTransfer ? "File Connection" : "Screen Connection")),
mainAxisAlignment: MainAxisAlignment.center, IconButton(
crossAxisAlignment: CrossAxisAlignment.start, onPressed: () {
children: [ close();
Text(translate("Do you accept?")), },
clientInfo(client), icon: const Icon(Icons.close))
Text( ]),
translate("android_new_connection_tip"), content: Column(
style: TextStyle(color: Colors.black54), mainAxisSize: MainAxisSize.min,
), mainAxisAlignment: MainAxisAlignment.center,
], crossAxisAlignment: CrossAxisAlignment.start,
), children: [
actions: [ Text(translate("Do you accept?")),
TextButton( clientInfo(client),
child: Text(translate("Dismiss")), Text(
onPressed: () { translate("android_new_connection_tip"),
sendLoginResponse(client, false); style: const TextStyle(color: Colors.black54),
close();
}),
ElevatedButton(
child: Text(translate("Accept")),
onPressed: () {
sendLoginResponse(client, true);
close();
}),
],
), ),
tag: getLoginDialogTag(client.id)); ],
),
actions: [
TextButton(onPressed: cancel, child: Text(translate("Dismiss"))),
ElevatedButton(onPressed: submit, child: Text(translate("Accept"))),
],
onSubmit: submit,
onCancel: cancel,
);
}, tag: getLoginDialogTag(client.id));
} }
scrollToBottom() { scrollToBottom() {
@ -562,24 +565,29 @@ String getLoginDialogTag(int id) {
} }
showInputWarnAlert(FFI ffi) { showInputWarnAlert(FFI ffi) {
ffi.dialogManager.show((setState, close) => CustomAlertDialog( ffi.dialogManager.show((setState, close) {
title: Text(translate("How to get Android input permission?")), submit() {
content: Column( ffi.serverModel.initInput();
mainAxisSize: MainAxisSize.min, close();
children: [ }
Text(translate("android_input_permission_tip1")),
SizedBox(height: 10), return CustomAlertDialog(
Text(translate("android_input_permission_tip2")), title: Text(translate("How to get Android input permission?")),
], content: Column(
), mainAxisSize: MainAxisSize.min,
actions: [ children: [
TextButton(child: Text(translate("Cancel")), onPressed: close), Text(translate("android_input_permission_tip1")),
ElevatedButton( const SizedBox(height: 10),
child: Text(translate("Open System Setting")), Text(translate("android_input_permission_tip2")),
onPressed: () {
ffi.serverModel.initInput();
close();
}),
], ],
)); ),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
ElevatedButton(
onPressed: submit, child: Text(translate("Open System Setting"))),
],
onSubmit: submit,
onCancel: close,
);
});
} }