mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-06 23:51:26 +03:00
fix conflicts
This commit is contained in:
225
flutter/lib/desktop/widgets/kb_layout_type_chooser.dart
Normal file
225
flutter/lib/desktop/widgets/kb_layout_type_chooser.dart
Normal file
@@ -0,0 +1,225 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
|
||||
typedef KBChosenCallback = Future<bool> Function(String);
|
||||
|
||||
const double _kImageMarginVertical = 6.0;
|
||||
const double _kImageMarginHorizontal = 10.0;
|
||||
const double _kImageBoarderWidth = 4.0;
|
||||
const double _kImagePaddingWidth = 4.0;
|
||||
const Color _kImageBorderColor = Color.fromARGB(125, 202, 247, 2);
|
||||
const double _kBorderRadius = 6.0;
|
||||
const String _kKBLayoutTypeISO = 'ISO';
|
||||
const String _kKBLayoutTypeNotISO = 'Not ISO';
|
||||
|
||||
const _kKBLayoutImageMap = {
|
||||
_kKBLayoutTypeISO: 'kb_layout_iso',
|
||||
_kKBLayoutTypeNotISO: 'kb_layout_not_iso',
|
||||
};
|
||||
|
||||
class _KBImage extends StatelessWidget {
|
||||
final String kbLayoutType;
|
||||
final double imageWidth;
|
||||
final RxString chosenType;
|
||||
const _KBImage({
|
||||
Key? key,
|
||||
required this.kbLayoutType,
|
||||
required this.imageWidth,
|
||||
required this.chosenType,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(_kBorderRadius),
|
||||
border: Border.all(
|
||||
color: chosenType.value == kbLayoutType
|
||||
? _kImageBorderColor
|
||||
: Colors.transparent,
|
||||
width: _kImageBoarderWidth,
|
||||
),
|
||||
),
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: _kImageMarginHorizontal,
|
||||
vertical: _kImageMarginVertical,
|
||||
),
|
||||
padding: EdgeInsets.all(_kImagePaddingWidth),
|
||||
child: SvgPicture.asset(
|
||||
'assets/${_kKBLayoutImageMap[kbLayoutType] ?? ""}.svg',
|
||||
width: imageWidth -
|
||||
_kImageMarginHorizontal * 2 -
|
||||
_kImagePaddingWidth * 2 -
|
||||
_kImageBoarderWidth * 2,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _KBChooser extends StatelessWidget {
|
||||
final String kbLayoutType;
|
||||
final double imageWidth;
|
||||
final RxString chosenType;
|
||||
final KBChosenCallback cb;
|
||||
const _KBChooser({
|
||||
Key? key,
|
||||
required this.kbLayoutType,
|
||||
required this.imageWidth,
|
||||
required this.chosenType,
|
||||
required this.cb,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
onChanged(String? v) async {
|
||||
if (v != null) {
|
||||
if (await cb(v)) {
|
||||
chosenType.value = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
onChanged(kbLayoutType);
|
||||
},
|
||||
child: _KBImage(
|
||||
kbLayoutType: kbLayoutType,
|
||||
imageWidth: imageWidth,
|
||||
chosenType: chosenType,
|
||||
),
|
||||
style: TextButton.styleFrom(padding: EdgeInsets.zero),
|
||||
),
|
||||
TextButton(
|
||||
child: Row(
|
||||
children: [
|
||||
Obx(() => Radio(
|
||||
splashRadius: 0,
|
||||
value: kbLayoutType,
|
||||
groupValue: chosenType.value,
|
||||
onChanged: onChanged,
|
||||
)),
|
||||
Text(kbLayoutType),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
onChanged(kbLayoutType);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class KBLayoutTypeChooser extends StatelessWidget {
|
||||
final RxString chosenType;
|
||||
final double width;
|
||||
final double height;
|
||||
final double dividerWidth;
|
||||
final KBChosenCallback cb;
|
||||
KBLayoutTypeChooser({
|
||||
Key? key,
|
||||
required this.chosenType,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.dividerWidth,
|
||||
required this.cb,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final imageWidth = width / 2 - dividerWidth;
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: height,
|
||||
child: Center(
|
||||
child: Row(
|
||||
children: [
|
||||
_KBChooser(
|
||||
kbLayoutType: _kKBLayoutTypeISO,
|
||||
imageWidth: imageWidth,
|
||||
chosenType: chosenType,
|
||||
cb: cb,
|
||||
),
|
||||
VerticalDivider(
|
||||
width: dividerWidth * 2,
|
||||
),
|
||||
_KBChooser(
|
||||
kbLayoutType: _kKBLayoutTypeNotISO,
|
||||
imageWidth: imageWidth,
|
||||
chosenType: chosenType,
|
||||
cb: cb,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RxString KBLayoutType = ''.obs;
|
||||
|
||||
String getLocalPlatformForKBLayoutType(String peerPlatform) {
|
||||
String localPlatform = '';
|
||||
if (peerPlatform != kPeerPlatformMacOS) {
|
||||
return localPlatform;
|
||||
}
|
||||
|
||||
if (Platform.isWindows) {
|
||||
localPlatform = kPeerPlatformWindows;
|
||||
} else if (Platform.isLinux) {
|
||||
localPlatform = kPeerPlatformLinux;
|
||||
}
|
||||
// to-do: web desktop support ?
|
||||
return localPlatform;
|
||||
}
|
||||
|
||||
showKBLayoutTypeChooserIfNeeded(
|
||||
String peerPlatform,
|
||||
OverlayDialogManager dialogManager,
|
||||
) async {
|
||||
final localPlatform = getLocalPlatformForKBLayoutType(peerPlatform);
|
||||
if (localPlatform == '') {
|
||||
return;
|
||||
}
|
||||
KBLayoutType.value = bind.getLocalKbLayoutType();
|
||||
if (KBLayoutType.value == _kKBLayoutTypeISO ||
|
||||
KBLayoutType.value == _kKBLayoutTypeNotISO) {
|
||||
return;
|
||||
}
|
||||
showKBLayoutTypeChooser(localPlatform, dialogManager);
|
||||
}
|
||||
|
||||
showKBLayoutTypeChooser(
|
||||
String localPlatform,
|
||||
OverlayDialogManager dialogManager,
|
||||
) {
|
||||
dialogManager.show((setState, close) {
|
||||
return CustomAlertDialog(
|
||||
title:
|
||||
Text('${translate('Select local keyboard type')} ($localPlatform)'),
|
||||
content: KBLayoutTypeChooser(
|
||||
chosenType: KBLayoutType,
|
||||
width: 360,
|
||||
height: 200,
|
||||
dividerWidth: 4.0,
|
||||
cb: (String v) async {
|
||||
await bind.setLocalKbLayoutType(kbLayoutType: v);
|
||||
KBLayoutType.value = bind.getLocalKbLayoutType();
|
||||
return v == KBLayoutType.value;
|
||||
}),
|
||||
actions: [msgBoxButton(translate('Close'), close)],
|
||||
onCancel: close,
|
||||
);
|
||||
});
|
||||
}
|
||||
75
flutter/lib/desktop/widgets/list_search_action_listener.dart
Normal file
75
flutter/lib/desktop/widgets/list_search_action_listener.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ListSearchActionListener extends StatelessWidget {
|
||||
final FocusNode node;
|
||||
final TimeoutStringBuffer buffer;
|
||||
final Widget child;
|
||||
final Function(String) onNext;
|
||||
final Function(String) onSearch;
|
||||
|
||||
const ListSearchActionListener(
|
||||
{super.key,
|
||||
required this.node,
|
||||
required this.buffer,
|
||||
required this.child,
|
||||
required this.onNext,
|
||||
required this.onSearch});
|
||||
|
||||
@mustCallSuper
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KeyboardListener(
|
||||
autofocus: true,
|
||||
onKeyEvent: (kv) {
|
||||
final ch = kv.character;
|
||||
if (ch == null) {
|
||||
return;
|
||||
}
|
||||
final action = buffer.input(ch);
|
||||
switch (action) {
|
||||
case ListSearchAction.search:
|
||||
onSearch(buffer.buffer);
|
||||
break;
|
||||
case ListSearchAction.next:
|
||||
onNext(buffer.buffer);
|
||||
break;
|
||||
}
|
||||
},
|
||||
focusNode: node,
|
||||
child: child);
|
||||
}
|
||||
}
|
||||
|
||||
enum ListSearchAction { search, next }
|
||||
|
||||
class TimeoutStringBuffer {
|
||||
var _buffer = "";
|
||||
late DateTime _duration;
|
||||
|
||||
static int timeoutMilliSec = 1500;
|
||||
|
||||
String get buffer => _buffer;
|
||||
|
||||
TimeoutStringBuffer() {
|
||||
_duration = DateTime.now();
|
||||
}
|
||||
|
||||
ListSearchAction input(String ch) {
|
||||
final curr = DateTime.now();
|
||||
try {
|
||||
if (curr.difference(_duration).inMilliseconds > timeoutMilliSec) {
|
||||
_buffer = ch;
|
||||
return ListSearchAction.search;
|
||||
} else {
|
||||
if (ch == _buffer) {
|
||||
return ListSearchAction.next;
|
||||
} else {
|
||||
_buffer += ch;
|
||||
return ListSearchAction.search;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
_duration = curr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,521 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
|
||||
final kMidButtonPadding = const EdgeInsets.fromLTRB(15, 0, 15, 0);
|
||||
|
||||
class _IconOP extends StatelessWidget {
|
||||
final String icon;
|
||||
final double iconWidth;
|
||||
const _IconOP({Key? key, required this.icon, required this.iconWidth})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: SvgPicture.asset(
|
||||
'assets/$icon.svg',
|
||||
width: iconWidth,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonOP extends StatelessWidget {
|
||||
final String op;
|
||||
final RxString curOP;
|
||||
final double iconWidth;
|
||||
final Color primaryColor;
|
||||
final double height;
|
||||
final Function() onTap;
|
||||
|
||||
const ButtonOP({
|
||||
Key? key,
|
||||
required this.op,
|
||||
required this.curOP,
|
||||
required this.iconWidth,
|
||||
required this.primaryColor,
|
||||
required this.height,
|
||||
required this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: height,
|
||||
padding: kMidButtonPadding,
|
||||
child: Obx(() => ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: curOP.value.isEmpty || curOP.value == op
|
||||
? primaryColor
|
||||
: Colors.grey,
|
||||
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)),
|
||||
onPressed:
|
||||
curOP.value.isEmpty || curOP.value == op ? onTap : null,
|
||||
child: Stack(children: [
|
||||
Center(child: Text('${translate("Continue with")} $op')),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SizedBox(
|
||||
width: 120,
|
||||
child: _IconOP(
|
||||
icon: op,
|
||||
iconWidth: iconWidth,
|
||||
)),
|
||||
),
|
||||
]),
|
||||
)),
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigOP {
|
||||
final String op;
|
||||
final double iconWidth;
|
||||
ConfigOP({required this.op, required this.iconWidth});
|
||||
}
|
||||
|
||||
class WidgetOP extends StatefulWidget {
|
||||
final ConfigOP config;
|
||||
final RxString curOP;
|
||||
final Function(String) cbLogin;
|
||||
const WidgetOP({
|
||||
Key? key,
|
||||
required this.config,
|
||||
required this.curOP,
|
||||
required this.cbLogin,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _WidgetOPState();
|
||||
}
|
||||
}
|
||||
|
||||
class _WidgetOPState extends State<WidgetOP> {
|
||||
Timer? _updateTimer;
|
||||
String _stateMsg = '';
|
||||
String _FailedMsg = '';
|
||||
String _url = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_updateTimer?.cancel();
|
||||
}
|
||||
|
||||
_beginQueryState() {
|
||||
_updateTimer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
_updateState();
|
||||
});
|
||||
}
|
||||
|
||||
_updateState() {
|
||||
bind.mainAccountAuthResult().then((result) {
|
||||
if (result.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final resultMap = jsonDecode(result);
|
||||
if (resultMap == null) {
|
||||
return;
|
||||
}
|
||||
final String stateMsg = resultMap['state_msg'];
|
||||
String failedMsg = resultMap['failed_msg'];
|
||||
final String? url = resultMap['url'];
|
||||
final authBody = resultMap['auth_body'];
|
||||
if (_stateMsg != stateMsg || _FailedMsg != failedMsg) {
|
||||
if (_url.isEmpty && url != null && url.isNotEmpty) {
|
||||
launchUrl(Uri.parse(url));
|
||||
_url = url;
|
||||
}
|
||||
if (authBody != null) {
|
||||
_updateTimer?.cancel();
|
||||
final String username = authBody['user']['name'];
|
||||
widget.curOP.value = '';
|
||||
widget.cbLogin(username);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_stateMsg = stateMsg;
|
||||
_FailedMsg = failedMsg;
|
||||
if (failedMsg.isNotEmpty) {
|
||||
widget.curOP.value = '';
|
||||
_updateTimer?.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_resetState() {
|
||||
_stateMsg = '';
|
||||
_FailedMsg = '';
|
||||
_url = '';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
ButtonOP(
|
||||
op: widget.config.op,
|
||||
curOP: widget.curOP,
|
||||
iconWidth: widget.config.iconWidth,
|
||||
primaryColor: str2color(widget.config.op, 0x7f),
|
||||
height: 36,
|
||||
onTap: () async {
|
||||
_resetState();
|
||||
widget.curOP.value = widget.config.op;
|
||||
await bind.mainAccountAuth(op: widget.config.op);
|
||||
_beginQueryState();
|
||||
},
|
||||
),
|
||||
Obx(() {
|
||||
if (widget.curOP.isNotEmpty &&
|
||||
widget.curOP.value != widget.config.op) {
|
||||
_FailedMsg = '';
|
||||
}
|
||||
return Offstage(
|
||||
offstage:
|
||||
_FailedMsg.isEmpty && widget.curOP.value != widget.config.op,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
_stateMsg,
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
_FailedMsg,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
}),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: widget.curOP.value != widget.config.op,
|
||||
child: const SizedBox(
|
||||
height: 5.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Offstage(
|
||||
offstage: widget.curOP.value != widget.config.op,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: 20),
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
widget.curOP.value = '';
|
||||
_updateTimer?.cancel();
|
||||
_resetState();
|
||||
bind.mainAccountAuthCancel();
|
||||
},
|
||||
child: Text(
|
||||
translate('Cancel'),
|
||||
style: TextStyle(fontSize: 15),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoginWidgetOP extends StatelessWidget {
|
||||
final List<ConfigOP> ops;
|
||||
final RxString curOP;
|
||||
final Function(String) cbLogin;
|
||||
|
||||
LoginWidgetOP({
|
||||
Key? key,
|
||||
required this.ops,
|
||||
required this.curOP,
|
||||
required this.cbLogin,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var children = ops
|
||||
.map((op) => [
|
||||
WidgetOP(
|
||||
config: op,
|
||||
curOP: curOP,
|
||||
cbLogin: cbLogin,
|
||||
),
|
||||
const Divider(
|
||||
indent: 5,
|
||||
endIndent: 5,
|
||||
)
|
||||
])
|
||||
.expand((i) => i)
|
||||
.toList();
|
||||
if (children.isNotEmpty) {
|
||||
children.removeLast();
|
||||
}
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: children,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class LoginWidgetUserPass extends StatelessWidget {
|
||||
final String username;
|
||||
final String pass;
|
||||
final String usernameMsg;
|
||||
final String passMsg;
|
||||
final bool isInProgress;
|
||||
final RxString curOP;
|
||||
final Function(String, String) onLogin;
|
||||
const LoginWidgetUserPass({
|
||||
Key? key,
|
||||
required this.username,
|
||||
required this.pass,
|
||||
required this.usernameMsg,
|
||||
required this.passMsg,
|
||||
required this.isInProgress,
|
||||
required this.curOP,
|
||||
required this.onLogin,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var userController = TextEditingController(text: username);
|
||||
var pwdController = TextEditingController(text: pass);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Container(
|
||||
padding: kMidButtonPadding,
|
||||
child: 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: userController,
|
||||
focusNode: FocusNode()..requestFocus(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Container(
|
||||
padding: kMidButtonPadding,
|
||||
child: 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()),
|
||||
const SizedBox(
|
||||
height: 12.0,
|
||||
),
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 38,
|
||||
padding: kMidButtonPadding,
|
||||
child: Obx(() => ElevatedButton(
|
||||
style: curOP.value.isEmpty || curOP.value == 'rustdesk'
|
||||
? null
|
||||
: ElevatedButton.styleFrom(
|
||||
primary: Colors.grey,
|
||||
),
|
||||
child: Text(
|
||||
translate('Login'),
|
||||
style: TextStyle(fontSize: 16),
|
||||
),
|
||||
onPressed: curOP.value.isEmpty || curOP.value == 'rustdesk'
|
||||
? () {
|
||||
onLogin(userController.text, pwdController.text);
|
||||
}
|
||||
: null,
|
||||
)),
|
||||
),
|
||||
),
|
||||
]),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// common login dialog for desktop
|
||||
/// call this directly
|
||||
Future<bool> loginDialog() async {
|
||||
String username = '';
|
||||
var usernameMsg = '';
|
||||
String pass = '';
|
||||
var passMsg = '';
|
||||
var isInProgress = false;
|
||||
var completer = Completer<bool>();
|
||||
final RxString curOP = ''.obs;
|
||||
|
||||
gFFI.dialogManager.show((setState, close) {
|
||||
cancel() {
|
||||
isInProgress = false;
|
||||
completer.complete(false);
|
||||
close();
|
||||
}
|
||||
|
||||
onLogin(String username0, String pass0) async {
|
||||
setState(() {
|
||||
usernameMsg = '';
|
||||
passMsg = '';
|
||||
isInProgress = true;
|
||||
});
|
||||
cancel() {
|
||||
curOP.value = '';
|
||||
if (isInProgress) {
|
||||
setState(() {
|
||||
isInProgress = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
curOP.value = 'rustdesk';
|
||||
username = username0;
|
||||
pass = pass0;
|
||||
if (username.isEmpty) {
|
||||
usernameMsg = translate('Username missed');
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
if (pass.isEmpty) {
|
||||
passMsg = translate('Password missed');
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final resp = await gFFI.userModel.login(username, pass);
|
||||
if (resp.containsKey('error')) {
|
||||
passMsg = resp['error'];
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
// {access_token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJndWlkIjoiMDFkZjQ2ZjgtZjg3OS00MDE0LTk5Y2QtMGMwYzM2MmViZGJlIiwiZXhwIjoxNjYxNDg2NzYwfQ.GZpe1oI8TfM5yTYNrpcwbI599P4Z_-b2GmnwNl2Lr-w,
|
||||
// token_type: Bearer, user: {id: , name: admin, email: null, note: null, status: null, grp: null, is_admin: true}}
|
||||
debugPrint('$resp');
|
||||
completer.complete(true);
|
||||
} catch (err) {
|
||||
debugPrintStack(label: err.toString());
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
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,
|
||||
),
|
||||
LoginWidgetUserPass(
|
||||
username: username,
|
||||
pass: pass,
|
||||
usernameMsg: usernameMsg,
|
||||
passMsg: passMsg,
|
||||
isInProgress: isInProgress,
|
||||
curOP: curOP,
|
||||
onLogin: onLogin,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
translate('or'),
|
||||
style: TextStyle(fontSize: 16),
|
||||
)),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
LoginWidgetOP(
|
||||
ops: [
|
||||
ConfigOP(op: 'Github', iconWidth: 20),
|
||||
ConfigOP(op: 'Google', iconWidth: 20),
|
||||
ConfigOP(op: 'Okta', iconWidth: 38),
|
||||
],
|
||||
curOP: curOP,
|
||||
cbLogin: (String username) {
|
||||
gFFI.userModel.userName.value = username;
|
||||
completer.complete(true);
|
||||
close();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [msgBoxButton(translate('Close'), cancel)],
|
||||
onCancel: cancel,
|
||||
);
|
||||
});
|
||||
return completer.future;
|
||||
}
|
||||
@@ -118,6 +118,15 @@ abstract class MenuEntryBase<T> {
|
||||
this.enabled,
|
||||
});
|
||||
List<mod_menu.PopupMenuEntry<T>> build(BuildContext context, MenuConfig conf);
|
||||
|
||||
enabledStyle(BuildContext context) => TextStyle(
|
||||
color: Theme.of(context).textTheme.titleLarge?.color,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal);
|
||||
disabledStyle() => TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal);
|
||||
}
|
||||
|
||||
class MenuEntryDivider<T> extends MenuEntryBase<T> {
|
||||
@@ -189,54 +198,76 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
||||
|
||||
mod_menu.PopupMenuEntry<T> _buildMenuItem(
|
||||
BuildContext context, MenuConfig conf, MenuEntryRadioOption opt) {
|
||||
Widget getTextChild() {
|
||||
final enabledTextChild = Text(
|
||||
opt.text,
|
||||
style: enabledStyle(context),
|
||||
);
|
||||
final disabledTextChild = Text(
|
||||
opt.text,
|
||||
style: disabledStyle(),
|
||||
);
|
||||
if (opt.enabled == null) {
|
||||
return enabledTextChild;
|
||||
} else {
|
||||
return Obx(
|
||||
() => opt.enabled!.isTrue ? enabledTextChild : disabledTextChild);
|
||||
}
|
||||
}
|
||||
|
||||
final child = Container(
|
||||
padding: padding,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
constraints:
|
||||
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
|
||||
child: Row(
|
||||
children: [
|
||||
getTextChild(),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Transform.scale(
|
||||
scale: MenuConfig.iconScale,
|
||||
child: Obx(() => opt.value == curOption.value
|
||||
? IconButton(
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.0),
|
||||
hoverColor: Colors.transparent,
|
||||
focusColor: Colors.transparent,
|
||||
onPressed: () {},
|
||||
icon: Icon(
|
||||
Icons.check,
|
||||
color: (opt.enabled ?? true.obs).isTrue
|
||||
? conf.commonColor
|
||||
: Colors.grey,
|
||||
))
|
||||
: const SizedBox.shrink()),
|
||||
))),
|
||||
],
|
||||
),
|
||||
);
|
||||
onPressed() {
|
||||
if (opt.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
setOption(opt.value);
|
||||
}
|
||||
|
||||
return mod_menu.PopupMenuItem(
|
||||
padding: EdgeInsets.zero,
|
||||
height: conf.height,
|
||||
child: Container(
|
||||
width: conf.boxWidth,
|
||||
child: TextButton(
|
||||
child: Container(
|
||||
padding: padding,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
constraints: BoxConstraints(
|
||||
minHeight: conf.height, maxHeight: conf.height),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
opt.text,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.titleLarge?.color,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal),
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Transform.scale(
|
||||
scale: MenuConfig.iconScale,
|
||||
child: Obx(() => opt.value == curOption.value
|
||||
? IconButton(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
8.0, 0.0, 8.0, 0.0),
|
||||
hoverColor: Colors.transparent,
|
||||
focusColor: Colors.transparent,
|
||||
onPressed: () {},
|
||||
icon: Icon(
|
||||
Icons.check,
|
||||
color: conf.commonColor,
|
||||
))
|
||||
: const SizedBox.shrink()),
|
||||
))),
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
if (opt.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
setOption(opt.value);
|
||||
},
|
||||
)),
|
||||
width: conf.boxWidth,
|
||||
child: opt.enabled == null
|
||||
? TextButton(
|
||||
child: child,
|
||||
onPressed: onPressed,
|
||||
)
|
||||
: Obx(() => TextButton(
|
||||
child: child,
|
||||
onPressed: opt.enabled!.isTrue ? onPressed : null,
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -567,12 +598,9 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
|
||||
const SizedBox(width: MenuConfig.midPadding),
|
||||
Obx(() => Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: super.enabled!.value
|
||||
? Theme.of(context).textTheme.titleLarge?.color
|
||||
: Colors.grey,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal),
|
||||
style: super.enabled!.value
|
||||
? enabledStyle(context)
|
||||
: disabledStyle(),
|
||||
)),
|
||||
Expanded(
|
||||
child: Align(
|
||||
@@ -605,14 +633,6 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||
);
|
||||
|
||||
Widget _buildChild(BuildContext context, MenuConfig conf) {
|
||||
final enabledStyle = TextStyle(
|
||||
color: Theme.of(context).textTheme.titleLarge?.color,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal);
|
||||
const disabledStyle = TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
fontWeight: FontWeight.normal);
|
||||
super.enabled ??= true.obs;
|
||||
return Obx(() => Container(
|
||||
width: conf.boxWidth,
|
||||
@@ -631,7 +651,7 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||
constraints:
|
||||
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
|
||||
child: childBuilder(
|
||||
super.enabled!.value ? enabledStyle : disabledStyle),
|
||||
super.enabled!.value ? enabledStyle(context) : disabledStyle()),
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import '../../models/platform_model.dart';
|
||||
import '../../common/shared_state.dart';
|
||||
import './popup_menu.dart';
|
||||
import './material_mod_popup_menu.dart' as mod_menu;
|
||||
import './kb_layout_type_chooser.dart';
|
||||
|
||||
class MenubarState {
|
||||
final kStoreKey = 'remoteMenubarState';
|
||||
@@ -171,6 +172,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// No need to use future builder here.
|
||||
_updateScreen();
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Obx(() => show.value
|
||||
@@ -362,8 +365,6 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
RxInt display = CurrentDisplayState.find(widget.id);
|
||||
if (display.value != i) {
|
||||
bind.sessionSwitchDisplay(id: widget.id, value: i);
|
||||
pi.currentDisplay = i;
|
||||
display.value = i;
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -569,7 +570,8 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
),
|
||||
]);
|
||||
// {handler.get_audit_server() && <li #note>{translate('Note')}</li>}
|
||||
final auditServer = bind.sessionGetAuditServerSync(id: widget.id);
|
||||
final auditServer =
|
||||
bind.sessionGetAuditServerSync(id: widget.id, typ: "conn");
|
||||
if (auditServer.isNotEmpty) {
|
||||
displayMenu.add(
|
||||
MenuEntryButton<String>(
|
||||
@@ -587,7 +589,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
}
|
||||
displayMenu.add(MenuEntryDivider());
|
||||
if (perms['keyboard'] != false) {
|
||||
if (pi.platform == 'Linux' || pi.sasEnabled) {
|
||||
if (pi.platform == kPeerPlatformLinux || pi.sasEnabled) {
|
||||
displayMenu.add(MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
'${translate("Insert")} Ctrl + Alt + Del',
|
||||
@@ -602,9 +604,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
}
|
||||
}
|
||||
if (perms['restart'] != false &&
|
||||
(pi.platform == 'Linux' ||
|
||||
pi.platform == 'Windows' ||
|
||||
pi.platform == 'Mac OS')) {
|
||||
(pi.platform == kPeerPlatformLinux ||
|
||||
pi.platform == kPeerPlatformWindows ||
|
||||
pi.platform == kPeerPlatformMacOS)) {
|
||||
displayMenu.add(MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
translate('Restart Remote Device'),
|
||||
@@ -631,7 +633,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
|
||||
if (pi.platform == 'Windows') {
|
||||
if (pi.platform == kPeerPlatformWindows) {
|
||||
displayMenu.add(MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Obx(() => Text(
|
||||
translate(
|
||||
@@ -697,12 +699,12 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
if (_screen == null) {
|
||||
return false;
|
||||
}
|
||||
double scale = _screen!.scaleFactor;
|
||||
double selfWidth = _screen!.frame.width;
|
||||
double selfHeight = _screen!.frame.height;
|
||||
final scale = kIgnoreDpi ? 1.0 : _screen!.scaleFactor;
|
||||
double selfWidth = _screen!.visibleFrame.width;
|
||||
double selfHeight = _screen!.visibleFrame.height;
|
||||
if (isFullscreen) {
|
||||
selfWidth = _screen!.visibleFrame.width;
|
||||
selfHeight = _screen!.visibleFrame.height;
|
||||
selfWidth = _screen!.frame.width;
|
||||
selfHeight = _screen!.frame.height;
|
||||
}
|
||||
|
||||
final canvasModel = widget.ffi.canvasModel;
|
||||
@@ -827,7 +829,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
qualityInitValue = qualityMaxValue;
|
||||
}
|
||||
final RxDouble qualitySliderValue = RxDouble(qualityInitValue);
|
||||
final debouncerQuanlity = Debouncer<double>(
|
||||
final debouncerQuality = Debouncer<double>(
|
||||
Duration(milliseconds: 1000),
|
||||
onChanged: (double v) {
|
||||
setCustomValues(quality: v);
|
||||
@@ -843,7 +845,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
divisions: 90,
|
||||
onChanged: (double value) {
|
||||
qualitySliderValue.value = value;
|
||||
debouncerQuanlity.value = value;
|
||||
debouncerQuality.value = value;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
@@ -934,11 +936,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
text: translate('ScrollAuto'),
|
||||
value: kRemoteScrollStyleAuto,
|
||||
dismissOnClicked: true,
|
||||
enabled: widget.ffi.canvasModel.imageOverflow,
|
||||
),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scrollbar'),
|
||||
value: kRemoteScrollStyleBar,
|
||||
dismissOnClicked: true,
|
||||
enabled: widget.ffi.canvasModel.imageOverflow,
|
||||
),
|
||||
],
|
||||
curOptionGetter: () async =>
|
||||
@@ -952,75 +956,77 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
displayMenu.insert(3, MenuEntryDivider<String>());
|
||||
}
|
||||
|
||||
if (_isWindowCanBeAdjusted(remoteCount)) {
|
||||
displayMenu.insert(
|
||||
0,
|
||||
MenuEntryDivider<String>(),
|
||||
);
|
||||
displayMenu.insert(
|
||||
0,
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Container(
|
||||
child: Text(
|
||||
translate('Adjust Window'),
|
||||
style: style,
|
||||
)),
|
||||
proc: () {
|
||||
() async {
|
||||
await _updateScreen();
|
||||
if (_screen != null) {
|
||||
_setFullscreen(false);
|
||||
double scale = _screen!.scaleFactor;
|
||||
final wndRect =
|
||||
await WindowController.fromWindowId(windowId).getFrame();
|
||||
final mediaSize = MediaQueryData.fromWindow(ui.window).size;
|
||||
// On windows, wndRect is equal to GetWindowRect and mediaSize is equal to GetClientRect.
|
||||
// https://stackoverflow.com/a/7561083
|
||||
double magicWidth =
|
||||
wndRect.right - wndRect.left - mediaSize.width * scale;
|
||||
double magicHeight =
|
||||
wndRect.bottom - wndRect.top - mediaSize.height * scale;
|
||||
if (_isWindowCanBeAdjusted(remoteCount)) {
|
||||
displayMenu.insert(
|
||||
0,
|
||||
MenuEntryDivider<String>(),
|
||||
);
|
||||
displayMenu.insert(
|
||||
0,
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Container(
|
||||
child: Text(
|
||||
translate('Adjust Window'),
|
||||
style: style,
|
||||
)),
|
||||
proc: () {
|
||||
() async {
|
||||
await _updateScreen();
|
||||
if (_screen != null) {
|
||||
_setFullscreen(false);
|
||||
double scale = _screen!.scaleFactor;
|
||||
final wndRect =
|
||||
await WindowController.fromWindowId(windowId).getFrame();
|
||||
final mediaSize = MediaQueryData.fromWindow(ui.window).size;
|
||||
// On windows, wndRect is equal to GetWindowRect and mediaSize is equal to GetClientRect.
|
||||
// https://stackoverflow.com/a/7561083
|
||||
double magicWidth =
|
||||
wndRect.right - wndRect.left - mediaSize.width * scale;
|
||||
double magicHeight =
|
||||
wndRect.bottom - wndRect.top - mediaSize.height * scale;
|
||||
|
||||
final canvasModel = widget.ffi.canvasModel;
|
||||
final width = (canvasModel.getDisplayWidth() +
|
||||
canvasModel.windowBorderWidth * 2) *
|
||||
scale +
|
||||
magicWidth;
|
||||
final height = (canvasModel.getDisplayHeight() +
|
||||
canvasModel.tabBarHeight +
|
||||
canvasModel.windowBorderWidth * 2) *
|
||||
scale +
|
||||
magicHeight;
|
||||
double left = wndRect.left + (wndRect.width - width) / 2;
|
||||
double top = wndRect.top + (wndRect.height - height) / 2;
|
||||
final canvasModel = widget.ffi.canvasModel;
|
||||
final width =
|
||||
(canvasModel.getDisplayWidth() * canvasModel.scale +
|
||||
canvasModel.windowBorderWidth * 2) *
|
||||
scale +
|
||||
magicWidth;
|
||||
final height =
|
||||
(canvasModel.getDisplayHeight() * canvasModel.scale +
|
||||
canvasModel.tabBarHeight +
|
||||
canvasModel.windowBorderWidth * 2) *
|
||||
scale +
|
||||
magicHeight;
|
||||
double left = wndRect.left + (wndRect.width - width) / 2;
|
||||
double top = wndRect.top + (wndRect.height - height) / 2;
|
||||
|
||||
Rect frameRect = _screen!.frame;
|
||||
if (!isFullscreen) {
|
||||
frameRect = _screen!.visibleFrame;
|
||||
Rect frameRect = _screen!.frame;
|
||||
if (!isFullscreen) {
|
||||
frameRect = _screen!.visibleFrame;
|
||||
}
|
||||
if (left < frameRect.left) {
|
||||
left = frameRect.left;
|
||||
}
|
||||
if (top < frameRect.top) {
|
||||
top = frameRect.top;
|
||||
}
|
||||
if ((left + width) > frameRect.right) {
|
||||
left = frameRect.right - width;
|
||||
}
|
||||
if ((top + height) > frameRect.bottom) {
|
||||
top = frameRect.bottom - height;
|
||||
}
|
||||
await WindowController.fromWindowId(windowId)
|
||||
.setFrame(Rect.fromLTWH(left, top, width, height));
|
||||
}
|
||||
if (left < frameRect.left) {
|
||||
left = frameRect.left;
|
||||
}
|
||||
if (top < frameRect.top) {
|
||||
top = frameRect.top;
|
||||
}
|
||||
if ((left + width) > frameRect.right) {
|
||||
left = frameRect.right - width;
|
||||
}
|
||||
if ((top + height) > frameRect.bottom) {
|
||||
top = frameRect.bottom - height;
|
||||
}
|
||||
await WindowController.fromWindowId(windowId)
|
||||
.setFrame(Rect.fromLTWH(left, top, width, height));
|
||||
}
|
||||
}();
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
);
|
||||
}();
|
||||
},
|
||||
padding: padding,
|
||||
dismissOnClicked: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Show Codec Preference
|
||||
@@ -1032,7 +1038,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
final h265 = codecsJson['h265'] ?? false;
|
||||
codecs.add(h264);
|
||||
codecs.add(h265);
|
||||
} finally {}
|
||||
} catch (e) {
|
||||
debugPrint("Show Codec Preference err=$e");
|
||||
}
|
||||
if (codecs.length == 2 && (codecs[0] || codecs[1])) {
|
||||
displayMenu.add(MenuEntryRadios<String>(
|
||||
text: translate('Codec Preference'),
|
||||
@@ -1082,7 +1090,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
}
|
||||
|
||||
/// Show remote cursor
|
||||
if (!widget.ffi.canvasModel.cursorEmbeded) {
|
||||
if (!widget.ffi.canvasModel.cursorEmbedded) {
|
||||
displayMenu.add(() {
|
||||
final state = ShowRemoteCursorState.find(widget.id);
|
||||
return MenuEntrySwitch2<String>(
|
||||
@@ -1149,7 +1157,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
}
|
||||
|
||||
if (Platform.isWindows &&
|
||||
pi.platform == 'Windows' &&
|
||||
pi.platform == kPeerPlatformWindows &&
|
||||
perms['file'] != false) {
|
||||
displayMenu.add(_createSwitchMenuEntry(
|
||||
'Allow file copy and paste', 'enable-file-transfer', padding, true));
|
||||
@@ -1182,7 +1190,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
}
|
||||
|
||||
List<MenuEntryBase<String>> _getKeyboardMenu() {
|
||||
final keyboardMenu = [
|
||||
final List<MenuEntryBase<String>> keyboardMenu = [
|
||||
MenuEntryRadios<String>(
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () {
|
||||
@@ -1209,11 +1217,58 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
},
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionSetKeyboardMode(id: widget.id, value: newValue);
|
||||
widget.ffi.canvasModel.updateViewStyle();
|
||||
},
|
||||
)
|
||||
];
|
||||
|
||||
final localPlatform =
|
||||
getLocalPlatformForKBLayoutType(widget.ffi.ffiModel.pi.platform);
|
||||
if (localPlatform != '') {
|
||||
keyboardMenu.add(MenuEntryDivider());
|
||||
keyboardMenu.add(
|
||||
MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
height: _MenubarTheme.height,
|
||||
child: Row(
|
||||
children: [
|
||||
Obx(() => RichText(
|
||||
text: TextSpan(
|
||||
text: '${translate('Local keyboard type')}: ',
|
||||
style: DefaultTextStyle.of(context).style,
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: KBLayoutType.value,
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () {
|
||||
if (Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
showKBLayoutTypeChooser(
|
||||
localPlatform, widget.ffi.dialogManager);
|
||||
},
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
)),
|
||||
proc: () {},
|
||||
padding: EdgeInsets.zero,
|
||||
dismissOnClicked: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
return keyboardMenu;
|
||||
}
|
||||
|
||||
@@ -1373,10 +1428,10 @@ class _DraggableShowHide extends StatefulWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_DraggableShowHide> createState() => __DraggableShowHideState();
|
||||
State<_DraggableShowHide> createState() => _DraggableShowHideState();
|
||||
}
|
||||
|
||||
class __DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
Offset position = Offset.zero;
|
||||
Size size = Size.zero;
|
||||
|
||||
@@ -1385,7 +1440,8 @@ class __DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
axis: Axis.horizontal,
|
||||
child: Icon(
|
||||
Icons.drag_indicator,
|
||||
size: 15,
|
||||
size: 20,
|
||||
color: Colors.grey,
|
||||
),
|
||||
feedback: widget,
|
||||
onDragStarted: (() {
|
||||
@@ -1428,7 +1484,7 @@ class __DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
}),
|
||||
child: Obx((() => Icon(
|
||||
widget.show.isTrue ? Icons.expand_less : Icons.expand_more,
|
||||
size: 15,
|
||||
size: 20,
|
||||
))),
|
||||
),
|
||||
],
|
||||
@@ -1441,7 +1497,7 @@ class __DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
border: Border.all(color: MyTheme.border),
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 15,
|
||||
height: 20,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -331,6 +331,7 @@ class DesktopTab extends StatelessWidget {
|
||||
return _buildBlock(
|
||||
child: Obx(() => PageView(
|
||||
controller: state.value.pageController,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: state.value.tabs
|
||||
.map((tab) => tab.page)
|
||||
.toList(growable: false))));
|
||||
@@ -526,13 +527,19 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
||||
void onWindowClose() async {
|
||||
// hide window on close
|
||||
if (widget.isMainWindow) {
|
||||
await rustDeskWinManager.unregisterActiveWindow(0);
|
||||
// `hide` must be placed after unregisterActiveWindow, because once all windows are hidden,
|
||||
// flutter closes the application on macOS. We should ensure the post-run logic has ran successfully.
|
||||
// e.g.: saving window position.
|
||||
await windowManager.hide();
|
||||
rustDeskWinManager.unregisterActiveWindow(0);
|
||||
} else {
|
||||
widget.onClose?.call();
|
||||
// it's safe to hide the subwindow
|
||||
await WindowController.fromWindowId(windowId!).hide();
|
||||
rustDeskWinManager
|
||||
.call(WindowType.Main, kWindowEventHide, {"id": windowId!});
|
||||
await Future.wait([
|
||||
rustDeskWinManager
|
||||
.call(WindowType.Main, kWindowEventHide, {"id": windowId!}),
|
||||
widget.onClose?.call() ?? Future.microtask(() => null)
|
||||
]);
|
||||
}
|
||||
super.onWindowClose();
|
||||
}
|
||||
@@ -899,7 +906,7 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
||||
children: [
|
||||
_buildTabContent(),
|
||||
Obx((() => _CloseButton(
|
||||
visiable: hover.value && widget.closable,
|
||||
visible: hover.value && widget.closable,
|
||||
tabSelected: isSelected,
|
||||
onClose: () => widget.onClose(),
|
||||
)))
|
||||
@@ -931,13 +938,13 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
||||
}
|
||||
|
||||
class _CloseButton extends StatelessWidget {
|
||||
final bool visiable;
|
||||
final bool visible;
|
||||
final bool tabSelected;
|
||||
final Function onClose;
|
||||
|
||||
const _CloseButton({
|
||||
Key? key,
|
||||
required this.visiable,
|
||||
required this.visible,
|
||||
required this.tabSelected,
|
||||
required this.onClose,
|
||||
}) : super(key: key);
|
||||
@@ -947,7 +954,7 @@ class _CloseButton extends StatelessWidget {
|
||||
return SizedBox(
|
||||
width: _kIconSize,
|
||||
child: Offstage(
|
||||
offstage: !visiable,
|
||||
offstage: !visible,
|
||||
child: InkWell(
|
||||
customBorder: const RoundedRectangleBorder(),
|
||||
onTap: () => onClose(),
|
||||
|
||||
Reference in New Issue
Block a user