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/desktop/pages/connection_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/server_model.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 '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import '../../models/chat_model.dart';
|
||||
import '../../common/shared_state.dart';
|
||||
|
||||
final initText = '\1' * 1024;
|
||||
@ -39,7 +38,6 @@ class RemotePage extends StatefulWidget {
|
||||
class _RemotePageState extends State<RemotePage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
Timer? _timer;
|
||||
bool _showBar = !isWebDesktop;
|
||||
String _value = '';
|
||||
final _cursorOverImage = false.obs;
|
||||
|
||||
@ -131,7 +129,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
common < oldValue.length &&
|
||||
common < newValue.length &&
|
||||
newValue[common] == oldValue[common];
|
||||
++common);
|
||||
++common) {}
|
||||
for (i = 0; i < oldValue.length - common; ++i) {
|
||||
_ffi.inputKey('VK_BACK');
|
||||
}
|
||||
@ -145,8 +143,8 @@ class _RemotePageState extends State<RemotePage>
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (oldValue.length > 0 &&
|
||||
newValue.length > 0 &&
|
||||
if (oldValue.isNotEmpty &&
|
||||
newValue.isNotEmpty &&
|
||||
oldValue[0] == '\1' &&
|
||||
newValue[0] != '\1') {
|
||||
// clipboard
|
||||
@ -155,7 +153,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
if (newValue.length == oldValue.length) {
|
||||
// ?
|
||||
} else if (newValue.length < oldValue.length) {
|
||||
final char = 'VK_BACK';
|
||||
const char = 'VK_BACK';
|
||||
_ffi.inputKey(char);
|
||||
} else {
|
||||
final content = newValue.substring(oldValue.length);
|
||||
@ -200,24 +198,9 @@ class _RemotePageState extends State<RemotePage>
|
||||
}
|
||||
|
||||
Widget buildBody(BuildContext context, FfiModel ffiModel) {
|
||||
final hasDisplays = ffiModel.pi.displays.length > 0;
|
||||
final keyboard = ffiModel.permissions['keyboard'] != false;
|
||||
return Scaffold(
|
||||
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(
|
||||
initialEntries: [
|
||||
OverlayEntry(builder: (context) {
|
||||
@ -249,7 +232,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
ChangeNotifierProvider.value(value: _ffi.canvasModel),
|
||||
],
|
||||
child: Consumer<FfiModel>(
|
||||
builder: (context, ffiModel, _child) =>
|
||||
builder: (context, ffiModel, child) =>
|
||||
buildBody(context, ffiModel))));
|
||||
}
|
||||
|
||||
@ -307,100 +290,6 @@ class _RemotePageState extends State<RemotePage>
|
||||
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:
|
||||
/// LongPress -> right click
|
||||
/// OneFingerPan -> start/end -> left down start/end
|
||||
@ -458,12 +347,16 @@ class _RemotePageState extends State<RemotePage>
|
||||
if (e is PointerScrollEvent) {
|
||||
var dx = e.scrollDelta.dx.toInt();
|
||||
var dy = e.scrollDelta.dy.toInt();
|
||||
if (dx > 0)
|
||||
if (dx > 0) {
|
||||
dx = -1;
|
||||
else if (dx < 0) dx = 1;
|
||||
if (dy > 0)
|
||||
} else if (dx < 0) {
|
||||
dx = 1;
|
||||
}
|
||||
if (dy > 0) {
|
||||
dy = -1;
|
||||
else if (dy < 0) dy = 1;
|
||||
} else if (dy < 0) {
|
||||
dy = 1;
|
||||
}
|
||||
bind.sessionSendMouse(
|
||||
id: widget.id, msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
|
||||
}
|
||||
@ -546,106 +439,6 @@ class _RemotePageState extends State<RemotePage>
|
||||
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
|
||||
void onWindowEvent(String eventName) {
|
||||
print("window event: $eventName");
|
||||
@ -676,7 +469,7 @@ class ImagePaint extends StatelessWidget {
|
||||
{Key? key,
|
||||
required this.id,
|
||||
required this.cursorOverImage,
|
||||
this.listenerBuilder = null})
|
||||
this.listenerBuilder})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -855,177 +648,7 @@ class QualityMonitor extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
)
|
||||
: 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')),
|
||||
),
|
||||
]);
|
||||
});
|
||||
: const SizedBox.shrink())));
|
||||
}
|
||||
|
||||
void sendPrompt(String id, bool isMac, String key) {
|
||||
|
@ -15,7 +15,7 @@ class _PopupMenuTheme {
|
||||
static const Color commonColor = MyTheme.accent;
|
||||
// kMinInteractiveDimension
|
||||
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>>>
|
||||
@ -46,6 +46,7 @@ class _PeerCard extends StatefulWidget {
|
||||
/// State for the connection page.
|
||||
class _PeerCardState extends State<_PeerCard>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
var _menuPos = RelativeRect.fill;
|
||||
final double _cardRadis = 20;
|
||||
final double _borderWidth = 2;
|
||||
final RxBool _iconMoreHover = false.obs;
|
||||
@ -253,36 +254,36 @@ class _PeerCardState extends State<_PeerCard>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionMore(Peer peer) {
|
||||
return FutureBuilder(
|
||||
future: widget.popupMenuEntryBuilder(context),
|
||||
initialData: const <mod_menu.PopupMenuEntry<String>>[],
|
||||
builder: (BuildContext context,
|
||||
AsyncSnapshot<List<mod_menu.PopupMenuEntry<String>>> snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Listener(
|
||||
child: MouseRegion(
|
||||
onEnter: (_) => _iconMoreHover.value = true,
|
||||
onExit: (_) => _iconMoreHover.value = false,
|
||||
child: CircleAvatar(
|
||||
radius: 14,
|
||||
backgroundColor: _iconMoreHover.value
|
||||
? MyTheme.color(context).grayBg!
|
||||
: MyTheme.color(context).bg!,
|
||||
child: mod_menu.PopupMenuButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Icon(Icons.more_vert,
|
||||
size: 18,
|
||||
color: _iconMoreHover.value
|
||||
? MyTheme.color(context).text
|
||||
: MyTheme.color(context).lightText),
|
||||
position: mod_menu.PopupMenuPosition.over,
|
||||
itemBuilder: (BuildContext context) => snapshot.data!,
|
||||
))));
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
});
|
||||
Widget _actionMore(Peer peer) => Listener(
|
||||
onPointerDown: (e) {
|
||||
final x = e.position.dx;
|
||||
final y = e.position.dy;
|
||||
_menuPos = RelativeRect.fromLTRB(x, y, x, y);
|
||||
},
|
||||
onPointerUp: (_) => _showPeerMenu(context, peer.id),
|
||||
child: MouseRegion(
|
||||
onEnter: (_) => _iconMoreHover.value = true,
|
||||
onExit: (_) => _iconMoreHover.value = false,
|
||||
child: CircleAvatar(
|
||||
radius: 14,
|
||||
backgroundColor: _iconMoreHover.value
|
||||
? MyTheme.color(context).grayBg!
|
||||
: MyTheme.color(context).bg!,
|
||||
child: Icon(Icons.more_vert,
|
||||
size: 18,
|
||||
color: _iconMoreHover.value
|
||||
? MyTheme.color(context).text
|
||||
: MyTheme.color(context).lightText))));
|
||||
|
||||
/// Show the peer menu and handle user's choice.
|
||||
/// User might remove the peer or send a file to the peer.
|
||||
void _showPeerMenu(BuildContext context, String id) async {
|
||||
await mod_menu.showMenu(
|
||||
context: context,
|
||||
position: _menuPos,
|
||||
items: await super.widget.popupMenuEntryBuilder(context),
|
||||
elevation: 8,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get the image for the current [platform].
|
||||
@ -411,19 +412,26 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
@protected
|
||||
MenuEntryBase<String> _rdpAction(BuildContext context, String id) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Row(
|
||||
children: [
|
||||
Text(
|
||||
translate('RDP'),
|
||||
style: style,
|
||||
),
|
||||
SizedBox(width: 20),
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit),
|
||||
onPressed: () => _rdpDialog(id),
|
||||
)
|
||||
],
|
||||
),
|
||||
childBuilder: (TextStyle? style) => Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
height: _PopupMenuTheme.height,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
translate('RDP'),
|
||||
style: style,
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Icon(Icons.edit),
|
||||
onPressed: () => _rdpDialog(id),
|
||||
),
|
||||
))
|
||||
],
|
||||
)),
|
||||
proc: () {
|
||||
_connect(context, id, isRDP: true);
|
||||
},
|
||||
@ -614,6 +622,7 @@ class RecentPeerCard extends BasePeerCard {
|
||||
if (peer.platform == 'Windows') {
|
||||
menuItems.add(_rdpAction(context, peer.id));
|
||||
}
|
||||
menuItems.add(MenuEntryDivider());
|
||||
menuItems.add(await _forceAlwaysRelayAction(peer.id));
|
||||
menuItems.add(_renameAction(peer.id, false));
|
||||
menuItems.add(_removeAction(peer.id, () async {
|
||||
|
@ -472,9 +472,17 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
});
|
||||
final quality =
|
||||
await bind.sessionGetCustomImageQuality(id: widget.id);
|
||||
final double initValue = quality != null && quality.isNotEmpty
|
||||
double initValue = quality != null && quality.isNotEmpty
|
||||
? quality[0].toDouble()
|
||||
: 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 rxReplay = rxdart.ReplaySubject<double>();
|
||||
rxReplay
|
||||
@ -489,10 +497,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
final slider = Obx(() {
|
||||
return Slider(
|
||||
value: sliderValue.value,
|
||||
min: 10.0,
|
||||
max: 100.0,
|
||||
min: minValue,
|
||||
max: maxValue,
|
||||
divisions: 90,
|
||||
// label: sliderValue.value.round().toString(),
|
||||
onChanged: (double value) {
|
||||
sliderValue.value = value;
|
||||
rxReplay.add(value);
|
||||
@ -502,7 +509,12 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
final content = Row(
|
||||
children: [
|
||||
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',
|
||||
|
Loading…
Reference in New Issue
Block a user