flutter_desktop: password menu
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
parent
a50482af5c
commit
70c4726766
flutter/lib
@ -1,7 +1,8 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../consts.dart';
|
import '../consts.dart';
|
||||||
import '../models/platform_model.dart';
|
|
||||||
|
// TODO: A lot of dup code.
|
||||||
|
|
||||||
class PrivacyModeState {
|
class PrivacyModeState {
|
||||||
static String tag(String id) => 'privacy_mode_$id';
|
static String tag(String id) => 'privacy_mode_$id';
|
||||||
@ -156,3 +157,25 @@ class KeyboardEnabledState {
|
|||||||
|
|
||||||
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
|
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RemoteCursorMovedState {
|
||||||
|
static String tag(String id) => 'remote_cursor_moved_$id';
|
||||||
|
|
||||||
|
static void init(String id) {
|
||||||
|
final key = tag(id);
|
||||||
|
if (!Get.isRegistered(tag: key)) {
|
||||||
|
// Server side, default true
|
||||||
|
final RxBool state = false.obs;
|
||||||
|
Get.put(state, tag: key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void delete(String id) {
|
||||||
|
final key = tag(id);
|
||||||
|
if (Get.isRegistered(tag: key)) {
|
||||||
|
Get.delete(tag: key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
|
||||||
|
}
|
||||||
|
@ -19,18 +19,18 @@ import 'package:tray_manager/tray_manager.dart';
|
|||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
class _PopupMenuTheme {
|
class _MenubarTheme {
|
||||||
static const Color commonColor = MyTheme.accent;
|
static const Color commonColor = MyTheme.accent;
|
||||||
// kMinInteractiveDimension
|
// kMinInteractiveDimension
|
||||||
static const double height = 25.0;
|
static const double height = 25.0;
|
||||||
static const double dividerHeight = 3.0;
|
static const double dividerHeight = 12.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DesktopHomePage extends StatefulWidget {
|
class DesktopHomePage extends StatefulWidget {
|
||||||
const DesktopHomePage({Key? key}) : super(key: key);
|
const DesktopHomePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _DesktopHomePageState();
|
State<DesktopHomePage> createState() => _DesktopHomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
const borderColor = Color(0xFF2F65BA);
|
const borderColor = Color(0xFF2F65BA);
|
||||||
@ -93,7 +93,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
buildServerBoard(BuildContext context) {
|
buildServerBoard(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
color: MyTheme.color(context).grayBg,
|
color: MyTheme.color(context).grayBg,
|
||||||
child: const ConnectionPage(),
|
child: ConnectionPage(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
Container(
|
||||||
height: 25,
|
height: 25,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@ -142,11 +142,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: model.serverId,
|
controller: model.serverId,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.only(bottom: 20),
|
contentPadding: EdgeInsets.only(bottom: 20),
|
||||||
),
|
),
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -161,190 +161,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<MenuEntryBase<String>> _genSwitchEntry(
|
|
||||||
// String label, String key) async {
|
|
||||||
|
|
||||||
// final v = await bind.mainGetOption(key: key);
|
|
||||||
// bool enable;
|
|
||||||
// if (key == "stop-service") {
|
|
||||||
// enable = v != "Y";
|
|
||||||
// } else if (key.startsWith("allow-")) {
|
|
||||||
// enable = v == "Y";
|
|
||||||
// } else {
|
|
||||||
// enable = v != "N";
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return PopupMenuItem(
|
|
||||||
// child: Row(
|
|
||||||
// children: [
|
|
||||||
// Icon(Icons.check,
|
|
||||||
// color: enable ? null : MyTheme.accent.withAlpha(00)),
|
|
||||||
// Text(
|
|
||||||
// label,
|
|
||||||
// style: genTextStyle(enable),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// value: key,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
_popupMenu(BuildContext context, RelativeRect position) async {
|
|
||||||
TextStyle styleEnabled = const TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: MenuConfig.fontSize,
|
|
||||||
fontWeight: FontWeight.normal);
|
|
||||||
TextStyle styleDisabled = const TextStyle(
|
|
||||||
color: Colors.redAccent,
|
|
||||||
fontSize: MenuConfig.fontSize,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
decoration: TextDecoration.lineThrough);
|
|
||||||
|
|
||||||
enabledEntry(String label, String key) {
|
|
||||||
Rx<TextStyle> textStyle = styleEnabled.obs;
|
|
||||||
return MenuEntrySwitch<String>(
|
|
||||||
text: translate(label),
|
|
||||||
textStyle: textStyle,
|
|
||||||
getter: () async {
|
|
||||||
final opt = await bind.mainGetOption(key: key);
|
|
||||||
bool enabled;
|
|
||||||
if (key == 'stop-service') {
|
|
||||||
enabled = opt != 'Y';
|
|
||||||
} else if (key.startsWith("allow-")) {
|
|
||||||
enabled = opt == 'Y';
|
|
||||||
} else {
|
|
||||||
enabled = opt != 'N';
|
|
||||||
}
|
|
||||||
textStyle.value = enabled ? styleEnabled : styleDisabled;
|
|
||||||
return enabled;
|
|
||||||
},
|
|
||||||
setter: (bool v) async {
|
|
||||||
String opt;
|
|
||||||
if (key == 'stop-service') {
|
|
||||||
opt = v ? 'Y' : '';
|
|
||||||
} else if (key.startsWith("allow-")) {
|
|
||||||
opt = v ? 'Y' : '';
|
|
||||||
} else {
|
|
||||||
opt = v ? '' : 'N';
|
|
||||||
}
|
|
||||||
await bind.mainSetOption(key: key, value: opt);
|
|
||||||
if (key == 'allow-darktheme') {
|
|
||||||
changeTheme(opt);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissOnClicked: false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final userName = await gFFI.userModel.getUserName();
|
|
||||||
final enabledInput = await bind.mainGetOption(key: 'enable-audio');
|
|
||||||
final defaultInput = await gFFI.getDefaultAudioInput();
|
|
||||||
|
|
||||||
final List<MenuEntryBase<String>> menu = <MenuEntryBase<String>>[
|
|
||||||
enabledEntry('Enable Keyboard/Mouse', 'enable-keyboard'),
|
|
||||||
enabledEntry('Enable Clipboard', 'enable-clipboard'),
|
|
||||||
enabledEntry('Enable File Transfer', 'enable-file-transfer'),
|
|
||||||
enabledEntry('Enable TCP Tunneling', 'enable-tunnel'),
|
|
||||||
// TODO: audio sub menu?
|
|
||||||
// genAudioInputPopupMenuItem(enabledInput != "N", defaultInput),
|
|
||||||
MenuEntryDivider(),
|
|
||||||
MenuEntryButton(
|
|
||||||
childBuilder: (TextStyle? style) => Text(
|
|
||||||
translate('ID/Relay Server'),
|
|
||||||
style: style,
|
|
||||||
),
|
|
||||||
proc: () {
|
|
||||||
changeServer();
|
|
||||||
},
|
|
||||||
dismissOnClicked: true,
|
|
||||||
),
|
|
||||||
MenuEntryButton(
|
|
||||||
childBuilder: (TextStyle? style) => Text(
|
|
||||||
translate('IP Whitelisting'),
|
|
||||||
style: style,
|
|
||||||
),
|
|
||||||
proc: () {
|
|
||||||
changeWhiteList();
|
|
||||||
},
|
|
||||||
dismissOnClicked: true,
|
|
||||||
),
|
|
||||||
MenuEntryButton(
|
|
||||||
childBuilder: (TextStyle? style) => Text(
|
|
||||||
translate('Socks5 Proxy'),
|
|
||||||
style: style,
|
|
||||||
),
|
|
||||||
proc: () {
|
|
||||||
changeSocks5Proxy();
|
|
||||||
},
|
|
||||||
dismissOnClicked: true,
|
|
||||||
),
|
|
||||||
MenuEntryDivider(),
|
|
||||||
enabledEntry('Enable Service', 'stop-service'),
|
|
||||||
enabledEntry('Always connected via relay', 'allow-always-relay'),
|
|
||||||
// FIXME: is this option correct?
|
|
||||||
enabledEntry('Start ID/relay service', 'stop-rendezvous-service'),
|
|
||||||
MenuEntryDivider(),
|
|
||||||
userName.isEmpty
|
|
||||||
? MenuEntryButton(
|
|
||||||
childBuilder: (TextStyle? style) => Text(
|
|
||||||
translate('Login'),
|
|
||||||
style: style,
|
|
||||||
),
|
|
||||||
proc: () {
|
|
||||||
login();
|
|
||||||
},
|
|
||||||
dismissOnClicked: true,
|
|
||||||
)
|
|
||||||
: MenuEntryButton(
|
|
||||||
childBuilder: (TextStyle? style) => Text(
|
|
||||||
translate('Logout'),
|
|
||||||
style: style,
|
|
||||||
),
|
|
||||||
proc: () {
|
|
||||||
logOut();
|
|
||||||
},
|
|
||||||
dismissOnClicked: true,
|
|
||||||
),
|
|
||||||
MenuEntryButton(
|
|
||||||
childBuilder: (TextStyle? style) => Text(
|
|
||||||
translate('Change ID'),
|
|
||||||
style: style,
|
|
||||||
),
|
|
||||||
proc: () {
|
|
||||||
changeId();
|
|
||||||
},
|
|
||||||
dismissOnClicked: true,
|
|
||||||
),
|
|
||||||
MenuEntryDivider(),
|
|
||||||
enabledEntry('Dark Theme', 'allow-darktheme'),
|
|
||||||
MenuEntryButton(
|
|
||||||
childBuilder: (TextStyle? style) => Text(
|
|
||||||
translate('About'),
|
|
||||||
style: style,
|
|
||||||
),
|
|
||||||
proc: () {
|
|
||||||
about();
|
|
||||||
},
|
|
||||||
dismissOnClicked: true,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
await mod_menu.showMenu(
|
|
||||||
context: context,
|
|
||||||
position: position,
|
|
||||||
items: menu
|
|
||||||
.map((e) => e.build(
|
|
||||||
context,
|
|
||||||
const MenuConfig(
|
|
||||||
commonColor: _PopupMenuTheme.commonColor,
|
|
||||||
height: _PopupMenuTheme.height,
|
|
||||||
dividerHeight: _PopupMenuTheme.dividerHeight)))
|
|
||||||
.expand((i) => i)
|
|
||||||
.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildPopupMenu(BuildContext context) {
|
Widget buildPopupMenu(BuildContext context) {
|
||||||
RelativeRect position = const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0);
|
var position;
|
||||||
RxBool hover = false.obs;
|
RxBool hover = false.obs;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTapDown: (detail) {
|
onTapDown: (detail) {
|
||||||
@ -353,7 +171,83 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
position = RelativeRect.fromLTRB(x, y, x, y);
|
position = RelativeRect.fromLTRB(x, y, x, y);
|
||||||
},
|
},
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await _popupMenu(context, position);
|
final userName = await gFFI.userModel.getUserName();
|
||||||
|
final enabledInput = await bind.mainGetOption(key: 'enable-audio');
|
||||||
|
final defaultInput = await gFFI.getDefaultAudioInput();
|
||||||
|
var menu = <PopupMenuEntry>[
|
||||||
|
await genEnablePopupMenuItem(
|
||||||
|
translate("Enable Keyboard/Mouse"),
|
||||||
|
'enable-keyboard',
|
||||||
|
),
|
||||||
|
await genEnablePopupMenuItem(
|
||||||
|
translate("Enable Clipboard"),
|
||||||
|
'enable-clipboard',
|
||||||
|
),
|
||||||
|
await genEnablePopupMenuItem(
|
||||||
|
translate("Enable File Transfer"),
|
||||||
|
'enable-file-transfer',
|
||||||
|
),
|
||||||
|
await genEnablePopupMenuItem(
|
||||||
|
translate("Enable TCP Tunneling"),
|
||||||
|
'enable-tunnel',
|
||||||
|
),
|
||||||
|
genAudioInputPopupMenuItem(enabledInput != "N", defaultInput),
|
||||||
|
PopupMenuDivider(),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Text(translate("ID/Relay Server")),
|
||||||
|
value: 'custom-server',
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Text(translate("IP Whitelisting")),
|
||||||
|
value: 'whitelist',
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Text(translate("Socks5 Proxy")),
|
||||||
|
value: 'socks5-proxy',
|
||||||
|
),
|
||||||
|
PopupMenuDivider(),
|
||||||
|
await genEnablePopupMenuItem(
|
||||||
|
translate("Enable Service"),
|
||||||
|
'stop-service',
|
||||||
|
),
|
||||||
|
// TODO: direct server
|
||||||
|
await genEnablePopupMenuItem(
|
||||||
|
translate("Always connected via relay"),
|
||||||
|
'allow-always-relay',
|
||||||
|
),
|
||||||
|
await genEnablePopupMenuItem(
|
||||||
|
translate("Start ID/relay service"),
|
||||||
|
'stop-rendezvous-service',
|
||||||
|
),
|
||||||
|
PopupMenuDivider(),
|
||||||
|
userName.isEmpty
|
||||||
|
? PopupMenuItem(
|
||||||
|
child: Text(translate("Login")),
|
||||||
|
value: 'login',
|
||||||
|
)
|
||||||
|
: PopupMenuItem(
|
||||||
|
child: Text("${translate("Logout")} $userName"),
|
||||||
|
value: 'logout',
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Text(translate("Change ID")),
|
||||||
|
value: 'change-id',
|
||||||
|
),
|
||||||
|
PopupMenuDivider(),
|
||||||
|
await genEnablePopupMenuItem(
|
||||||
|
translate("Dark Theme"),
|
||||||
|
'allow-darktheme',
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Text(translate("About")),
|
||||||
|
value: 'about',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
final v =
|
||||||
|
await showMenu(context: context, position: position, items: menu);
|
||||||
|
if (v != null) {
|
||||||
|
onSelectMenu(v);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => CircleAvatar(
|
() => CircleAvatar(
|
||||||
@ -435,18 +329,19 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
onTap: () => bind.mainUpdateTemporaryPassword(),
|
onTap: () => bind.mainUpdateTemporaryPassword(),
|
||||||
onHover: (value) => refreshHover.value = value,
|
onHover: (value) => refreshHover.value = value,
|
||||||
),
|
),
|
||||||
FutureBuilder<Widget>(
|
const _PasswordPopupMenu(),
|
||||||
future: buildPasswordPopupMenu(context),
|
// FutureBuilder<Widget>(
|
||||||
builder: (context, snapshot) {
|
// future: buildPasswordPopupMenu(context),
|
||||||
if (snapshot.hasError) {
|
// builder: (context, snapshot) {
|
||||||
print("${snapshot.error}");
|
// if (snapshot.hasError) {
|
||||||
}
|
// print("${snapshot.error}");
|
||||||
if (snapshot.hasData) {
|
// }
|
||||||
return snapshot.data!;
|
// if (snapshot.hasData) {
|
||||||
} else {
|
// return snapshot.data!;
|
||||||
return Offstage();
|
// } else {
|
||||||
}
|
// return Offstage();
|
||||||
})
|
// }
|
||||||
|
// })
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -479,7 +374,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () => gFFI.serverModel.verificationMethod = value,
|
onTap: () => gFFI.serverModel.setVerificationMethod(value),
|
||||||
);
|
);
|
||||||
final temporary_enabled =
|
final temporary_enabled =
|
||||||
gFFI.serverModel.verificationMethod != kUsePermanentPassword;
|
gFFI.serverModel.verificationMethod != kUsePermanentPassword;
|
||||||
@ -516,8 +411,11 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
if (gFFI.serverModel.temporaryPasswordLength !=
|
if (gFFI.serverModel.temporaryPasswordLength !=
|
||||||
e) {
|
e) {
|
||||||
gFFI.serverModel.temporaryPasswordLength = e;
|
() async {
|
||||||
bind.mainUpdateTemporaryPassword();
|
await gFFI.serverModel
|
||||||
|
.setTemporaryPasswordLength(e);
|
||||||
|
await bind.mainUpdateTemporaryPassword();
|
||||||
|
}();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@ -1148,3 +1046,120 @@ void setPasswordDialog() async {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PasswordPopupMenu extends StatefulWidget {
|
||||||
|
const _PasswordPopupMenu({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_PasswordPopupMenu> createState() => _PasswordPopupMenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PasswordPopupMenuState extends State<_PasswordPopupMenu> {
|
||||||
|
final RxBool _tempEnabled = true.obs;
|
||||||
|
final RxBool _permEnabled = true.obs;
|
||||||
|
|
||||||
|
List<MenuEntryBase<String>> _buildMenus() {
|
||||||
|
return <MenuEntryBase<String>>[
|
||||||
|
MenuEntryRadios<String>(
|
||||||
|
text: translate('Password type'),
|
||||||
|
optionsGetter: () => [
|
||||||
|
MenuEntryRadioOption(
|
||||||
|
text: translate('Use temporary password'),
|
||||||
|
value: kUseTemporaryPassword),
|
||||||
|
MenuEntryRadioOption(
|
||||||
|
text: translate('Use permanent password'),
|
||||||
|
value: kUsePermanentPassword),
|
||||||
|
MenuEntryRadioOption(
|
||||||
|
text: translate('Use both passwords'),
|
||||||
|
value: kUseBothPasswords),
|
||||||
|
],
|
||||||
|
curOptionGetter: () async {
|
||||||
|
return gFFI.serverModel.verificationMethod;
|
||||||
|
},
|
||||||
|
optionSetter: (String oldValue, String newValue) async {
|
||||||
|
await bind.mainSetOption(
|
||||||
|
key: "verification-method", value: newValue);
|
||||||
|
await gFFI.serverModel.updatePasswordModel();
|
||||||
|
setState(() {
|
||||||
|
_tempEnabled.value =
|
||||||
|
gFFI.serverModel.verificationMethod != kUsePermanentPassword;
|
||||||
|
_permEnabled.value =
|
||||||
|
gFFI.serverModel.verificationMethod != kUseTemporaryPassword;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
MenuEntryDivider(),
|
||||||
|
MenuEntryButton<String>(
|
||||||
|
enabled: _permEnabled,
|
||||||
|
childBuilder: (TextStyle? style) => Text(
|
||||||
|
translate('Set permanent password'),
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
proc: () {
|
||||||
|
setPasswordDialog();
|
||||||
|
},
|
||||||
|
dismissOnClicked: true,
|
||||||
|
),
|
||||||
|
MenuEntrySubMenu(
|
||||||
|
enabled: _tempEnabled,
|
||||||
|
text: translate('Set temporary password length'),
|
||||||
|
entries: [
|
||||||
|
MenuEntryRadios<String>(
|
||||||
|
enabled: _tempEnabled,
|
||||||
|
text: translate(''),
|
||||||
|
optionsGetter: () => [
|
||||||
|
MenuEntryRadioOption(
|
||||||
|
text: translate('6'),
|
||||||
|
value: '6',
|
||||||
|
enabled: _tempEnabled,
|
||||||
|
),
|
||||||
|
MenuEntryRadioOption(
|
||||||
|
text: translate('8'),
|
||||||
|
value: '8',
|
||||||
|
enabled: _tempEnabled,
|
||||||
|
),
|
||||||
|
MenuEntryRadioOption(
|
||||||
|
text: translate('10'),
|
||||||
|
value: '10',
|
||||||
|
enabled: _tempEnabled,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
curOptionGetter: () async {
|
||||||
|
return gFFI.serverModel.temporaryPasswordLength;
|
||||||
|
},
|
||||||
|
optionSetter: (String oldValue, String newValue) async {
|
||||||
|
if (oldValue != newValue) {
|
||||||
|
await gFFI.serverModel.setTemporaryPasswordLength(newValue);
|
||||||
|
await gFFI.serverModel.updatePasswordModel();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final editHover = false.obs;
|
||||||
|
return mod_menu.PopupMenuButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
onHover: (v) => editHover.value = v,
|
||||||
|
tooltip: translate(''),
|
||||||
|
position: mod_menu.PopupMenuPosition.overSide,
|
||||||
|
itemBuilder: (BuildContext context) => _buildMenus()
|
||||||
|
.map((entry) => entry.build(
|
||||||
|
context,
|
||||||
|
const MenuConfig(
|
||||||
|
commonColor: _MenubarTheme.commonColor,
|
||||||
|
height: _MenubarTheme.height,
|
||||||
|
dividerHeight: _MenubarTheme.dividerHeight,
|
||||||
|
)))
|
||||||
|
.expand((i) => i)
|
||||||
|
.toList(),
|
||||||
|
child: Obx(() => Icon(Icons.edit,
|
||||||
|
size: 22,
|
||||||
|
color: editHover.value
|
||||||
|
? MyTheme.color(context).text
|
||||||
|
: const Color(0xFFDDDDDD))
|
||||||
|
.marginOnly(bottom: 2)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -313,8 +313,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
translate("Use permanent password"),
|
translate("Use permanent password"),
|
||||||
translate("Use both passwords"),
|
translate("Use both passwords"),
|
||||||
];
|
];
|
||||||
bool tmp_enabled = model.verificationMethod != kUsePermanentPassword;
|
bool tmpEnabled = model.verificationMethod != kUsePermanentPassword;
|
||||||
bool perm_enabled = model.verificationMethod != kUseTemporaryPassword;
|
bool permEnabled = model.verificationMethod != kUseTemporaryPassword;
|
||||||
String currentValue = values[keys.indexOf(model.verificationMethod)];
|
String currentValue = values[keys.indexOf(model.verificationMethod)];
|
||||||
List<Widget> radios = values
|
List<Widget> radios = values
|
||||||
.map((value) => _Radio<String>(
|
.map((value) => _Radio<String>(
|
||||||
@ -323,16 +323,24 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
groupValue: currentValue,
|
groupValue: currentValue,
|
||||||
label: value,
|
label: value,
|
||||||
onChanged: ((value) {
|
onChanged: ((value) {
|
||||||
model.verificationMethod = keys[values.indexOf(value)];
|
() async {
|
||||||
|
await model
|
||||||
|
.setVerificationMethod(keys[values.indexOf(value)]);
|
||||||
|
await model.updatePasswordModel();
|
||||||
|
}();
|
||||||
}),
|
}),
|
||||||
enabled: !locked,
|
enabled: !locked,
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
var onChanged = tmp_enabled && !locked
|
var onChanged = tmpEnabled && !locked
|
||||||
? (value) {
|
? (value) {
|
||||||
if (value != null)
|
if (value != null) {
|
||||||
model.temporaryPasswordLength = value.toString();
|
() async {
|
||||||
|
await model.setTemporaryPasswordLength(value.toString());
|
||||||
|
await model.updatePasswordModel();
|
||||||
|
}();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
List<Widget> lengthRadios = ['6', '8', '10']
|
List<Widget> lengthRadios = ['6', '8', '10']
|
||||||
@ -364,10 +372,10 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
...lengthRadios,
|
...lengthRadios,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
enabled: tmp_enabled && !locked),
|
enabled: tmpEnabled && !locked),
|
||||||
radios[1],
|
radios[1],
|
||||||
_SubButton('Set permanent password', setPasswordDialog,
|
_SubButton('Set permanent password', setPasswordDialog,
|
||||||
perm_enabled && !locked),
|
permEnabled && !locked),
|
||||||
radios[2],
|
radios[2],
|
||||||
]);
|
]);
|
||||||
})));
|
})));
|
||||||
|
@ -42,6 +42,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
String _value = '';
|
String _value = '';
|
||||||
final _cursorOverImage = false.obs;
|
final _cursorOverImage = false.obs;
|
||||||
late RxBool _showRemoteCursor;
|
late RxBool _showRemoteCursor;
|
||||||
|
late RxBool _remoteCursorMoved;
|
||||||
late RxBool _keyboardEnabled;
|
late RxBool _keyboardEnabled;
|
||||||
|
|
||||||
final FocusNode _mobileFocusNode = FocusNode();
|
final FocusNode _mobileFocusNode = FocusNode();
|
||||||
@ -61,8 +62,10 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
CurrentDisplayState.init(id);
|
CurrentDisplayState.init(id);
|
||||||
KeyboardEnabledState.init(id);
|
KeyboardEnabledState.init(id);
|
||||||
ShowRemoteCursorState.init(id);
|
ShowRemoteCursorState.init(id);
|
||||||
|
RemoteCursorMovedState.init(id);
|
||||||
_showRemoteCursor = ShowRemoteCursorState.find(id);
|
_showRemoteCursor = ShowRemoteCursorState.find(id);
|
||||||
_keyboardEnabled = KeyboardEnabledState.find(id);
|
_keyboardEnabled = KeyboardEnabledState.find(id);
|
||||||
|
_remoteCursorMoved = RemoteCursorMovedState.find(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeStates(String id) {
|
void _removeStates(String id) {
|
||||||
@ -71,6 +74,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
CurrentDisplayState.delete(id);
|
CurrentDisplayState.delete(id);
|
||||||
ShowRemoteCursorState.delete(id);
|
ShowRemoteCursorState.delete(id);
|
||||||
KeyboardEnabledState.delete(id);
|
KeyboardEnabledState.delete(id);
|
||||||
|
RemoteCursorMovedState.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -396,6 +400,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
id: widget.id,
|
id: widget.id,
|
||||||
cursorOverImage: _cursorOverImage,
|
cursorOverImage: _cursorOverImage,
|
||||||
keyboardEnabled: _keyboardEnabled,
|
keyboardEnabled: _keyboardEnabled,
|
||||||
|
remoteCursorMoved: _remoteCursorMoved,
|
||||||
listenerBuilder: _buildImageListener,
|
listenerBuilder: _buildImageListener,
|
||||||
);
|
);
|
||||||
}))
|
}))
|
||||||
@ -460,6 +465,7 @@ class ImagePaint extends StatelessWidget {
|
|||||||
final String id;
|
final String id;
|
||||||
final Rx<bool> cursorOverImage;
|
final Rx<bool> cursorOverImage;
|
||||||
final Rx<bool> keyboardEnabled;
|
final Rx<bool> keyboardEnabled;
|
||||||
|
final Rx<bool> remoteCursorMoved;
|
||||||
final Widget Function(Widget)? listenerBuilder;
|
final Widget Function(Widget)? listenerBuilder;
|
||||||
final ScrollController _horizontal = ScrollController();
|
final ScrollController _horizontal = ScrollController();
|
||||||
final ScrollController _vertical = ScrollController();
|
final ScrollController _vertical = ScrollController();
|
||||||
@ -469,6 +475,7 @@ class ImagePaint extends StatelessWidget {
|
|||||||
required this.id,
|
required this.id,
|
||||||
required this.cursorOverImage,
|
required this.cursorOverImage,
|
||||||
required this.keyboardEnabled,
|
required this.keyboardEnabled,
|
||||||
|
required this.remoteCursorMoved,
|
||||||
this.listenerBuilder})
|
this.listenerBuilder})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@ -476,6 +483,7 @@ class ImagePaint extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final m = Provider.of<ImageModel>(context);
|
final m = Provider.of<ImageModel>(context);
|
||||||
var c = Provider.of<CanvasModel>(context);
|
var c = Provider.of<CanvasModel>(context);
|
||||||
|
final cursor = Provider.of<CursorModel>(context);
|
||||||
final s = c.scale;
|
final s = c.scale;
|
||||||
if (c.scrollStyle == ScrollStyle.scrollbar) {
|
if (c.scrollStyle == ScrollStyle.scrollbar) {
|
||||||
final imageWidget = SizedBox(
|
final imageWidget = SizedBox(
|
||||||
@ -501,12 +509,16 @@ class ImagePaint extends StatelessWidget {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: Obx(() => MouseRegion(
|
child: Obx(() => MouseRegion(
|
||||||
// cursor: (keyboardEnabled.isTrue && cursorOverImage.isTrue)
|
cursor: (cursorOverImage.isTrue && keyboardEnabled.isTrue)
|
||||||
// ? SystemMouseCursors.none
|
? (remoteCursorMoved.isTrue
|
||||||
// : MouseCursor.defer,
|
? SystemMouseCursors.none
|
||||||
/// cursor: MouseCursor.defer,
|
: FlutterCustomMemoryImageCursor(
|
||||||
cursor: FlutterCustomCursor(
|
pixbuf: cursor.rgba!,
|
||||||
path: "assets/pencil.png", x: 1.0, y: 8.0),
|
hotx: cursor.hotx,
|
||||||
|
hoty: cursor.hoty,
|
||||||
|
imageWidth: (cursor.image!.width * s).toInt(),
|
||||||
|
imageHeight: (cursor.image!.height * s).toInt()))
|
||||||
|
: MouseCursor.defer,
|
||||||
onHover: (evt) {
|
onHover: (evt) {
|
||||||
pos.value = evt.position;
|
pos.value = evt.position;
|
||||||
},
|
},
|
||||||
|
@ -1031,6 +1031,7 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
required this.itemBuilder,
|
required this.itemBuilder,
|
||||||
this.initialValue,
|
this.initialValue,
|
||||||
|
this.onHover,
|
||||||
this.onSelected,
|
this.onSelected,
|
||||||
this.onCanceled,
|
this.onCanceled,
|
||||||
this.tooltip,
|
this.tooltip,
|
||||||
@ -1061,6 +1062,9 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||||||
/// The value of the menu item, if any, that should be highlighted when the menu opens.
|
/// The value of the menu item, if any, that should be highlighted when the menu opens.
|
||||||
final T? initialValue;
|
final T? initialValue;
|
||||||
|
|
||||||
|
/// Called when the user hovers this button.
|
||||||
|
final ValueChanged<bool>? onHover;
|
||||||
|
|
||||||
/// Called when the user selects a value from the popup menu created by this button.
|
/// Called when the user selects a value from the popup menu created by this button.
|
||||||
///
|
///
|
||||||
/// If the popup menu is dismissed without selecting a value, [onCanceled] is
|
/// If the popup menu is dismissed without selecting a value, [onCanceled] is
|
||||||
@ -1273,18 +1277,20 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
|||||||
|
|
||||||
assert(debugCheckHasMaterialLocalizations(context));
|
assert(debugCheckHasMaterialLocalizations(context));
|
||||||
|
|
||||||
if (widget.child != null)
|
if (widget.child != null) {
|
||||||
return Tooltip(
|
return Tooltip(
|
||||||
message:
|
message:
|
||||||
widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
|
widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: widget.enabled ? showButtonMenu : null,
|
onTap: widget.enabled ? showButtonMenu : null,
|
||||||
|
onHover: widget.onHover,
|
||||||
canRequestFocus: _canRequestFocus,
|
canRequestFocus: _canRequestFocus,
|
||||||
radius: widget.splashRadius,
|
radius: widget.splashRadius,
|
||||||
enableFeedback: enableFeedback,
|
enableFeedback: enableFeedback,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: widget.icon ?? Icon(Icons.adaptive.more),
|
icon: widget.icon ?? Icon(Icons.adaptive.more),
|
||||||
|
@ -12,7 +12,7 @@ class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> {
|
|||||||
key,
|
key,
|
||||||
this.height = kMinInteractiveDimension,
|
this.height = kMinInteractiveDimension,
|
||||||
this.padding,
|
this.padding,
|
||||||
this.enable = true,
|
this.enabled,
|
||||||
this.textStyle,
|
this.textStyle,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.position = mod_menu.PopupMenuPosition.overSide,
|
this.position = mod_menu.PopupMenuPosition.overSide,
|
||||||
@ -25,7 +25,7 @@ class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> {
|
|||||||
final Offset offset;
|
final Offset offset;
|
||||||
final TextStyle? textStyle;
|
final TextStyle? textStyle;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
final bool enable;
|
final RxBool? enabled;
|
||||||
final void Function()? onTap;
|
final void Function()? onTap;
|
||||||
final List<mod_menu.PopupMenuEntry<T>> Function(BuildContext) itemBuilder;
|
final List<mod_menu.PopupMenuEntry<T>> Function(BuildContext) itemBuilder;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
@ -56,9 +56,9 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
|
|||||||
TextStyle style = widget.textStyle ??
|
TextStyle style = widget.textStyle ??
|
||||||
popupMenuTheme.textStyle ??
|
popupMenuTheme.textStyle ??
|
||||||
theme.textTheme.subtitle1!;
|
theme.textTheme.subtitle1!;
|
||||||
|
return Obx(() {
|
||||||
return mod_menu.PopupMenuButton<T>(
|
return mod_menu.PopupMenuButton<T>(
|
||||||
enabled: widget.enable,
|
enabled: widget.enabled != null ? widget.enabled!.value : true,
|
||||||
position: widget.position,
|
position: widget.position,
|
||||||
offset: widget.offset,
|
offset: widget.offset,
|
||||||
onSelected: handleTap,
|
onSelected: handleTap,
|
||||||
@ -70,11 +70,13 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
|
|||||||
child: Container(
|
child: Container(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
constraints: BoxConstraints(minHeight: widget.height),
|
constraints: BoxConstraints(minHeight: widget.height),
|
||||||
padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
|
padding:
|
||||||
|
widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,8 +100,12 @@ class MenuConfig {
|
|||||||
|
|
||||||
abstract class MenuEntryBase<T> {
|
abstract class MenuEntryBase<T> {
|
||||||
bool dismissOnClicked;
|
bool dismissOnClicked;
|
||||||
|
RxBool? enabled;
|
||||||
|
|
||||||
MenuEntryBase({this.dismissOnClicked = false});
|
MenuEntryBase({
|
||||||
|
this.dismissOnClicked = false,
|
||||||
|
this.enabled,
|
||||||
|
});
|
||||||
List<mod_menu.PopupMenuEntry<T>> build(BuildContext context, MenuConfig conf);
|
List<mod_menu.PopupMenuEntry<T>> build(BuildContext context, MenuConfig conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,9 +125,14 @@ class MenuEntryRadioOption {
|
|||||||
String text;
|
String text;
|
||||||
String value;
|
String value;
|
||||||
bool dismissOnClicked;
|
bool dismissOnClicked;
|
||||||
|
RxBool? enabled;
|
||||||
|
|
||||||
MenuEntryRadioOption(
|
MenuEntryRadioOption({
|
||||||
{required this.text, required this.value, this.dismissOnClicked = false});
|
required this.text,
|
||||||
|
required this.value,
|
||||||
|
this.dismissOnClicked = false,
|
||||||
|
this.enabled,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef RadioOptionsGetter = List<MenuEntryRadioOption> Function();
|
typedef RadioOptionsGetter = List<MenuEntryRadioOption> Function();
|
||||||
@ -138,13 +149,14 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
|||||||
final RadioOptionSetter optionSetter;
|
final RadioOptionSetter optionSetter;
|
||||||
final RxString _curOption = "".obs;
|
final RxString _curOption = "".obs;
|
||||||
|
|
||||||
MenuEntryRadios(
|
MenuEntryRadios({
|
||||||
{required this.text,
|
required this.text,
|
||||||
required this.optionsGetter,
|
required this.optionsGetter,
|
||||||
required this.curOptionGetter,
|
required this.curOptionGetter,
|
||||||
required this.optionSetter,
|
required this.optionSetter,
|
||||||
dismissOnClicked = false})
|
dismissOnClicked = false,
|
||||||
: super(dismissOnClicked: dismissOnClicked) {
|
RxBool? enabled,
|
||||||
|
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled) {
|
||||||
() async {
|
() async {
|
||||||
_curOption.value = await curOptionGetter();
|
_curOption.value = await curOptionGetter();
|
||||||
}();
|
}();
|
||||||
@ -220,13 +232,17 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
|||||||
final RadioOptionSetter optionSetter;
|
final RadioOptionSetter optionSetter;
|
||||||
final RxString _curOption = "".obs;
|
final RxString _curOption = "".obs;
|
||||||
|
|
||||||
MenuEntrySubRadios(
|
MenuEntrySubRadios({
|
||||||
{required this.text,
|
required this.text,
|
||||||
required this.optionsGetter,
|
required this.optionsGetter,
|
||||||
required this.curOptionGetter,
|
required this.curOptionGetter,
|
||||||
required this.optionSetter,
|
required this.optionSetter,
|
||||||
dismissOnClicked = false})
|
dismissOnClicked = false,
|
||||||
: super(dismissOnClicked: dismissOnClicked) {
|
RxBool? enabled,
|
||||||
|
}) : super(
|
||||||
|
dismissOnClicked: dismissOnClicked,
|
||||||
|
enabled: enabled,
|
||||||
|
) {
|
||||||
() async {
|
() async {
|
||||||
_curOption.value = await curOptionGetter();
|
_curOption.value = await curOptionGetter();
|
||||||
}();
|
}();
|
||||||
@ -293,6 +309,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
|||||||
BuildContext context, MenuConfig conf) {
|
BuildContext context, MenuConfig conf) {
|
||||||
return [
|
return [
|
||||||
PopupMenuChildrenItem(
|
PopupMenuChildrenItem(
|
||||||
|
enabled: super.enabled,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
height: conf.height,
|
height: conf.height,
|
||||||
itemBuilder: (BuildContext context) =>
|
itemBuilder: (BuildContext context) =>
|
||||||
@ -327,9 +344,12 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
|||||||
final String text;
|
final String text;
|
||||||
final Rx<TextStyle>? textStyle;
|
final Rx<TextStyle>? textStyle;
|
||||||
|
|
||||||
MenuEntrySwitchBase(
|
MenuEntrySwitchBase({
|
||||||
{required this.text, required dismissOnClicked, this.textStyle})
|
required this.text,
|
||||||
: super(dismissOnClicked: dismissOnClicked);
|
required dismissOnClicked,
|
||||||
|
this.textStyle,
|
||||||
|
RxBool? enabled,
|
||||||
|
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled);
|
||||||
|
|
||||||
RxBool get curOption;
|
RxBool get curOption;
|
||||||
Future<void> setOption(bool option);
|
Future<void> setOption(bool option);
|
||||||
@ -395,16 +415,19 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
|
|||||||
final SwitchSetter setter;
|
final SwitchSetter setter;
|
||||||
final RxBool _curOption = false.obs;
|
final RxBool _curOption = false.obs;
|
||||||
|
|
||||||
MenuEntrySwitch(
|
MenuEntrySwitch({
|
||||||
{required String text,
|
required String text,
|
||||||
required this.getter,
|
required this.getter,
|
||||||
required this.setter,
|
required this.setter,
|
||||||
Rx<TextStyle>? textStyle,
|
Rx<TextStyle>? textStyle,
|
||||||
dismissOnClicked = false})
|
dismissOnClicked = false,
|
||||||
: super(
|
RxBool? enabled,
|
||||||
|
}) : super(
|
||||||
text: text,
|
text: text,
|
||||||
textStyle: textStyle,
|
textStyle: textStyle,
|
||||||
dismissOnClicked: dismissOnClicked) {
|
dismissOnClicked: dismissOnClicked,
|
||||||
|
enabled: enabled,
|
||||||
|
) {
|
||||||
() async {
|
() async {
|
||||||
_curOption.value = await getter();
|
_curOption.value = await getter();
|
||||||
}();
|
}();
|
||||||
@ -429,13 +452,14 @@ class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
|
|||||||
final Switch2Getter getter;
|
final Switch2Getter getter;
|
||||||
final SwitchSetter setter;
|
final SwitchSetter setter;
|
||||||
|
|
||||||
MenuEntrySwitch2(
|
MenuEntrySwitch2({
|
||||||
{required String text,
|
required String text,
|
||||||
required this.getter,
|
required this.getter,
|
||||||
required this.setter,
|
required this.setter,
|
||||||
Rx<TextStyle>? textStyle,
|
Rx<TextStyle>? textStyle,
|
||||||
dismissOnClicked = false})
|
dismissOnClicked = false,
|
||||||
: super(
|
RxBool? enabled,
|
||||||
|
}) : super(
|
||||||
text: text,
|
text: text,
|
||||||
textStyle: textStyle,
|
textStyle: textStyle,
|
||||||
dismissOnClicked: dismissOnClicked);
|
dismissOnClicked: dismissOnClicked);
|
||||||
@ -452,13 +476,18 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
|
|||||||
final String text;
|
final String text;
|
||||||
final List<MenuEntryBase<T>> entries;
|
final List<MenuEntryBase<T>> entries;
|
||||||
|
|
||||||
MenuEntrySubMenu({required this.text, required this.entries});
|
MenuEntrySubMenu({
|
||||||
|
required this.text,
|
||||||
|
required this.entries,
|
||||||
|
RxBool? enabled,
|
||||||
|
}) : super(enabled: enabled);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<mod_menu.PopupMenuEntry<T>> build(
|
List<mod_menu.PopupMenuEntry<T>> build(
|
||||||
BuildContext context, MenuConfig conf) {
|
BuildContext context, MenuConfig conf) {
|
||||||
return [
|
return [
|
||||||
PopupMenuChildrenItem(
|
PopupMenuChildrenItem(
|
||||||
|
enabled: super.enabled,
|
||||||
height: conf.height,
|
height: conf.height,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
position: mod_menu.PopupMenuPosition.overSide,
|
position: mod_menu.PopupMenuPosition.overSide,
|
||||||
@ -468,20 +497,24 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
|
|||||||
.toList(),
|
.toList(),
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
const SizedBox(width: MenuConfig.midPadding),
|
const SizedBox(width: MenuConfig.midPadding),
|
||||||
Text(
|
Obx(() => Text(
|
||||||
text,
|
text,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: MyTheme.color(context).text,
|
color: (super.enabled != null ? super.enabled!.value : true)
|
||||||
|
? Colors.black
|
||||||
|
: Colors.grey,
|
||||||
fontSize: MenuConfig.fontSize,
|
fontSize: MenuConfig.fontSize,
|
||||||
fontWeight: FontWeight.normal),
|
fontWeight: FontWeight.normal),
|
||||||
),
|
)),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Icon(
|
child: Obx(() => Icon(
|
||||||
Icons.keyboard_arrow_right,
|
Icons.keyboard_arrow_right,
|
||||||
color: conf.commonColor,
|
color: (super.enabled != null ? super.enabled!.value : true)
|
||||||
),
|
? conf.commonColor
|
||||||
|
: Colors.grey,
|
||||||
|
)),
|
||||||
))
|
))
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
@ -493,36 +526,57 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
|||||||
final Widget Function(TextStyle? style) childBuilder;
|
final Widget Function(TextStyle? style) childBuilder;
|
||||||
Function() proc;
|
Function() proc;
|
||||||
|
|
||||||
MenuEntryButton(
|
MenuEntryButton({
|
||||||
{required this.childBuilder,
|
required this.childBuilder,
|
||||||
required this.proc,
|
required this.proc,
|
||||||
dismissOnClicked = false})
|
dismissOnClicked = false,
|
||||||
: super(dismissOnClicked: dismissOnClicked);
|
RxBool? enabled,
|
||||||
|
}) : super(
|
||||||
|
dismissOnClicked: dismissOnClicked,
|
||||||
|
enabled: enabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildChild(BuildContext context, MenuConfig conf) {
|
||||||
|
return Obx(() {
|
||||||
|
bool enabled = true;
|
||||||
|
if (super.enabled != null) {
|
||||||
|
enabled = super.enabled!.value;
|
||||||
|
}
|
||||||
|
const enabledStyle = TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: MenuConfig.fontSize,
|
||||||
|
fontWeight: FontWeight.normal);
|
||||||
|
const disabledStyle = TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: MenuConfig.fontSize,
|
||||||
|
fontWeight: FontWeight.normal);
|
||||||
|
return TextButton(
|
||||||
|
onPressed: enabled
|
||||||
|
? () {
|
||||||
|
if (super.dismissOnClicked && Navigator.canPop(context)) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
proc();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: Container(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
constraints: BoxConstraints(minHeight: conf.height),
|
||||||
|
child: childBuilder(enabled ? enabledStyle : disabledStyle),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<mod_menu.PopupMenuEntry<T>> build(
|
List<mod_menu.PopupMenuEntry<T>> build(
|
||||||
BuildContext context, MenuConfig conf) {
|
BuildContext context, MenuConfig conf) {
|
||||||
return [
|
return [
|
||||||
mod_menu.PopupMenuItem(
|
mod_menu.PopupMenuItem(
|
||||||
|
enabled: super.enabled != null ? super.enabled!.value : true,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
height: conf.height,
|
height: conf.height,
|
||||||
child: TextButton(
|
child: _buildChild(context, conf),
|
||||||
child: Container(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
constraints: BoxConstraints(minHeight: conf.height),
|
|
||||||
child: childBuilder(
|
|
||||||
TextStyle(
|
|
||||||
color: MyTheme.color(context).text,
|
|
||||||
fontSize: MenuConfig.fontSize,
|
|
||||||
fontWeight: FontWeight.normal),
|
|
||||||
)),
|
|
||||||
onPressed: () {
|
|
||||||
if (super.dismissOnClicked && Navigator.canPop(context)) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
proc();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
|
|
||||||
bool get touchMode => _touchMode;
|
bool get touchMode => _touchMode;
|
||||||
|
|
||||||
bool get isPeerAndroid => _pi.platform == "Android";
|
bool get isPeerAndroid => _pi.platform == 'Android';
|
||||||
|
|
||||||
set inputBlocked(v) {
|
set inputBlocked(v) {
|
||||||
_inputBlocked = v;
|
_inputBlocked = v;
|
||||||
@ -116,7 +116,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
final icon =
|
final icon =
|
||||||
'${secure == true ? "secure" : "insecure"}${direct == true ? "" : "_relay"}';
|
'${secure == true ? 'secure' : 'insecure'}${direct == true ? '' : '_relay'}';
|
||||||
return Image.asset('assets/$icon.png', width: 48, height: 48);
|
return Image.asset('assets/$icon.png', width: 48, height: 48);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,17 +143,17 @@ class FfiModel with ChangeNotifier {
|
|||||||
} else if (name == 'cursor_id') {
|
} else if (name == 'cursor_id') {
|
||||||
parent.target?.cursorModel.updateCursorId(evt);
|
parent.target?.cursorModel.updateCursorId(evt);
|
||||||
} else if (name == 'cursor_position') {
|
} else if (name == 'cursor_position') {
|
||||||
parent.target?.cursorModel.updateCursorPosition(evt);
|
parent.target?.cursorModel.updateCursorPosition(evt, peerId);
|
||||||
} else if (name == 'clipboard') {
|
} else if (name == 'clipboard') {
|
||||||
Clipboard.setData(ClipboardData(text: evt['content']));
|
Clipboard.setData(ClipboardData(text: evt['content']));
|
||||||
} else if (name == 'permission') {
|
} else if (name == 'permission') {
|
||||||
parent.target?.ffiModel.updatePermission(evt, peerId);
|
parent.target?.ffiModel.updatePermission(evt, peerId);
|
||||||
} else if (name == 'chat_client_mode') {
|
} else if (name == 'chat_client_mode') {
|
||||||
parent.target?.chatModel
|
parent.target?.chatModel
|
||||||
.receive(ChatModel.clientModeID, evt['text'] ?? "");
|
.receive(ChatModel.clientModeID, evt['text'] ?? '');
|
||||||
} else if (name == 'chat_server_mode') {
|
} else if (name == 'chat_server_mode') {
|
||||||
parent.target?.chatModel
|
parent.target?.chatModel
|
||||||
.receive(int.parse(evt['id'] as String), evt['text'] ?? "");
|
.receive(int.parse(evt['id'] as String), evt['text'] ?? '');
|
||||||
} else if (name == 'file_dir') {
|
} else if (name == 'file_dir') {
|
||||||
parent.target?.fileModel.receiveFileDir(evt);
|
parent.target?.fileModel.receiveFileDir(evt);
|
||||||
} else if (name == 'job_progress') {
|
} else if (name == 'job_progress') {
|
||||||
@ -184,61 +184,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
|
|
||||||
/// Bind the event listener to receive events from the Rust core.
|
/// Bind the event listener to receive events from the Rust core.
|
||||||
void updateEventListener(String peerId) {
|
void updateEventListener(String peerId) {
|
||||||
cb(evt) {
|
platformFFI.setEventCallback(startEventListener(peerId));
|
||||||
var name = evt['name'];
|
|
||||||
if (name == 'msgbox') {
|
|
||||||
handleMsgBox(evt, peerId);
|
|
||||||
} else if (name == 'peer_info') {
|
|
||||||
handlePeerInfo(evt, peerId);
|
|
||||||
} else if (name == 'connection_ready') {
|
|
||||||
parent.target?.ffiModel.setConnectionType(
|
|
||||||
peerId, evt['secure'] == 'true', evt['direct'] == 'true');
|
|
||||||
} else if (name == 'switch_display') {
|
|
||||||
handleSwitchDisplay(evt);
|
|
||||||
} else if (name == 'cursor_data') {
|
|
||||||
parent.target?.cursorModel.updateCursorData(evt);
|
|
||||||
} else if (name == 'cursor_id') {
|
|
||||||
parent.target?.cursorModel.updateCursorId(evt);
|
|
||||||
} else if (name == 'cursor_position') {
|
|
||||||
parent.target?.cursorModel.updateCursorPosition(evt);
|
|
||||||
} else if (name == 'clipboard') {
|
|
||||||
Clipboard.setData(ClipboardData(text: evt['content']));
|
|
||||||
} else if (name == 'permission') {
|
|
||||||
parent.target?.ffiModel.updatePermission(evt, peerId);
|
|
||||||
} else if (name == 'chat_client_mode') {
|
|
||||||
parent.target?.chatModel
|
|
||||||
.receive(ChatModel.clientModeID, evt['text'] ?? "");
|
|
||||||
} else if (name == 'chat_server_mode') {
|
|
||||||
parent.target?.chatModel
|
|
||||||
.receive(int.parse(evt['id'] as String), evt['text'] ?? "");
|
|
||||||
} else if (name == 'file_dir') {
|
|
||||||
parent.target?.fileModel.receiveFileDir(evt);
|
|
||||||
} else if (name == 'job_progress') {
|
|
||||||
parent.target?.fileModel.tryUpdateJobProgress(evt);
|
|
||||||
} else if (name == 'job_done') {
|
|
||||||
parent.target?.fileModel.jobDone(evt);
|
|
||||||
} else if (name == 'job_error') {
|
|
||||||
parent.target?.fileModel.jobError(evt);
|
|
||||||
} else if (name == 'override_file_confirm') {
|
|
||||||
parent.target?.fileModel.overrideFileConfirm(evt);
|
|
||||||
} else if (name == 'load_last_job') {
|
|
||||||
parent.target?.fileModel.loadLastJob(evt);
|
|
||||||
} else if (name == 'update_folder_files') {
|
|
||||||
parent.target?.fileModel.updateFolderFiles(evt);
|
|
||||||
} else if (name == 'add_connection') {
|
|
||||||
parent.target?.serverModel.addConnection(evt);
|
|
||||||
} else if (name == 'on_client_remove') {
|
|
||||||
parent.target?.serverModel.onClientRemove(evt);
|
|
||||||
} else if (name == 'update_quality_status') {
|
|
||||||
parent.target?.qualityMonitorModel.updateQualityStatus(evt);
|
|
||||||
} else if (name == 'update_block_input_state') {
|
|
||||||
updateBlockInputState(evt, peerId);
|
|
||||||
} else if (name == 'update_privacy_mode') {
|
|
||||||
updatePrivacyMode(evt, peerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
platformFFI.setEventCallback(cb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleSwitchDisplay(Map<String, dynamic> evt) {
|
void handleSwitchDisplay(Map<String, dynamic> evt) {
|
||||||
@ -249,8 +195,9 @@ class FfiModel with ChangeNotifier {
|
|||||||
_display.y = double.parse(evt['y']);
|
_display.y = double.parse(evt['y']);
|
||||||
_display.width = int.parse(evt['width']);
|
_display.width = int.parse(evt['width']);
|
||||||
_display.height = int.parse(evt['height']);
|
_display.height = int.parse(evt['height']);
|
||||||
if (old != _pi.currentDisplay)
|
if (old != _pi.currentDisplay) {
|
||||||
parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y);
|
parent.target?.cursorModel.updateDisplayOrigin(_display.x, _display.y);
|
||||||
|
}
|
||||||
|
|
||||||
// remote is mobile, and orientation changed
|
// remote is mobile, and orientation changed
|
||||||
if ((_display.width > _display.height) != oldOrientation) {
|
if ((_display.width > _display.height) != oldOrientation) {
|
||||||
@ -307,7 +254,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
_pi.username = evt['username'];
|
_pi.username = evt['username'];
|
||||||
_pi.hostname = evt['hostname'];
|
_pi.hostname = evt['hostname'];
|
||||||
_pi.platform = evt['platform'];
|
_pi.platform = evt['platform'];
|
||||||
_pi.sasEnabled = evt['sas_enabled'] == "true";
|
_pi.sasEnabled = evt['sas_enabled'] == 'true';
|
||||||
_pi.currentDisplay = int.parse(evt['current_display']);
|
_pi.currentDisplay = int.parse(evt['current_display']);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -323,7 +270,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_touchMode =
|
_touchMode =
|
||||||
await bind.sessionGetOption(id: peerId, arg: "touch-mode") != '';
|
await bind.sessionGetOption(id: peerId, arg: 'touch-mode') != '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent.target != null &&
|
if (parent.target != null &&
|
||||||
@ -381,7 +328,7 @@ class ImageModel with ChangeNotifier {
|
|||||||
|
|
||||||
ui.Image? get image => _image;
|
ui.Image? get image => _image;
|
||||||
|
|
||||||
String _id = "";
|
String _id = '';
|
||||||
|
|
||||||
WeakReference<FFI> parent;
|
WeakReference<FFI> parent;
|
||||||
|
|
||||||
@ -426,7 +373,7 @@ class ImageModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
Future.delayed(Duration(milliseconds: 1), () {
|
Future.delayed(Duration(milliseconds: 1), () {
|
||||||
if (parent.target?.ffiModel.isPeerAndroid ?? false) {
|
if (parent.target?.ffiModel.isPeerAndroid ?? false) {
|
||||||
bind.sessionPeerOption(id: _id, name: "view-style", value: "shrink");
|
bind.sessionPeerOption(id: _id, name: 'view-style', value: 'shrink');
|
||||||
parent.target?.canvasModel.updateViewStyle();
|
parent.target?.canvasModel.updateViewStyle();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -471,7 +418,7 @@ class CanvasModel with ChangeNotifier {
|
|||||||
// the tabbar over the image
|
// the tabbar over the image
|
||||||
double tabBarHeight = 0.0;
|
double tabBarHeight = 0.0;
|
||||||
// TODO multi canvas model
|
// TODO multi canvas model
|
||||||
String id = "";
|
String id = '';
|
||||||
// scroll offset x percent
|
// scroll offset x percent
|
||||||
double _scrollX = 0.0;
|
double _scrollX = 0.0;
|
||||||
// scroll offset y percent
|
// scroll offset y percent
|
||||||
@ -580,9 +527,16 @@ class CanvasModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If keyboard is not permitted, do not move cursor when mouse is moving.
|
// If keyboard is not permitted, do not move cursor when mouse is moving.
|
||||||
if (parent.target != null) {
|
if (parent.target != null && parent.target!.ffiModel.keyboard()) {
|
||||||
if (parent.target!.ffiModel.keyboard()) {
|
// Draw cursor if is not desktop.
|
||||||
|
if (!isDesktop) {
|
||||||
parent.target!.cursorModel.moveLocal(x, y);
|
parent.target!.cursorModel.moveLocal(x, y);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
RemoteCursorMovedState.find(id).value = false;
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -641,17 +595,19 @@ class CanvasModel with ChangeNotifier {
|
|||||||
|
|
||||||
class CursorModel with ChangeNotifier {
|
class CursorModel with ChangeNotifier {
|
||||||
ui.Image? _image;
|
ui.Image? _image;
|
||||||
final _images = <int, Tuple3<ui.Image, double, double>>{};
|
Uint8List? _rgba;
|
||||||
|
final _images = <int, Tuple4<Uint8List, ui.Image, double, double>>{};
|
||||||
double _x = -10000;
|
double _x = -10000;
|
||||||
double _y = -10000;
|
double _y = -10000;
|
||||||
double _hotx = 0;
|
double _hotx = 0;
|
||||||
double _hoty = 0;
|
double _hoty = 0;
|
||||||
double _displayOriginX = 0;
|
double _displayOriginX = 0;
|
||||||
double _displayOriginY = 0;
|
double _displayOriginY = 0;
|
||||||
String id = ""; // TODO multi cursor model
|
String id = ''; // TODO multi cursor model
|
||||||
WeakReference<FFI> parent;
|
WeakReference<FFI> parent;
|
||||||
|
|
||||||
ui.Image? get image => _image;
|
ui.Image? get image => _image;
|
||||||
|
Uint8List? get rgba => _rgba;
|
||||||
|
|
||||||
double get x => _x - _displayOriginX;
|
double get x => _x - _displayOriginX;
|
||||||
|
|
||||||
@ -803,7 +759,8 @@ class CursorModel with ChangeNotifier {
|
|||||||
(image) {
|
(image) {
|
||||||
if (parent.target?.id != pid) return;
|
if (parent.target?.id != pid) return;
|
||||||
_image = image;
|
_image = image;
|
||||||
_images[id] = Tuple3(image, _hotx, _hoty);
|
_rgba = rgba;
|
||||||
|
_images[id] = Tuple4(rgba, image, _hotx, _hoty);
|
||||||
try {
|
try {
|
||||||
// my throw exception, because the listener maybe already dispose
|
// my throw exception, because the listener maybe already dispose
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@ -816,17 +773,23 @@ class CursorModel with ChangeNotifier {
|
|||||||
void updateCursorId(Map<String, dynamic> evt) {
|
void updateCursorId(Map<String, dynamic> evt) {
|
||||||
final tmp = _images[int.parse(evt['id'])];
|
final tmp = _images[int.parse(evt['id'])];
|
||||||
if (tmp != null) {
|
if (tmp != null) {
|
||||||
_image = tmp.item1;
|
_rgba = tmp.item1;
|
||||||
_hotx = tmp.item2;
|
_image = tmp.item2;
|
||||||
_hoty = tmp.item3;
|
_hotx = tmp.item3;
|
||||||
|
_hoty = tmp.item4;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the cursor position.
|
/// Update the cursor position.
|
||||||
void updateCursorPosition(Map<String, dynamic> evt) {
|
void updateCursorPosition(Map<String, dynamic> evt, String id) {
|
||||||
_x = double.parse(evt['x']);
|
_x = double.parse(evt['x']);
|
||||||
_y = double.parse(evt['y']);
|
_y = double.parse(evt['y']);
|
||||||
|
try {
|
||||||
|
RemoteCursorMovedState.find(id).value = false;
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -888,13 +851,15 @@ class QualityMonitorModel with ChangeNotifier {
|
|||||||
|
|
||||||
updateQualityStatus(Map<String, dynamic> evt) {
|
updateQualityStatus(Map<String, dynamic> evt) {
|
||||||
try {
|
try {
|
||||||
if ((evt["speed"] as String).isNotEmpty) _data.speed = evt["speed"];
|
if ((evt['speed'] as String).isNotEmpty) _data.speed = evt['speed'];
|
||||||
if ((evt["fps"] as String).isNotEmpty) _data.fps = evt["fps"];
|
if ((evt['fps'] as String).isNotEmpty) _data.fps = evt['fps'];
|
||||||
if ((evt["delay"] as String).isNotEmpty) _data.delay = evt["delay"];
|
if ((evt['delay'] as String).isNotEmpty) _data.delay = evt['delay'];
|
||||||
if ((evt["target_bitrate"] as String).isNotEmpty)
|
if ((evt['target_bitrate'] as String).isNotEmpty) {
|
||||||
_data.targetBitrate = evt["target_bitrate"];
|
_data.targetBitrate = evt['target_bitrate'];
|
||||||
if ((evt["codec_format"] as String).isNotEmpty)
|
}
|
||||||
_data.codecFormat = evt["codec_format"];
|
if ((evt['codec_format'] as String).isNotEmpty) {
|
||||||
|
_data.codecFormat = evt['codec_format'];
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
@ -907,11 +872,11 @@ extension ToString on MouseButtons {
|
|||||||
String get value {
|
String get value {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case MouseButtons.left:
|
case MouseButtons.left:
|
||||||
return "left";
|
return 'left';
|
||||||
case MouseButtons.right:
|
case MouseButtons.right:
|
||||||
return "right";
|
return 'right';
|
||||||
case MouseButtons.wheel:
|
case MouseButtons.wheel:
|
||||||
return "wheel";
|
return 'wheel';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -920,12 +885,12 @@ enum ConnType { defaultConn, fileTransfer, portForward, rdp }
|
|||||||
|
|
||||||
/// FFI class for communicating with the Rust core.
|
/// FFI class for communicating with the Rust core.
|
||||||
class FFI {
|
class FFI {
|
||||||
var id = "";
|
var id = '';
|
||||||
var shift = false;
|
var shift = false;
|
||||||
var ctrl = false;
|
var ctrl = false;
|
||||||
var alt = false;
|
var alt = false;
|
||||||
var command = false;
|
var command = false;
|
||||||
var version = "";
|
var version = '';
|
||||||
var connType = ConnType.defaultConn;
|
var connType = ConnType.defaultConn;
|
||||||
|
|
||||||
/// dialogManager use late to ensure init after main page binding [globalKey]
|
/// dialogManager use late to ensure init after main page binding [globalKey]
|
||||||
@ -1006,11 +971,11 @@ class FFI {
|
|||||||
// out['name'] = name;
|
// out['name'] = name;
|
||||||
// // default: down = false
|
// // default: down = false
|
||||||
// if (down == true) {
|
// if (down == true) {
|
||||||
// out['down'] = "true";
|
// out['down'] = 'true';
|
||||||
// }
|
// }
|
||||||
// // default: press = true
|
// // default: press = true
|
||||||
// if (press != false) {
|
// if (press != false) {
|
||||||
// out['press'] = "true";
|
// out['press'] = 'true';
|
||||||
// }
|
// }
|
||||||
// setByName('input_key', json.encode(modify(out)));
|
// setByName('input_key', json.encode(modify(out)));
|
||||||
// TODO id
|
// TODO id
|
||||||
@ -1038,7 +1003,7 @@ class FFI {
|
|||||||
Future<List<Peer>> peers() async {
|
Future<List<Peer>> peers() async {
|
||||||
try {
|
try {
|
||||||
var str = await bind.mainGetRecentPeers();
|
var str = await bind.mainGetRecentPeers();
|
||||||
if (str == "") return [];
|
if (str == '') return [];
|
||||||
List<dynamic> peers = json.decode(str);
|
List<dynamic> peers = json.decode(str);
|
||||||
return peers
|
return peers
|
||||||
.map((s) => s as List<dynamic>)
|
.map((s) => s as List<dynamic>)
|
||||||
@ -1056,7 +1021,7 @@ class FFI {
|
|||||||
{bool isFileTransfer = false,
|
{bool isFileTransfer = false,
|
||||||
bool isPortForward = false,
|
bool isPortForward = false,
|
||||||
double tabBarHeight = 0.0}) {
|
double tabBarHeight = 0.0}) {
|
||||||
assert(!(isFileTransfer && isPortForward), "more than one connect type");
|
assert(!(isFileTransfer && isPortForward), 'more than one connect type');
|
||||||
if (isFileTransfer) {
|
if (isFileTransfer) {
|
||||||
connType = ConnType.fileTransfer;
|
connType = ConnType.fileTransfer;
|
||||||
id = 'ft_$id';
|
id = 'ft_$id';
|
||||||
@ -1108,13 +1073,13 @@ class FFI {
|
|||||||
canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay);
|
canvasModel.y, canvasModel.scale, ffiModel.pi.currentDisplay);
|
||||||
}
|
}
|
||||||
bind.sessionClose(id: id);
|
bind.sessionClose(id: id);
|
||||||
id = "";
|
id = '';
|
||||||
imageModel.update(null, 0.0);
|
imageModel.update(null, 0.0);
|
||||||
cursorModel.clear();
|
cursorModel.clear();
|
||||||
ffiModel.clear();
|
ffiModel.clear();
|
||||||
canvasModel.clear();
|
canvasModel.clear();
|
||||||
resetModifiers();
|
resetModifiers();
|
||||||
debugPrint("model $id closed");
|
debugPrint('model $id closed');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send **get** command to the Rust core based on [name] and [arg].
|
/// Send **get** command to the Rust core based on [name] and [arg].
|
||||||
@ -1221,7 +1186,7 @@ class FFI {
|
|||||||
Future<String> getDefaultAudioInput() async {
|
Future<String> getDefaultAudioInput() async {
|
||||||
final input = await bind.mainGetOption(key: 'audio-input');
|
final input = await bind.mainGetOption(key: 'audio-input');
|
||||||
if (input.isEmpty && Platform.isWindows) {
|
if (input.isEmpty && Platform.isWindows) {
|
||||||
return "System Sound";
|
return 'System Sound';
|
||||||
}
|
}
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
@ -1232,8 +1197,8 @@ class FFI {
|
|||||||
|
|
||||||
Future<Map<String, String>> getHttpHeaders() async {
|
Future<Map<String, String>> getHttpHeaders() async {
|
||||||
return {
|
return {
|
||||||
"Authorization":
|
'Authorization':
|
||||||
"Bearer " + await bind.mainGetLocalOption(key: "access_token")
|
'Bearer ' + await bind.mainGetLocalOption(key: 'access_token')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1246,10 +1211,10 @@ class Display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PeerInfo {
|
class PeerInfo {
|
||||||
String version = "";
|
String version = '';
|
||||||
String username = "";
|
String username = '';
|
||||||
String hostname = "";
|
String hostname = '';
|
||||||
String platform = "";
|
String platform = '';
|
||||||
bool sasEnabled = false;
|
bool sasEnabled = false;
|
||||||
int currentDisplay = 0;
|
int currentDisplay = 0;
|
||||||
List<Display> displays = [];
|
List<Display> displays = [];
|
||||||
|
@ -35,7 +35,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
|
|
||||||
final tabController = DesktopTabController(tabType: DesktopTabType.cm);
|
final tabController = DesktopTabController(tabType: DesktopTabType.cm);
|
||||||
|
|
||||||
List<Client> _clients = [];
|
final List<Client> _clients = [];
|
||||||
|
|
||||||
bool get isStart => _isStart;
|
bool get isStart => _isStart;
|
||||||
|
|
||||||
@ -61,8 +61,8 @@ class ServerModel with ChangeNotifier {
|
|||||||
return _verificationMethod;
|
return _verificationMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
set verificationMethod(String method) {
|
setVerificationMethod(String method) async {
|
||||||
bind.mainSetOption(key: "verification-method", value: method);
|
await bind.mainSetOption(key: "verification-method", value: method);
|
||||||
}
|
}
|
||||||
|
|
||||||
String get temporaryPasswordLength {
|
String get temporaryPasswordLength {
|
||||||
@ -73,8 +73,8 @@ class ServerModel with ChangeNotifier {
|
|||||||
return _temporaryPasswordLength;
|
return _temporaryPasswordLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
set temporaryPasswordLength(String length) {
|
setTemporaryPasswordLength(String length) async {
|
||||||
bind.mainSetOption(key: "temporary-password-length", value: length);
|
await bind.mainSetOption(key: "temporary-password-length", value: length);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextEditingController get serverId => _serverId;
|
TextEditingController get serverId => _serverId;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user