flutter desktop: ui changes
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
parent
2dc8c02d15
commit
722a4d3de7
@ -6,6 +6,9 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/connection_page.dart';
|
import 'package:flutter_hbb/desktop/pages/connection_page.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
||||||
|
import 'package:flutter_hbb/desktop/widgets/popup_menu.dart';
|
||||||
|
import 'package:flutter_hbb/desktop/widgets/material_mod_popup_menu.dart'
|
||||||
|
as mod_menu;
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:flutter_hbb/models/server_model.dart';
|
import 'package:flutter_hbb/models/server_model.dart';
|
||||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||||
|
@ -17,7 +17,6 @@ import '../../mobile/widgets/dialog.dart';
|
|||||||
import '../../mobile/widgets/overlay.dart';
|
import '../../mobile/widgets/overlay.dart';
|
||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../../models/chat_model.dart';
|
|
||||||
import '../../common/shared_state.dart';
|
import '../../common/shared_state.dart';
|
||||||
|
|
||||||
final initText = '\1' * 1024;
|
final initText = '\1' * 1024;
|
||||||
@ -39,7 +38,6 @@ class RemotePage extends StatefulWidget {
|
|||||||
class _RemotePageState extends State<RemotePage>
|
class _RemotePageState extends State<RemotePage>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
bool _showBar = !isWebDesktop;
|
|
||||||
String _value = '';
|
String _value = '';
|
||||||
final _cursorOverImage = false.obs;
|
final _cursorOverImage = false.obs;
|
||||||
|
|
||||||
@ -131,7 +129,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
common < oldValue.length &&
|
common < oldValue.length &&
|
||||||
common < newValue.length &&
|
common < newValue.length &&
|
||||||
newValue[common] == oldValue[common];
|
newValue[common] == oldValue[common];
|
||||||
++common);
|
++common) {}
|
||||||
for (i = 0; i < oldValue.length - common; ++i) {
|
for (i = 0; i < oldValue.length - common; ++i) {
|
||||||
_ffi.inputKey('VK_BACK');
|
_ffi.inputKey('VK_BACK');
|
||||||
}
|
}
|
||||||
@ -145,8 +143,8 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (oldValue.length > 0 &&
|
if (oldValue.isNotEmpty &&
|
||||||
newValue.length > 0 &&
|
newValue.isNotEmpty &&
|
||||||
oldValue[0] == '\1' &&
|
oldValue[0] == '\1' &&
|
||||||
newValue[0] != '\1') {
|
newValue[0] != '\1') {
|
||||||
// clipboard
|
// clipboard
|
||||||
@ -155,7 +153,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
if (newValue.length == oldValue.length) {
|
if (newValue.length == oldValue.length) {
|
||||||
// ?
|
// ?
|
||||||
} else if (newValue.length < oldValue.length) {
|
} else if (newValue.length < oldValue.length) {
|
||||||
final char = 'VK_BACK';
|
const char = 'VK_BACK';
|
||||||
_ffi.inputKey(char);
|
_ffi.inputKey(char);
|
||||||
} else {
|
} else {
|
||||||
final content = newValue.substring(oldValue.length);
|
final content = newValue.substring(oldValue.length);
|
||||||
@ -200,24 +198,9 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget buildBody(BuildContext context, FfiModel ffiModel) {
|
Widget buildBody(BuildContext context, FfiModel ffiModel) {
|
||||||
final hasDisplays = ffiModel.pi.displays.length > 0;
|
|
||||||
final keyboard = ffiModel.permissions['keyboard'] != false;
|
final keyboard = ffiModel.permissions['keyboard'] != false;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: MyTheme.color(context).bg,
|
backgroundColor: MyTheme.color(context).bg,
|
||||||
// resizeToAvoidBottomInset: true,
|
|
||||||
// floatingActionButton: _showBar
|
|
||||||
// ? null
|
|
||||||
// : FloatingActionButton(
|
|
||||||
// mini: true,
|
|
||||||
// child: Icon(Icons.expand_less),
|
|
||||||
// backgroundColor: MyTheme.accent,
|
|
||||||
// onPressed: () {
|
|
||||||
// setState(() {
|
|
||||||
// _showBar = !_showBar;
|
|
||||||
// });
|
|
||||||
// }),
|
|
||||||
// bottomNavigationBar:
|
|
||||||
// _showBar && hasDisplays ? getBottomAppBar(ffiModel) : null,
|
|
||||||
body: Overlay(
|
body: Overlay(
|
||||||
initialEntries: [
|
initialEntries: [
|
||||||
OverlayEntry(builder: (context) {
|
OverlayEntry(builder: (context) {
|
||||||
@ -249,7 +232,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
ChangeNotifierProvider.value(value: _ffi.canvasModel),
|
ChangeNotifierProvider.value(value: _ffi.canvasModel),
|
||||||
],
|
],
|
||||||
child: Consumer<FfiModel>(
|
child: Consumer<FfiModel>(
|
||||||
builder: (context, ffiModel, _child) =>
|
builder: (context, ffiModel, child) =>
|
||||||
buildBody(context, ffiModel))));
|
buildBody(context, ffiModel))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,100 +290,6 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
child: child))));
|
child: child))));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget? getBottomAppBar(FfiModel ffiModel) {
|
|
||||||
final RxBool fullscreen = Get.find(tag: 'fullscreen');
|
|
||||||
return MouseRegion(
|
|
||||||
cursor: SystemMouseCursors.basic,
|
|
||||||
child: BottomAppBar(
|
|
||||||
elevation: 10,
|
|
||||||
color: MyTheme.accent,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
color: Colors.white,
|
|
||||||
icon: Icon(Icons.clear),
|
|
||||||
onPressed: () {
|
|
||||||
clientClose(_ffi.dialogManager);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
] +
|
|
||||||
<Widget>[
|
|
||||||
IconButton(
|
|
||||||
color: Colors.white,
|
|
||||||
icon: Icon(Icons.tv),
|
|
||||||
onPressed: () {
|
|
||||||
_ffi.dialogManager.dismissAll();
|
|
||||||
showOptions(widget.id);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
] +
|
|
||||||
(isWebDesktop
|
|
||||||
? []
|
|
||||||
: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
color: Colors.white,
|
|
||||||
icon: Icon(fullscreen.isTrue
|
|
||||||
? Icons.fullscreen
|
|
||||||
: Icons.close_fullscreen),
|
|
||||||
onPressed: () {
|
|
||||||
fullscreen.value = !fullscreen.value;
|
|
||||||
},
|
|
||||||
)
|
|
||||||
]) +
|
|
||||||
(isWebDesktop
|
|
||||||
? []
|
|
||||||
: _ffi.ffiModel.isPeerAndroid
|
|
||||||
? [
|
|
||||||
IconButton(
|
|
||||||
color: Colors.white,
|
|
||||||
icon: Icon(Icons.build),
|
|
||||||
onPressed: () {
|
|
||||||
if (mobileActionsOverlayEntry == null) {
|
|
||||||
showMobileActionsOverlay();
|
|
||||||
} else {
|
|
||||||
hideMobileActionsOverlay();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
]
|
|
||||||
: []) +
|
|
||||||
(isWeb
|
|
||||||
? []
|
|
||||||
: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
color: Colors.white,
|
|
||||||
icon: Icon(Icons.message),
|
|
||||||
onPressed: () {
|
|
||||||
_ffi.chatModel
|
|
||||||
.changeCurrentID(ChatModel.clientModeID);
|
|
||||||
_ffi.chatModel.toggleChatOverlay();
|
|
||||||
},
|
|
||||||
)
|
|
||||||
]) +
|
|
||||||
[
|
|
||||||
IconButton(
|
|
||||||
color: Colors.white,
|
|
||||||
icon: Icon(Icons.more_vert),
|
|
||||||
onPressed: () {
|
|
||||||
showActions(widget.id, ffiModel);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
IconButton(
|
|
||||||
color: Colors.white,
|
|
||||||
icon: Icon(Icons.expand_more),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() => _showBar = !_showBar);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// touchMode only:
|
/// touchMode only:
|
||||||
/// LongPress -> right click
|
/// LongPress -> right click
|
||||||
/// OneFingerPan -> start/end -> left down start/end
|
/// OneFingerPan -> start/end -> left down start/end
|
||||||
@ -458,12 +347,16 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
if (e is PointerScrollEvent) {
|
if (e is PointerScrollEvent) {
|
||||||
var dx = e.scrollDelta.dx.toInt();
|
var dx = e.scrollDelta.dx.toInt();
|
||||||
var dy = e.scrollDelta.dy.toInt();
|
var dy = e.scrollDelta.dy.toInt();
|
||||||
if (dx > 0)
|
if (dx > 0) {
|
||||||
dx = -1;
|
dx = -1;
|
||||||
else if (dx < 0) dx = 1;
|
} else if (dx < 0) {
|
||||||
if (dy > 0)
|
dx = 1;
|
||||||
|
}
|
||||||
|
if (dy > 0) {
|
||||||
dy = -1;
|
dy = -1;
|
||||||
else if (dy < 0) dy = 1;
|
} else if (dy < 0) {
|
||||||
|
dy = 1;
|
||||||
|
}
|
||||||
bind.sessionSendMouse(
|
bind.sessionSendMouse(
|
||||||
id: widget.id, msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
|
id: widget.id, msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
|
||||||
}
|
}
|
||||||
@ -546,106 +439,6 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void showActions(String id, FfiModel ffiModel) async {
|
|
||||||
final size = MediaQuery.of(context).size;
|
|
||||||
final x = 120.0;
|
|
||||||
final y = size.height - super.widget.tabBarHeight;
|
|
||||||
final more = <PopupMenuItem<String>>[];
|
|
||||||
final pi = _ffi.ffiModel.pi;
|
|
||||||
final perms = _ffi.ffiModel.permissions;
|
|
||||||
if (pi.version.isNotEmpty) {
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate('Refresh')), value: 'refresh'));
|
|
||||||
}
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Row(
|
|
||||||
children: ([
|
|
||||||
Text(translate('OS Password')),
|
|
||||||
TextButton(
|
|
||||||
style: flatButtonStyle,
|
|
||||||
onPressed: () {
|
|
||||||
showSetOSPassword(widget.id, false, _ffi.dialogManager);
|
|
||||||
},
|
|
||||||
child: Icon(Icons.edit, color: MyTheme.accent),
|
|
||||||
)
|
|
||||||
])),
|
|
||||||
value: 'enter_os_password'));
|
|
||||||
if (!isWebDesktop) {
|
|
||||||
if (perms['keyboard'] != false && perms['clipboard'] != false) {
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate('Paste')), value: 'paste'));
|
|
||||||
}
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate('Reset canvas')), value: 'reset_canvas'));
|
|
||||||
}
|
|
||||||
if (perms['keyboard'] != false) {
|
|
||||||
if (pi.platform == 'Linux' || pi.sasEnabled) {
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate('Insert') + ' Ctrl + Alt + Del'),
|
|
||||||
value: 'cad'));
|
|
||||||
}
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate('Insert Lock')), value: 'lock'));
|
|
||||||
if (pi.platform == 'Windows' &&
|
|
||||||
await bind.sessionGetToggleOption(id: id, arg: 'privacy-mode') !=
|
|
||||||
true) {
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate(
|
|
||||||
(ffiModel.inputBlocked ? 'Unb' : 'B') + 'lock user input')),
|
|
||||||
value: 'block-input'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (gFFI.ffiModel.permissions["restart"] != false &&
|
|
||||||
(pi.platform == "Linux" ||
|
|
||||||
pi.platform == "Windows" ||
|
|
||||||
pi.platform == "Mac OS")) {
|
|
||||||
more.add(PopupMenuItem<String>(
|
|
||||||
child: Text(translate('Restart Remote Device')), value: 'restart'));
|
|
||||||
}
|
|
||||||
() async {
|
|
||||||
var value = await showMenu(
|
|
||||||
context: context,
|
|
||||||
position: RelativeRect.fromLTRB(x, y, x, y),
|
|
||||||
items: more,
|
|
||||||
elevation: 8,
|
|
||||||
);
|
|
||||||
if (value == 'cad') {
|
|
||||||
bind.sessionCtrlAltDel(id: widget.id);
|
|
||||||
} else if (value == 'lock') {
|
|
||||||
bind.sessionLockScreen(id: widget.id);
|
|
||||||
} else if (value == 'block-input') {
|
|
||||||
bind.sessionToggleOption(
|
|
||||||
id: widget.id,
|
|
||||||
value: (_ffi.ffiModel.inputBlocked ? 'un' : '') + 'block-input');
|
|
||||||
_ffi.ffiModel.inputBlocked = !_ffi.ffiModel.inputBlocked;
|
|
||||||
} else if (value == 'refresh') {
|
|
||||||
bind.sessionRefresh(id: widget.id);
|
|
||||||
} else if (value == 'paste') {
|
|
||||||
() async {
|
|
||||||
ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
|
|
||||||
if (data != null && data.text != null) {
|
|
||||||
bind.sessionInputString(id: widget.id, value: data.text ?? "");
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
} else if (value == 'enter_os_password') {
|
|
||||||
// FIXME:
|
|
||||||
// TODO icon diff
|
|
||||||
// null means no session of id
|
|
||||||
// empty string means no password
|
|
||||||
var password = await bind.sessionGetOption(id: id, arg: "os-password");
|
|
||||||
if (password != null) {
|
|
||||||
bind.sessionInputOsPassword(id: widget.id, value: password);
|
|
||||||
} else {
|
|
||||||
showSetOSPassword(widget.id, true, _ffi.dialogManager);
|
|
||||||
}
|
|
||||||
} else if (value == 'reset_canvas') {
|
|
||||||
_ffi.cursorModel.reset();
|
|
||||||
} else if (value == 'restart') {
|
|
||||||
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onWindowEvent(String eventName) {
|
void onWindowEvent(String eventName) {
|
||||||
print("window event: $eventName");
|
print("window event: $eventName");
|
||||||
@ -676,7 +469,7 @@ class ImagePaint extends StatelessWidget {
|
|||||||
{Key? key,
|
{Key? key,
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.cursorOverImage,
|
required this.cursorOverImage,
|
||||||
this.listenerBuilder = null})
|
this.listenerBuilder})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -855,177 +648,7 @@ class QualityMonitor extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: SizedBox.shrink())));
|
: const SizedBox.shrink())));
|
||||||
}
|
|
||||||
|
|
||||||
void showOptions(String id) async {
|
|
||||||
final _ffi = ffi(id);
|
|
||||||
String quality = await bind.sessionGetImageQuality(id: id) ?? 'balanced';
|
|
||||||
if (quality == '') quality = 'balanced';
|
|
||||||
String viewStyle =
|
|
||||||
await bind.sessionGetOption(id: id, arg: 'view-style') ?? '';
|
|
||||||
String scrollStyle =
|
|
||||||
await bind.sessionGetOption(id: id, arg: 'scroll-style') ?? '';
|
|
||||||
var displays = <Widget>[];
|
|
||||||
final pi = _ffi.ffiModel.pi;
|
|
||||||
final image = _ffi.ffiModel.getConnectionImage();
|
|
||||||
if (image != null)
|
|
||||||
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
|
|
||||||
if (pi.displays.length > 1) {
|
|
||||||
final cur = pi.currentDisplay;
|
|
||||||
final children = <Widget>[];
|
|
||||||
for (var i = 0; i < pi.displays.length; ++i)
|
|
||||||
children.add(InkWell(
|
|
||||||
onTap: () {
|
|
||||||
if (i == cur) return;
|
|
||||||
bind.sessionSwitchDisplay(id: id, value: i);
|
|
||||||
_ffi.dialogManager.dismissAll();
|
|
||||||
},
|
|
||||||
child: Ink(
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(color: Colors.black87),
|
|
||||||
color: i == cur ? Colors.black87 : Colors.white),
|
|
||||||
child: Center(
|
|
||||||
child: Text((i + 1).toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: i == cur ? Colors.white : Colors.black87))))));
|
|
||||||
displays.add(Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8),
|
|
||||||
child: Wrap(
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
spacing: 8,
|
|
||||||
children: children,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
if (displays.isNotEmpty) {
|
|
||||||
displays.add(Divider(color: MyTheme.border));
|
|
||||||
}
|
|
||||||
final perms = _ffi.ffiModel.permissions;
|
|
||||||
|
|
||||||
_ffi.dialogManager.show((setState, close) {
|
|
||||||
final more = <Widget>[];
|
|
||||||
if (perms['audio'] != false) {
|
|
||||||
more.add(getToggle(id, setState, 'disable-audio', 'Mute'));
|
|
||||||
}
|
|
||||||
if (perms['keyboard'] != false) {
|
|
||||||
if (perms['clipboard'] != false)
|
|
||||||
more.add(
|
|
||||||
getToggle(id, setState, 'disable-clipboard', 'Disable clipboard'));
|
|
||||||
more.add(getToggle(
|
|
||||||
id, setState, 'lock-after-session-end', 'Lock after session end'));
|
|
||||||
if (pi.platform == 'Windows') {
|
|
||||||
more.add(Consumer<FfiModel>(
|
|
||||||
builder: (_context, _ffiModel, _child) => () {
|
|
||||||
return getToggle(
|
|
||||||
id, setState, 'privacy-mode', 'Privacy mode');
|
|
||||||
}()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var setQuality = (String? value) {
|
|
||||||
if (value == null) return;
|
|
||||||
setState(() {
|
|
||||||
quality = value;
|
|
||||||
bind.sessionSetImageQuality(id: id, value: value);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var setViewStyle = (String? value) {
|
|
||||||
if (value == null) return;
|
|
||||||
setState(() {
|
|
||||||
viewStyle = value;
|
|
||||||
bind.sessionPeerOption(id: id, name: "view-style", value: value);
|
|
||||||
_ffi.canvasModel.updateViewStyle();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var setScrollStyle = (String? value) {
|
|
||||||
if (value == null) return;
|
|
||||||
setState(() {
|
|
||||||
scrollStyle = value;
|
|
||||||
bind.sessionPeerOption(id: id, name: "scroll-style", value: value);
|
|
||||||
_ffi.canvasModel.updateScrollStyle();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return CustomAlertDialog(
|
|
||||||
title: SizedBox.shrink(),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: displays +
|
|
||||||
<Widget>[
|
|
||||||
getRadio('Original', 'original', viewStyle, setViewStyle),
|
|
||||||
getRadio('Shrink', 'shrink', viewStyle, setViewStyle),
|
|
||||||
getRadio('Stretch', 'stretch', viewStyle, setViewStyle),
|
|
||||||
Divider(color: MyTheme.border),
|
|
||||||
getRadio(
|
|
||||||
'ScrollAuto', 'scrollauto', scrollStyle, setScrollStyle),
|
|
||||||
getRadio('Scrollbar', 'scrollbar', scrollStyle, setScrollStyle),
|
|
||||||
Divider(color: MyTheme.border),
|
|
||||||
getRadio('Good image quality', 'best', quality, setQuality),
|
|
||||||
getRadio('Balanced', 'balanced', quality, setQuality),
|
|
||||||
getRadio('Optimize reaction time', 'low', quality, setQuality),
|
|
||||||
Divider(color: MyTheme.border),
|
|
||||||
getToggle(
|
|
||||||
id, setState, 'show-remote-cursor', 'Show remote cursor'),
|
|
||||||
getToggle(id, setState, 'show-quality-monitor',
|
|
||||||
'Show quality monitor',
|
|
||||||
ffi: _ffi),
|
|
||||||
] +
|
|
||||||
more),
|
|
||||||
actions: [],
|
|
||||||
contentPadding: 0,
|
|
||||||
);
|
|
||||||
}, clickMaskDismiss: true, backDismiss: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void showSetOSPassword(
|
|
||||||
String id, bool login, OverlayDialogManager dialogManager) async {
|
|
||||||
final controller = TextEditingController();
|
|
||||||
var password = await bind.sessionGetOption(id: id, arg: "os-password") ?? "";
|
|
||||||
var autoLogin = await bind.sessionGetOption(id: id, arg: "auto-login") != "";
|
|
||||||
controller.text = password;
|
|
||||||
dialogManager.show((setState, close) {
|
|
||||||
return CustomAlertDialog(
|
|
||||||
title: Text(translate('OS Password')),
|
|
||||||
content: Column(mainAxisSize: MainAxisSize.min, children: [
|
|
||||||
PasswordWidget(controller: controller),
|
|
||||||
CheckboxListTile(
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
dense: true,
|
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
|
||||||
title: Text(
|
|
||||||
translate('Auto Login'),
|
|
||||||
),
|
|
||||||
value: autoLogin,
|
|
||||||
onChanged: (v) {
|
|
||||||
if (v == null) return;
|
|
||||||
setState(() => autoLogin = v);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
style: flatButtonStyle,
|
|
||||||
onPressed: () {
|
|
||||||
close();
|
|
||||||
},
|
|
||||||
child: Text(translate('Cancel')),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
style: flatButtonStyle,
|
|
||||||
onPressed: () {
|
|
||||||
var text = controller.text.trim();
|
|
||||||
bind.sessionPeerOption(id: id, name: "os-password", value: text);
|
|
||||||
bind.sessionPeerOption(
|
|
||||||
id: id, name: "auto-login", value: autoLogin ? 'Y' : '');
|
|
||||||
if (text != "" && login) {
|
|
||||||
bind.sessionInputOsPassword(id: id, value: text);
|
|
||||||
}
|
|
||||||
close();
|
|
||||||
},
|
|
||||||
child: Text(translate('OK')),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendPrompt(String id, bool isMac, String key) {
|
void sendPrompt(String id, bool isMac, String key) {
|
||||||
|
@ -15,7 +15,7 @@ class _PopupMenuTheme {
|
|||||||
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 = 12.0;
|
static const double dividerHeight = 3.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef PopupMenuEntryBuilder = Future<List<mod_menu.PopupMenuEntry<String>>>
|
typedef PopupMenuEntryBuilder = Future<List<mod_menu.PopupMenuEntry<String>>>
|
||||||
@ -46,6 +46,7 @@ class _PeerCard extends StatefulWidget {
|
|||||||
/// State for the connection page.
|
/// State for the connection page.
|
||||||
class _PeerCardState extends State<_PeerCard>
|
class _PeerCardState extends State<_PeerCard>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
|
var _menuPos = RelativeRect.fill;
|
||||||
final double _cardRadis = 20;
|
final double _cardRadis = 20;
|
||||||
final double _borderWidth = 2;
|
final double _borderWidth = 2;
|
||||||
final RxBool _iconMoreHover = false.obs;
|
final RxBool _iconMoreHover = false.obs;
|
||||||
@ -253,36 +254,36 @@ class _PeerCardState extends State<_PeerCard>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _actionMore(Peer peer) {
|
Widget _actionMore(Peer peer) => Listener(
|
||||||
return FutureBuilder(
|
onPointerDown: (e) {
|
||||||
future: widget.popupMenuEntryBuilder(context),
|
final x = e.position.dx;
|
||||||
initialData: const <mod_menu.PopupMenuEntry<String>>[],
|
final y = e.position.dy;
|
||||||
builder: (BuildContext context,
|
_menuPos = RelativeRect.fromLTRB(x, y, x, y);
|
||||||
AsyncSnapshot<List<mod_menu.PopupMenuEntry<String>>> snapshot) {
|
},
|
||||||
if (snapshot.hasData) {
|
onPointerUp: (_) => _showPeerMenu(context, peer.id),
|
||||||
return Listener(
|
child: MouseRegion(
|
||||||
child: MouseRegion(
|
onEnter: (_) => _iconMoreHover.value = true,
|
||||||
onEnter: (_) => _iconMoreHover.value = true,
|
onExit: (_) => _iconMoreHover.value = false,
|
||||||
onExit: (_) => _iconMoreHover.value = false,
|
child: CircleAvatar(
|
||||||
child: CircleAvatar(
|
radius: 14,
|
||||||
radius: 14,
|
backgroundColor: _iconMoreHover.value
|
||||||
backgroundColor: _iconMoreHover.value
|
? MyTheme.color(context).grayBg!
|
||||||
? MyTheme.color(context).grayBg!
|
: MyTheme.color(context).bg!,
|
||||||
: MyTheme.color(context).bg!,
|
child: Icon(Icons.more_vert,
|
||||||
child: mod_menu.PopupMenuButton(
|
size: 18,
|
||||||
padding: EdgeInsets.zero,
|
color: _iconMoreHover.value
|
||||||
icon: Icon(Icons.more_vert,
|
? MyTheme.color(context).text
|
||||||
size: 18,
|
: MyTheme.color(context).lightText))));
|
||||||
color: _iconMoreHover.value
|
|
||||||
? MyTheme.color(context).text
|
/// Show the peer menu and handle user's choice.
|
||||||
: MyTheme.color(context).lightText),
|
/// User might remove the peer or send a file to the peer.
|
||||||
position: mod_menu.PopupMenuPosition.over,
|
void _showPeerMenu(BuildContext context, String id) async {
|
||||||
itemBuilder: (BuildContext context) => snapshot.data!,
|
await mod_menu.showMenu(
|
||||||
))));
|
context: context,
|
||||||
} else {
|
position: _menuPos,
|
||||||
return Container();
|
items: await super.widget.popupMenuEntryBuilder(context),
|
||||||
}
|
elevation: 8,
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the image for the current [platform].
|
/// Get the image for the current [platform].
|
||||||
@ -411,19 +412,26 @@ abstract class BasePeerCard extends StatelessWidget {
|
|||||||
@protected
|
@protected
|
||||||
MenuEntryBase<String> _rdpAction(BuildContext context, String id) {
|
MenuEntryBase<String> _rdpAction(BuildContext context, String id) {
|
||||||
return MenuEntryButton<String>(
|
return MenuEntryButton<String>(
|
||||||
childBuilder: (TextStyle? style) => Row(
|
childBuilder: (TextStyle? style) => Container(
|
||||||
children: [
|
alignment: AlignmentDirectional.center,
|
||||||
Text(
|
height: _PopupMenuTheme.height,
|
||||||
translate('RDP'),
|
child: Row(
|
||||||
style: style,
|
children: [
|
||||||
),
|
Text(
|
||||||
SizedBox(width: 20),
|
translate('RDP'),
|
||||||
IconButton(
|
style: style,
|
||||||
icon: Icon(Icons.edit),
|
),
|
||||||
onPressed: () => _rdpDialog(id),
|
Expanded(
|
||||||
)
|
child: Align(
|
||||||
],
|
alignment: Alignment.centerRight,
|
||||||
),
|
child: IconButton(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
icon: Icon(Icons.edit),
|
||||||
|
onPressed: () => _rdpDialog(id),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
)),
|
||||||
proc: () {
|
proc: () {
|
||||||
_connect(context, id, isRDP: true);
|
_connect(context, id, isRDP: true);
|
||||||
},
|
},
|
||||||
@ -614,6 +622,7 @@ class RecentPeerCard extends BasePeerCard {
|
|||||||
if (peer.platform == 'Windows') {
|
if (peer.platform == 'Windows') {
|
||||||
menuItems.add(_rdpAction(context, peer.id));
|
menuItems.add(_rdpAction(context, peer.id));
|
||||||
}
|
}
|
||||||
|
menuItems.add(MenuEntryDivider());
|
||||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||||
menuItems.add(_renameAction(peer.id, false));
|
menuItems.add(_renameAction(peer.id, false));
|
||||||
menuItems.add(_removeAction(peer.id, () async {
|
menuItems.add(_removeAction(peer.id, () async {
|
||||||
|
@ -472,9 +472,17 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
});
|
});
|
||||||
final quality =
|
final quality =
|
||||||
await bind.sessionGetCustomImageQuality(id: widget.id);
|
await bind.sessionGetCustomImageQuality(id: widget.id);
|
||||||
final double initValue = quality != null && quality.isNotEmpty
|
double initValue = quality != null && quality.isNotEmpty
|
||||||
? quality[0].toDouble()
|
? quality[0].toDouble()
|
||||||
: 50.0;
|
: 50.0;
|
||||||
|
const minValue = 10.0;
|
||||||
|
const maxValue = 100.0;
|
||||||
|
if (initValue < minValue) {
|
||||||
|
initValue = minValue;
|
||||||
|
}
|
||||||
|
if (initValue > maxValue) {
|
||||||
|
initValue = maxValue;
|
||||||
|
}
|
||||||
final RxDouble sliderValue = RxDouble(initValue);
|
final RxDouble sliderValue = RxDouble(initValue);
|
||||||
final rxReplay = rxdart.ReplaySubject<double>();
|
final rxReplay = rxdart.ReplaySubject<double>();
|
||||||
rxReplay
|
rxReplay
|
||||||
@ -489,10 +497,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
final slider = Obx(() {
|
final slider = Obx(() {
|
||||||
return Slider(
|
return Slider(
|
||||||
value: sliderValue.value,
|
value: sliderValue.value,
|
||||||
min: 10.0,
|
min: minValue,
|
||||||
max: 100.0,
|
max: maxValue,
|
||||||
divisions: 90,
|
divisions: 90,
|
||||||
// label: sliderValue.value.round().toString(),
|
|
||||||
onChanged: (double value) {
|
onChanged: (double value) {
|
||||||
sliderValue.value = value;
|
sliderValue.value = value;
|
||||||
rxReplay.add(value);
|
rxReplay.add(value);
|
||||||
@ -502,7 +509,12 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
|||||||
final content = Row(
|
final content = Row(
|
||||||
children: [
|
children: [
|
||||||
slider,
|
slider,
|
||||||
Obx(() => Text('${sliderValue.value.round()}% Bitrate'))
|
SizedBox(
|
||||||
|
width: 90,
|
||||||
|
child: Obx(() => Text(
|
||||||
|
'${sliderValue.value.round()}% Bitrate',
|
||||||
|
style: const TextStyle(fontSize: 15),
|
||||||
|
)))
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
|
msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user