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();
// request focus if there is no focused FocusNode in the dialog
Future.delayed(Duration.zero, () {
if (!focusNode.hasFocus) focusNode.requestFocus();
});
return Focus(
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, scrollable: true,
title: title, title: title,
contentPadding: contentPadding: EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: contentPadding ?? 25, vertical: 10), horizontal: contentPadding ?? 25, vertical: 10),
content: content, content: content,
actions: actions, actions: actions,
),
); );
} }
} }
@ -429,26 +474,28 @@ void msgBox(
{bool? hasCancel}) { {bool? hasCancel}) {
dialogManager.dismissAll(); dialogManager.dismissAll();
List<Widget> buttons = []; List<Widget> buttons = [];
if (type != "connecting" && type != "success" && !type.contains("nook")) { bool hasOk = false;
buttons.insert( submit() {
0,
msgBoxButton(translate('OK'), () {
dialogManager.dismissAll(); dialogManager.dismissAll();
// https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263 // https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263
if (!type.contains("custom")) { if (!type.contains("custom")) {
closeConnection(); closeConnection();
} }
})); }
cancel() {
dialogManager.dismissAll();
}
if (type != "connecting" && type != "success" && !type.contains("nook")) {
hasOk = true;
buttons.insert(0, msgBoxButton(translate('OK'), submit));
} }
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")) {
@ -460,8 +507,11 @@ 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,44 +179,56 @@ 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(
() => TextField(
autocorrect: false, autocorrect: false,
enableSuggestions: false, enableSuggestions: false,
keyboardType: TextInputType.visiblePassword, keyboardType: TextInputType.visiblePassword,
style: TextStyle( focusNode: focusNode,
style: const TextStyle(
fontFamily: 'WorkSans', fontFamily: 'WorkSans',
fontSize: 22, fontSize: 22,
height: 1, height: 1,
), ),
maxLines: 1,
cursorColor: MyTheme.color(context).text!,
decoration: InputDecoration( decoration: InputDecoration(
hintText: translate('Enter Remote ID'), hintText: inputFocused.value
? null
: translate('Enter Remote ID'),
hintStyle: TextStyle( hintStyle: TextStyle(
color: MyTheme.color(context).placeholder), color: MyTheme.color(context).placeholder),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.zero, borderRadius: BorderRadius.zero,
borderSide: BorderSide( borderSide: BorderSide(
color: MyTheme.color(context).placeholder!)), color: MyTheme.color(context).border!)),
focusedBorder: OutlineInputBorder( 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.button, width: 3), BorderSide(color: MyTheme.button, width: 3),
), ),
isDense: true, isDense: true,
contentPadding: contentPadding: const EdgeInsets.symmetric(
EdgeInsets.symmetric(horizontal: 10, vertical: 12)), horizontal: 10, vertical: 12)),
controller: _idController, controller: _idController,
inputFormatters: [IDTextInputFormatter()],
onSubmitted: (s) { onSubmitted: (s) {
onConnect(); onConnect();
}, },
), ),
), ),
),
], ],
), ),
Padding( Padding(
@ -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,52 +668,15 @@ 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) {
return CustomAlertDialog( submit() async {
title: Text(translate("Add ID")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("whitelist_sep")),
SizedBox(
height: 8.0,
),
Row(
children: [
Expanded(
child: TextField(
onChanged: (s) {
field = s;
},
maxLines: null,
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
),
controller: TextEditingController(text: field),
),
),
],
),
SizedBox(
height: 4.0,
),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator())
],
),
actions: [
TextButton(
onPressed: () {
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() { setState(() {
msg = ""; msg = "";
isInProgress = true; isInProgress = true;
}); });
field = field.trim(); field = controller.text.trim();
if (field.isEmpty) { if (field.isEmpty) {
// pass // pass
} else { } else {
@ -716,9 +693,44 @@ class _ConnectionPageState extends State<ConnectionPage> {
// final currentPeers // final currentPeers
} }
close(); close();
}, }
child: Text(translate("OK"))),
return CustomAlertDialog(
title: Text(translate("Add ID")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("whitelist_sep")),
const SizedBox(
height: 8.0,
),
Row(
children: [
Expanded(
child: TextField(
maxLines: null,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
),
controller: controller,
focusNode: FocusNode()..requestFocus()),
),
], ],
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, child: Text(translate("OK"))),
],
onSubmit: submit,
onCancel: close,
); );
}); });
} }
@ -727,52 +739,14 @@ 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) {
return CustomAlertDialog( submit() async {
title: Text(translate("Add Tag")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("whitelist_sep")),
SizedBox(
height: 8.0,
),
Row(
children: [
Expanded(
child: TextField(
onChanged: (s) {
field = s;
},
maxLines: null,
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
),
controller: TextEditingController(text: field),
),
),
],
),
SizedBox(
height: 4.0,
),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator())
],
),
actions: [
TextButton(
onPressed: () {
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() { setState(() {
msg = ""; msg = "";
isInProgress = true; isInProgress = true;
}); });
field = field.trim(); field = controller.text.trim();
if (field.isEmpty) { if (field.isEmpty) {
// pass // pass
} else { } else {
@ -785,9 +759,45 @@ class _ConnectionPageState extends State<ConnectionPage> {
// final currentPeers // final currentPeers
} }
close(); close();
}, }
child: Text(translate("OK"))),
return CustomAlertDialog(
title: Text(translate("Add Tag")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("whitelist_sep")),
const SizedBox(
height: 8.0,
),
Row(
children: [
Expanded(
child: TextField(
maxLines: null,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg),
),
controller: controller,
focusNode: FocusNode()..requestFocus(),
),
),
], ],
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, 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,53 +642,10 @@ 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) {
return CustomAlertDialog( submit() async {
title: Text(translate("Change ID")), newId = controller.text.trim();
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("id_change_tip")),
SizedBox(
height: 8.0,
),
Row(
children: [
Text("ID:").marginOnly(bottom: 16.0),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
onChanged: (s) {
newId = s;
},
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg)),
inputFormatters: [
LengthLimitingTextInputFormatter(16),
// FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
],
maxLength: 16,
),
),
],
),
SizedBox(
height: 4.0,
),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator())
],
),
actions: [
TextButton(
onPressed: () {
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() { setState(() {
msg = ""; msg = "";
isInProgress = true; isInProgress = true;
@ -697,7 +654,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
var status = await bind.mainGetAsyncStatus(); var status = await bind.mainGetAsyncStatus();
while (status == " ") { while (status == " ") {
await Future.delayed(Duration(milliseconds: 100)); await Future.delayed(const Duration(milliseconds: 100));
status = await bind.mainGetAsyncStatus(); status = await bind.mainGetAsyncStatus();
} }
if (status.isEmpty) { if (status.isEmpty) {
@ -709,9 +666,52 @@ class _DesktopHomePageState extends State<DesktopHomePage>
isInProgress = false; isInProgress = false;
msg = translate(status); msg = translate(status);
}); });
}, }
child: Text(translate("OK"))),
return CustomAlertDialog(
title: Text(translate("Change ID")),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(translate("id_change_tip")),
const SizedBox(
height: 8.0,
),
Row(
children: [
const Text("ID:").marginOnly(bottom: 16.0),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: msg.isEmpty ? null : translate(msg)),
inputFormatters: [
LengthLimitingTextInputFormatter(16),
// FilteringTextInputFormatter(RegExp(r"[a-zA-z][a-zA-z0-9\_]*"), allow: true)
], ],
maxLength: 16,
controller: controller,
focusNode: FocusNode()..requestFocus(),
),
),
],
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, 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,86 +814,18 @@ 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) {
return CustomAlertDialog( submit() async {
title: Text(translate("Login")),
content: ConstrainedBox(
constraints: BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text(
"${translate('Username')}:",
textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText: userNameMsg.isNotEmpty ? userNameMsg : null),
controller: userContontroller,
),
),
],
),
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:")
.marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText: passMsg.isNotEmpty ? passMsg : null),
controller: pwdController,
),
),
],
),
SizedBox(
height: 4.0,
),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator())
],
),
),
actions: [
TextButton(
onPressed: () {
completer.complete(false);
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() { setState(() {
userNameMsg = ""; userNameMsg = "";
passMsg = ""; passMsg = "";
isInProgress = true; isInProgress = true;
}); });
final cancel = () { cancel() {
setState(() { setState(() {
isInProgress = false; isInProgress = false;
}); });
}; }
userName = userContontroller.text; userName = userContontroller.text;
pass = pwdController.text; pass = pwdController.text;
if (userName.isEmpty) { if (userName.isEmpty) {
@ -919,14 +850,88 @@ Future<bool> loginDialog() async {
debugPrint("$resp"); debugPrint("$resp");
completer.complete(true); completer.complete(true);
} catch (err) { } catch (err) {
// ignore: avoid_print
print(err.toString()); print(err.toString());
cancel(); cancel();
return; return;
} }
close(); close();
}, }
child: Text(translate("OK"))),
cancel() {
completer.complete(false);
close();
}
return CustomAlertDialog(
title: Text(translate("Login")),
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text(
"${translate('Username')}:",
textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: userNameMsg.isNotEmpty ? userNameMsg : null),
controller: userContontroller,
focusNode: FocusNode()..requestFocus(),
),
),
], ],
),
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:")
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: passMsg.isNotEmpty ? passMsg : null),
controller: pwdController,
),
),
],
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
),
actions: [
TextButton(onPressed: cancel, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, child: Text(translate("OK"))),
],
onSubmit: submit,
onCancel: cancel,
); );
}); });
return completer.future; return completer.future;
@ -940,72 +945,7 @@ void setPasswordDialog() async {
var errMsg1 = ""; var errMsg1 = "";
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
return CustomAlertDialog( submit() {
title: Text(translate("Set Password")),
content: ConstrainedBox(
constraints: BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text(
"${translate('Password')}:",
textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText: errMsg0.isNotEmpty ? errMsg0 : null),
controller: p0,
),
),
],
),
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text("${translate('Confirmation')}:")
.marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText: errMsg1.isNotEmpty ? errMsg1 : null),
controller: p1,
),
),
],
),
],
),
),
actions: [
TextButton(
onPressed: () {
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () {
setState(() { setState(() {
errMsg0 = ""; errMsg0 = "";
errMsg1 = ""; errMsg1 = "";
@ -1025,9 +965,73 @@ void setPasswordDialog() async {
} }
bind.mainSetPermanentPassword(password: pass); bind.mainSetPermanentPassword(password: pass);
close(); close();
}, }
child: Text(translate("OK"))),
return CustomAlertDialog(
title: Text(translate("Set Password")),
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text(
"${translate('Password')}:",
textAlign: TextAlign.start,
).marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: errMsg0.isNotEmpty ? errMsg0 : null),
controller: p0,
focusNode: FocusNode()..requestFocus(),
),
),
], ],
),
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Confirmation')}:")
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
obscureText: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: errMsg1.isNotEmpty ? errMsg1 : null),
controller: p1,
),
),
],
),
],
),
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, 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,138 +1038,29 @@ 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) {
return CustomAlertDialog( submit() async {
title: Text(translate("ID/Relay Server")),
content: ConstrainedBox(
constraints: BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text("${translate('ID Server')}:")
.marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText: idServerMsg.isNotEmpty ? idServerMsg : null),
controller: idController,
),
),
],
),
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text("${translate('Relay Server')}:")
.marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText:
relayServerMsg.isNotEmpty ? relayServerMsg : null),
controller: relayController,
),
),
],
),
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text("${translate('API Server')}:")
.marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText:
apiServerMsg.isNotEmpty ? apiServerMsg : null),
controller: apiController,
),
),
],
),
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child:
Text("${translate('Key')}:").marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
),
controller: keyController,
),
),
],
),
SizedBox(
height: 4.0,
),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator())
],
),
),
actions: [
TextButton(
onPressed: () {
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() { setState(() {
[idServerMsg, relayServerMsg, apiServerMsg].forEach((element) { [idServerMsg, relayServerMsg, apiServerMsg].forEach((element) {
element = ""; element = "";
}); });
isInProgress = true; isInProgress = true;
}); });
final cancel = () { cancel() {
setState(() { setState(() {
isInProgress = false; isInProgress = false;
}); });
}; }
idServer = idController.text.trim(); idServer = idController.text.trim();
relayServer = relayController.text.trim(); relayServer = relayController.text.trim();
apiServer = apiController.text.trim().toLowerCase(); apiServer = apiController.text.trim().toLowerCase();
key = keyController.text.trim(); key = keyController.text.trim();
if (idServer.isNotEmpty) { if (idServer.isNotEmpty) {
idServerMsg = translate( idServerMsg =
await bind.mainTestIfValidServer(server: idServer)); translate(await bind.mainTestIfValidServer(server: idServer));
if (idServerMsg.isEmpty) { if (idServerMsg.isEmpty) {
oldOptions['custom-rendezvous-server'] = idServer; oldOptions['custom-rendezvous-server'] = idServer;
} else { } else {
@ -1181,8 +1072,8 @@ void changeServer() async {
} }
if (relayServer.isNotEmpty) { if (relayServer.isNotEmpty) {
relayServerMsg = translate( relayServerMsg =
await bind.mainTestIfValidServer(server: relayServer)); translate(await bind.mainTestIfValidServer(server: relayServer));
if (relayServerMsg.isEmpty) { if (relayServerMsg.isEmpty) {
oldOptions['relay-server'] = relayServer; oldOptions['relay-server'] = relayServer;
} else { } else {
@ -1210,9 +1101,120 @@ void changeServer() async {
oldOptions['key'] = key; oldOptions['key'] = key;
await bind.mainSetOptions(json: jsonEncode(oldOptions)); await bind.mainSetOptions(json: jsonEncode(oldOptions));
close(); close();
}, }
child: Text(translate("OK"))),
return CustomAlertDialog(
title: Text(translate("ID/Relay Server")),
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('ID Server')}:")
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: idServerMsg.isNotEmpty ? idServerMsg : null),
controller: idController,
focusNode: FocusNode()..requestFocus(),
),
),
], ],
),
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Relay Server')}:")
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText:
relayServerMsg.isNotEmpty ? relayServerMsg : null),
controller: relayController,
),
),
],
),
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('API Server')}:")
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText:
apiServerMsg.isNotEmpty ? apiServerMsg : null),
controller: apiController,
),
),
],
),
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child:
Text("${translate('Key')}:").marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
controller: keyController,
),
),
],
),
const SizedBox(
height: 4.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, child: Text(translate("OK"))),
],
onSubmit: submit,
onCancel: close,
); );
}); });
} }
@ -1231,7 +1233,7 @@ 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(
@ -1240,18 +1242,19 @@ void changeWhiteList() async {
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,110 +1318,23 @@ void changeSocks5Proxy() async {
var isInProgress = false; var isInProgress = false;
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
return CustomAlertDialog( submit() async {
title: Text(translate("Socks5 Proxy")),
content: ConstrainedBox(
constraints: BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text("${translate('Hostname')}:")
.marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
errorText: proxyMsg.isNotEmpty ? proxyMsg : null),
controller: proxyController,
),
),
],
),
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text("${translate('Username')}:")
.marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
),
controller: userController,
),
),
],
),
SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:")
.marginOnly(bottom: 16.0)),
SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
),
controller: pwdController,
),
),
],
),
SizedBox(
height: 8.0,
),
Offstage(offstage: !isInProgress, child: LinearProgressIndicator())
],
),
),
actions: [
TextButton(
onPressed: () {
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
setState(() { setState(() {
proxyMsg = ""; proxyMsg = "";
isInProgress = true; isInProgress = true;
}); });
final cancel = () { cancel() {
setState(() { setState(() {
isInProgress = false; isInProgress = false;
}); });
}; }
proxy = proxyController.text.trim(); proxy = proxyController.text.trim();
username = userController.text.trim(); username = userController.text.trim();
password = pwdController.text.trim(); password = pwdController.text.trim();
if (proxy.isNotEmpty) { if (proxy.isNotEmpty) {
proxyMsg = proxyMsg = translate(await bind.mainTestIfValidServer(server: proxy));
translate(await bind.mainTestIfValidServer(server: proxy));
if (proxyMsg.isEmpty) { if (proxyMsg.isEmpty) {
// ignore // ignore
} else { } else {
@ -1428,9 +1345,96 @@ void changeSocks5Proxy() async {
await bind.mainSetSocks( await bind.mainSetSocks(
proxy: proxy, username: username, password: password); proxy: proxy, username: username, password: password);
close(); close();
}, }
child: Text(translate("OK"))),
return CustomAlertDialog(
title: Text(translate("Socks5 Proxy")),
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Hostname')}:")
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(),
errorText: proxyMsg.isNotEmpty ? proxyMsg : null),
controller: proxyController,
focusNode: FocusNode()..requestFocus(),
),
),
], ],
),
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Username')}:")
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
controller: userController,
),
),
],
),
const SizedBox(
height: 8.0,
),
Row(
children: [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 100),
child: Text("${translate('Password')}:")
.marginOnly(bottom: 16.0)),
const SizedBox(
width: 24.0,
),
Expanded(
child: TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
controller: pwdController,
),
),
],
),
const SizedBox(
height: 8.0,
),
Offstage(
offstage: !isInProgress, child: const LinearProgressIndicator())
],
),
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, child: Text(translate("OK"))),
],
onSubmit: submit,
onCancel: close,
); );
}); });
} }

View File

@ -41,21 +41,23 @@ class _DesktopTabPageState extends State<DesktopTabPage> {
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: [
OverlayEntry(builder: (context) {
gFFI.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold(
backgroundColor: MyTheme.color(context).bg, backgroundColor: MyTheme.color(context).bg,
body: DesktopTab( body: DesktopTab(
controller: tabController, controller: tabController,
theme: dark ? TarBarTheme.dark() : TarBarTheme.light(),
tail: ActionIcon( tail: ActionIcon(
message: 'Settings', message: 'Settings',
icon: IconFont.menu, icon: IconFont.menu,
theme: dark ? TarBarTheme.dark() : TarBarTheme.light(),
onTap: onAddSetting, onTap: onAddSetting,
is_close: false, isClose: false,
),
)),
), ),
)); ));
})
]),
)));
} }
void onAddSetting() { void onAddSetting() {

View File

@ -642,8 +642,21 @@ 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() {
if (name.value.text.isNotEmpty) {
model.createDir(
PathUtil.join(
model.getCurrentDir(isLocal).path,
name.value.text,
model.getCurrentIsWindows(isLocal)),
isLocal: isLocal);
close();
}
}
cancel() => close(false);
return CustomAlertDialog(
title: Text(translate("Create Folder")), title: Text(translate("Create Folder")),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -654,35 +667,26 @@ class _FileManagerPageState extends State<FileManagerPage>
"Please enter the folder name"), "Please enter the folder name"),
), ),
controller: name, controller: name,
focusNode: FocusNode()..requestFocus(),
), ),
], ],
), ),
actions: [ actions: [
TextButton( TextButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () => close(false), onPressed: cancel,
child: Text(translate("Cancel"))), child: Text(translate("Cancel"))),
ElevatedButton( ElevatedButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () { onPressed: submit,
if (name.value.text.isNotEmpty) {
model.createDir(
PathUtil.join(
model
.getCurrentDir(
isLocal)
.path,
name.value.text,
model.getCurrentIsWindows(
isLocal)),
isLocal: isLocal);
close();
}
},
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,6 +70,9 @@ class _PortForwardPageState extends State<PortForwardPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Overlay(initialEntries: [
OverlayEntry(builder: (context) {
_ffi.dialogManager.setOverlayState(Overlay.of(context));
return Scaffold( return Scaffold(
backgroundColor: MyTheme.color(context).grayBg, backgroundColor: MyTheme.color(context).grayBg,
body: FutureBuilder(future: () async { body: FutureBuilder(future: () async {
@ -90,9 +93,11 @@ class _PortForwardPageState extends State<PortForwardPage>
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: MyTheme.color(context).bg, color: MyTheme.color(context).bg,
border: Border.all(width: 1, color: MyTheme.border)), border:
child: Border.all(width: 1, color: MyTheme.border)),
widget.isRDP ? buildRdp(context) : buildTunnel(context), child: widget.isRDP
? buildRdp(context)
: buildTunnel(context),
), ),
), ),
], ],
@ -102,6 +107,8 @@ class _PortForwardPageState extends State<PortForwardPage>
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,33 +563,7 @@ abstract class BasePeerCard extends StatelessWidget {
} }
} }
gFFI.dialogManager.show((setState, close) { gFFI.dialogManager.show((setState, close) {
return CustomAlertDialog( submit() async {
title: Text(translate('Rename')),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Form(
child: TextFormField(
controller: controller,
decoration: InputDecoration(border: OutlineInputBorder()),
),
),
),
Obx(() => Offstage(
offstage: isInProgress.isFalse,
child: LinearProgressIndicator())),
],
),
actions: [
TextButton(
onPressed: () {
close();
},
child: Text(translate("Cancel"))),
TextButton(
onPressed: () async {
isInProgress.value = true; isInProgress.value = true;
name = controller.text; name = controller.text;
await bind.mainSetPeerOption(id: id, key: 'alias', value: name); await bind.mainSetPeerOption(id: id, key: 'alias', value: name);
@ -596,13 +571,39 @@ abstract class BasePeerCard extends StatelessWidget {
gFFI.abModel.setPeerOption(id, 'alias', name); gFFI.abModel.setPeerOption(id, 'alias', name);
await gFFI.abModel.updateAb(); await gFFI.abModel.updateAb();
} }
alias.value = alias.value = await bind.mainGetPeerOption(id: peer.id, key: 'alias');
await bind.mainGetPeerOption(id: peer.id, key: 'alias');
close(); close();
isInProgress.value = false; isInProgress.value = false;
}, }
child: Text(translate("OK"))),
return CustomAlertDialog(
title: Text(translate('Rename')),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Form(
child: TextFormField(
controller: controller,
focusNode: FocusNode()..requestFocus(),
decoration:
const InputDecoration(border: OutlineInputBorder()),
),
),
),
Obx(() => Offstage(
offstage: isInProgress.isFalse,
child: const LinearProgressIndicator())),
], ],
),
actions: [
TextButton(onPressed: close, child: Text(translate("Cancel"))),
TextButton(onPressed: submit, 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,6 +596,17 @@ 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: [
@ -617,25 +628,17 @@ void showSetOSPassword(
actions: [ actions: [
TextButton( TextButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () { onPressed: close,
close();
},
child: Text(translate('Cancel')), child: Text(translate('Cancel')),
), ),
TextButton( TextButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () { onPressed: 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();
},
child: Text(translate('OK')), child: Text(translate('OK')),
), ),
]); ],
onSubmit: submit,
onCancel: close,
);
}); });
} }

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);
return CustomAlertDialog(
title: Row(children: [ title: Row(children: [
Icon(Icons.warning_amber_sharp, const Icon(Icons.warning_amber_sharp,
color: Colors.redAccent, size: 28), color: Colors.redAccent, size: 28),
SizedBox(width: 10), const SizedBox(width: 10),
Text(translate("Warning")), Text(translate("Warning")),
]), ]),
content: Text(translate("Disconnect all devices?")), content: Text(translate("Disconnect all devices?")),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () => close(), child: Text(translate("Cancel"))), ElevatedButton(onPressed: submit, child: Text(translate("OK"))),
ElevatedButton(
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,20 +529,19 @@ 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,
@ -563,8 +551,7 @@ class _Tab extends StatefulWidget {
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,11 +559,14 @@ 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) {
cancel() => close(false);
submit() => close(true);
return CustomAlertDialog(
title: Row( title: Row(
children: [ children: [
Icon(Icons.warning, color: Colors.red), const Icon(Icons.warning, color: Colors.red),
SizedBox(width: 20), const SizedBox(width: 20),
Text(title) Text(title)
], ],
), ),
@ -572,9 +575,9 @@ class FileModel extends ChangeNotifier {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(content), Text(content),
SizedBox(height: 5), const SizedBox(height: 5),
Text(translate("This is irreversible!"), Text(translate("This is irreversible!"),
style: TextStyle(fontWeight: FontWeight.bold)), style: const TextStyle(fontWeight: FontWeight.bold)),
showCheckbox showCheckbox
? CheckboxListTile( ? CheckboxListTile(
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
@ -589,19 +592,22 @@ class FileModel extends ChangeNotifier {
setState(() => removeCheckboxRemember = v); setState(() => removeCheckboxRemember = v);
}, },
) )
: SizedBox.shrink() : const SizedBox.shrink()
]), ]),
actions: [ actions: [
TextButton( TextButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () => close(false), onPressed: cancel,
child: Text(translate("Cancel"))), child: Text(translate("Cancel"))),
TextButton( TextButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () => close(true), onPressed: submit,
child: Text(translate("OK"))), child: Text(translate("OK"))),
]), ],
useAnimation: false); onSubmit: submit,
onCancel: cancel,
);
}, useAnimation: false);
} }
bool fileConfirmCheckboxRemember = false; bool fileConfirmCheckboxRemember = false;
@ -610,11 +616,14 @@ 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) {
cancel() => close(false);
submit() => close(true);
return CustomAlertDialog(
title: Row( title: Row(
children: [ children: [
Icon(Icons.warning, color: Colors.red), const Icon(Icons.warning, color: Colors.red),
SizedBox(width: 20), const SizedBox(width: 20),
Text(title) Text(title)
], ],
), ),
@ -622,11 +631,9 @@ class FileModel extends ChangeNotifier {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(translate("This file exists, skip or overwrite this file?"),
translate( style: const TextStyle(fontWeight: FontWeight.bold)),
"This file exists, skip or overwrite this file?"), const SizedBox(height: 5),
style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 5),
Text(content), Text(content),
showCheckbox showCheckbox
? CheckboxListTile( ? CheckboxListTile(
@ -642,12 +649,12 @@ class FileModel extends ChangeNotifier {
setState(() => fileConfirmCheckboxRemember = v); setState(() => fileConfirmCheckboxRemember = v);
}, },
) )
: SizedBox.shrink() : const SizedBox.shrink()
]), ]),
actions: [ actions: [
TextButton( TextButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () => close(false), onPressed: cancel,
child: Text(translate("Cancel"))), child: Text(translate("Cancel"))),
TextButton( TextButton(
style: flatButtonStyle, style: flatButtonStyle,
@ -655,10 +662,13 @@ class FileModel extends ChangeNotifier {
child: Text(translate("Skip"))), child: Text(translate("Skip"))),
TextButton( TextButton(
style: flatButtonStyle, style: flatButtonStyle,
onPressed: () => close(true), onPressed: submit,
child: Text(translate("OK"))), 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) {
submit() => close(true);
return CustomAlertDialog(
title: Row(children: [ title: Row(children: [
Icon(Icons.warning_amber_sharp, const Icon(Icons.warning_amber_sharp,
color: Colors.redAccent, size: 28), color: Colors.redAccent, size: 28),
SizedBox(width: 10), const SizedBox(width: 10),
Text(translate("Warning")), Text(translate("Warning")),
]), ]),
content: Text(translate("android_stop_service_tip")), content: Text(translate("android_stop_service_tip")),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () => close(), ElevatedButton(onPressed: submit, child: Text(translate("OK"))),
child: Text(translate("Cancel"))),
ElevatedButton(
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) {
submit() => close(true);
return CustomAlertDialog(
title: Row(children: [ title: Row(children: [
Icon(Icons.warning_amber_sharp, const Icon(Icons.warning_amber_sharp,
color: Colors.redAccent, size: 28), color: Colors.redAccent, size: 28),
SizedBox(width: 10), const SizedBox(width: 10),
Text(translate("Warning")), Text(translate("Warning")),
]), ]),
content: Text(translate("android_service_will_start_tip")), content: Text(translate("android_service_will_start_tip")),
actions: [ actions: [
TextButton( TextButton(onPressed: close, child: Text(translate("Cancel"))),
onPressed: () => close(), ElevatedButton(onPressed: submit, child: Text(translate("OK"))),
child: Text(translate("Cancel"))),
ElevatedButton(
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,19 +390,27 @@ 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);
close();
}
return CustomAlertDialog(
title:
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text(translate(
client.isFileTransfer ? "File Connection" : "Screen Connection")),
IconButton( IconButton(
onPressed: () { onPressed: () {
close(); close();
}, },
icon: Icon(Icons.close)) icon: const Icon(Icons.close))
]), ]),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -410,26 +421,18 @@ class ServerModel with ChangeNotifier {
clientInfo(client), clientInfo(client),
Text( Text(
translate("android_new_connection_tip"), translate("android_new_connection_tip"),
style: TextStyle(color: Colors.black54), style: const TextStyle(color: Colors.black54),
), ),
], ],
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: cancel, child: Text(translate("Dismiss"))),
child: Text(translate("Dismiss")), ElevatedButton(onPressed: submit, child: Text(translate("Accept"))),
onPressed: () {
sendLoginResponse(client, false);
close();
}),
ElevatedButton(
child: Text(translate("Accept")),
onPressed: () {
sendLoginResponse(client, true);
close();
}),
], ],
), onSubmit: submit,
tag: getLoginDialogTag(client.id)); 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) {
submit() {
ffi.serverModel.initInput();
close();
}
return CustomAlertDialog(
title: Text(translate("How to get Android input permission?")), title: Text(translate("How to get Android input permission?")),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(translate("android_input_permission_tip1")), Text(translate("android_input_permission_tip1")),
SizedBox(height: 10), const SizedBox(height: 10),
Text(translate("android_input_permission_tip2")), Text(translate("android_input_permission_tip2")),
], ],
), ),
actions: [ actions: [
TextButton(child: Text(translate("Cancel")), onPressed: close), TextButton(onPressed: close, child: Text(translate("Cancel"))),
ElevatedButton( ElevatedButton(
child: Text(translate("Open System Setting")), onPressed: submit, child: Text(translate("Open System Setting"))),
onPressed: () {
ffi.serverModel.initInput();
close();
}),
], ],
)); onSubmit: submit,
onCancel: close,
);
});
} }