diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index fb25742f6..7bb338cb4 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -18,6 +18,7 @@ import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/utils/platform_channel.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +import 'package:provider/provider.dart'; import 'package:uni_links/uni_links.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:uuid/uuid.dart'; @@ -858,30 +859,10 @@ class OverlayDialogManager { final overlayState = _overlayKeyState.state; if (overlayState == null) return; - // compute overlay position - final screenW = MediaQuery.of(globalKey.currentContext!).size.width; - final screenH = MediaQuery.of(globalKey.currentContext!).size.height; - const double overlayW = 200; - const double overlayH = 45; - final left = (screenW - overlayW) / 2; - final top = screenH - overlayH - 80; - - final overlay = OverlayEntry(builder: (context) { - final session = ffi ?? gFFI; - return DraggableMobileActions( - position: Offset(left, top), - width: overlayW, - height: overlayH, - onBackPressed: () => session.inputModel.tap(MouseButtons.right), - onHomePressed: () => session.inputModel.tap(MouseButtons.wheel), - onRecentPressed: () async { - session.inputModel.sendMouse('down', MouseButtons.wheel); - await Future.delayed(const Duration(milliseconds: 500)); - session.inputModel.sendMouse('up', MouseButtons.wheel); - }, - onHidePressed: () => hideMobileActionsOverlay(), - ); - }); + final overlay = makeMobileActionsOverlayEntry( + () => hideMobileActionsOverlay(), + ffi: ffi, + ); overlayState.insert(overlay); _mobileActionsOverlayEntry = overlay; mobileActionsOverlayVisible.value = true; @@ -909,6 +890,45 @@ class OverlayDialogManager { } } +makeMobileActionsOverlayEntry(VoidCallback? onHide, {FFI? ffi}) { + final position = SimpleWrapper(Offset(0, 0)); + makeMobileActions(BuildContext context, double s) { + final scale = s < 0.85 ? 0.85 : s; + final session = ffi ?? gFFI; + // compute overlay position + final screenW = MediaQuery.of(context).size.width; + final screenH = MediaQuery.of(context).size.height; + const double overlayW = 200; + const double overlayH = 45; + final left = (screenW - overlayW * scale) / 2; + final top = screenH - (overlayH + 80) * scale; + position.value = Offset(left, top); + return DraggableMobileActions( + scale: scale, + position: position, + width: overlayW, + height: overlayH, + onBackPressed: () => session.inputModel.tap(MouseButtons.right), + onHomePressed: () => session.inputModel.tap(MouseButtons.wheel), + onRecentPressed: () async { + session.inputModel.sendMouse('down', MouseButtons.wheel); + await Future.delayed(const Duration(milliseconds: 500)); + session.inputModel.sendMouse('up', MouseButtons.wheel); + }, + onHidePressed: onHide, + ); + } + + return OverlayEntry(builder: (context) { + if (isDesktop) { + final c = Provider.of(context); + return makeMobileActions(context, c.scale * 2.0); + } else { + return makeMobileActions(globalKey.currentContext!, 1.0); + } + }); +} + void showToast(String text, {Duration timeout = const Duration(seconds: 3)}) { final overlayState = globalKey.currentState?.overlay; if (overlayState == null) return; @@ -3255,7 +3275,8 @@ Widget buildPresetPasswordWarning() { translate("Security Alert"), style: TextStyle( color: Colors.red, - fontSize: 18, // https://github.com/rustdesk/rustdesk-server-pro/issues/261 + fontSize: + 18, // https://github.com/rustdesk/rustdesk-server-pro/issues/261 fontWeight: FontWeight.bold, ), )).paddingOnly(bottom: 8), diff --git a/flutter/lib/common/widgets/overlay.dart b/flutter/lib/common/widgets/overlay.dart index 1df7f0317..886e9b117 100644 --- a/flutter/lib/common/widgets/overlay.dart +++ b/flutter/lib/common/widgets/overlay.dart @@ -45,7 +45,7 @@ class DraggableChatWindow extends StatelessWidget { ) : Draggable( checkKeyboard: true, - position: position, + position: SimpleWrapper(position), width: width, height: height, chatModel: chatModel, @@ -166,15 +166,17 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { /// floating buttons of back/home/recent actions for android class DraggableMobileActions extends StatelessWidget { DraggableMobileActions( - {this.position = Offset.zero, - this.onBackPressed, + {this.onBackPressed, this.onRecentPressed, this.onHomePressed, this.onHidePressed, + required this.position, required this.width, - required this.height}); + required this.height, + required this.scale}); - final Offset position; + final double scale; + final SimpleWrapper position; final double width; final double height; final VoidCallback? onBackPressed; @@ -186,8 +188,8 @@ class DraggableMobileActions extends StatelessWidget { Widget build(BuildContext context) { return Draggable( position: position, - width: width, - height: height, + width: scale * width, + height: scale * height, builder: (_, onPanUpdate) { return GestureDetector( onPanUpdate: onPanUpdate, @@ -197,7 +199,8 @@ class DraggableMobileActions extends StatelessWidget { child: Container( decoration: BoxDecoration( color: MyTheme.accent.withOpacity(0.4), - borderRadius: BorderRadius.all(Radius.circular(15))), + borderRadius: + BorderRadius.all(Radius.circular(15 * scale))), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ @@ -205,17 +208,20 @@ class DraggableMobileActions extends StatelessWidget { color: Colors.white, onPressed: onBackPressed, splashRadius: kDesktopIconButtonSplashRadius, - icon: const Icon(Icons.arrow_back)), + icon: const Icon(Icons.arrow_back), + iconSize: 24 * scale), IconButton( color: Colors.white, onPressed: onHomePressed, splashRadius: kDesktopIconButtonSplashRadius, - icon: const Icon(Icons.home)), + icon: const Icon(Icons.home), + iconSize: 24 * scale), IconButton( color: Colors.white, onPressed: onRecentPressed, splashRadius: kDesktopIconButtonSplashRadius, - icon: const Icon(Icons.more_horiz)), + icon: const Icon(Icons.more_horiz), + iconSize: 24 * scale), const VerticalDivider( width: 0, thickness: 2, @@ -226,7 +232,8 @@ class DraggableMobileActions extends StatelessWidget { color: Colors.white, onPressed: onHidePressed, splashRadius: kDesktopIconButtonSplashRadius, - icon: const Icon(Icons.keyboard_arrow_down)), + icon: const Icon(Icons.keyboard_arrow_down), + iconSize: 24 * scale), ], ), ))); @@ -235,11 +242,11 @@ class DraggableMobileActions extends StatelessWidget { } class Draggable extends StatefulWidget { - const Draggable( + Draggable( {Key? key, this.checkKeyboard = false, this.checkScreenSize = false, - this.position = Offset.zero, + required this.position, required this.width, required this.height, this.chatModel, @@ -248,7 +255,7 @@ class Draggable extends StatefulWidget { final bool checkKeyboard; final bool checkScreenSize; - final Offset position; + final SimpleWrapper position; final double width; final double height; final ChatModel? chatModel; @@ -259,7 +266,6 @@ class Draggable extends StatefulWidget { } class _DraggableState extends State { - late Offset _position; late ChatModel? _chatModel; bool _keyboardVisible = false; double _saveHeight = 0; @@ -268,35 +274,36 @@ class _DraggableState extends State { @override void initState() { super.initState(); - _position = widget.position; _chatModel = widget.chatModel; } + get position => widget.position.value; + void onPanUpdate(DragUpdateDetails d) { final offset = d.delta; final size = MediaQuery.of(context).size; double x = 0; double y = 0; - if (_position.dx + offset.dx + widget.width > size.width) { + if (position.dx + offset.dx + widget.width > size.width) { x = size.width - widget.width; - } else if (_position.dx + offset.dx < 0) { + } else if (position.dx + offset.dx < 0) { x = 0; } else { - x = _position.dx + offset.dx; + x = position.dx + offset.dx; } - if (_position.dy + offset.dy + widget.height > size.height) { + if (position.dy + offset.dy + widget.height > size.height) { y = size.height - widget.height; - } else if (_position.dy + offset.dy < 0) { + } else if (position.dy + offset.dy < 0) { y = 0; } else { - y = _position.dy + offset.dy; + y = position.dy + offset.dy; } setState(() { - _position = Offset(x, y); + widget.position.value = Offset(x, y); }); - _chatModel?.setChatWindowPosition(_position); + _chatModel?.setChatWindowPosition(position); } checkScreenSize() {} @@ -307,13 +314,13 @@ class _DraggableState extends State { // save if (!_keyboardVisible && currentVisible) { - _saveHeight = _position.dy; + _saveHeight = position.dy; } // reset if (_lastBottomHeight > 0 && bottomHeight == 0) { setState(() { - _position = Offset(_position.dx, _saveHeight); + widget.position.value = Offset(position.dx, _saveHeight); }); } @@ -321,10 +328,10 @@ class _DraggableState extends State { if (_keyboardVisible && currentVisible) { final sumHeight = bottomHeight + widget.height; final contextHeight = MediaQuery.of(context).size.height; - if (sumHeight + _position.dy > contextHeight) { + if (sumHeight + position.dy > contextHeight) { final y = contextHeight - sumHeight; setState(() { - _position = Offset(_position.dx, y); + widget.position.value = Offset(position.dx, y); }); } } @@ -343,8 +350,8 @@ class _DraggableState extends State { } return Stack(children: [ Positioned( - top: _position.dy, - left: _position.dx, + top: position.dy, + left: position.dx, width: widget.width, height: widget.height, child: widget.builder(context, onPanUpdate)) diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 8bd3b0b21..a1266dc47 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -307,8 +307,30 @@ class _RemotePageState extends State _ffi.ffiModel.waitForFirstImage.isTrue ? emptyOverlay() : () { - _ffi.ffiModel.tryShowAndroidActionsOverlay(); - return Offstage(); + if (!_ffi.ffiModel.isPeerAndroid) { + return Offstage(); + } else { + if (_ffi.connType == ConnType.defaultConn && + _ffi.ffiModel.permissions['keyboard'] != false) { + Timer( + Duration(milliseconds: 10), + () => _ffi.dialogManager + .mobileActionsOverlayVisible.value = true); + } + return Obx(() => Offstage( + offstage: _ffi.dialogManager + .mobileActionsOverlayVisible.isFalse, + child: Overlay(initialEntries: [ + makeMobileActionsOverlayEntry( + () => _ffi + .dialogManager + .mobileActionsOverlayVisible + .value = false, + ffi: _ffi, + ) + ]), + )); + } }(), // Use Overlay to enable rebuild every time on menu button click. _ffi.ffiModel.pi.isSet.isTrue diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index aa6b7de48..1374b08f7 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -588,7 +588,7 @@ class _MobileActionMenu extends StatelessWidget { assetName: 'assets/actions_mobile.svg', tooltip: 'Mobile Actions', onPressed: () => - ffi.dialogManager.toggleMobileActionsOverlay(ffi: ffi), + ffi.dialogManager.mobileActionsOverlayVisible.toggle(), color: ffi.dialogManager.mobileActionsOverlayVisible.isTrue ? _ToolbarTheme.blueColor : _ToolbarTheme.inactiveColor,