fix: mobile actions, position (#8446)
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
		
							parent
							
								
									a9e0ea8520
								
							
						
					
					
						commit
						40cb59336f
					
				| @ -718,7 +718,21 @@ class OverlayDialogManager { | ||||
|   int _tagCount = 0; | ||||
| 
 | ||||
|   OverlayEntry? _mobileActionsOverlayEntry; | ||||
|   RxBool mobileActionsOverlayVisible = false.obs; | ||||
|   RxBool mobileActionsOverlayVisible = true.obs; | ||||
| 
 | ||||
|   setMobileActionsOverlayVisible(bool v, {store = true}) { | ||||
|     if (store) { | ||||
|       bind.setLocalFlutterOption(k: kOptionShowMobileAction, v: v ? 'Y' : 'N'); | ||||
|     } | ||||
|     // No need to read the value from local storage after setting it. | ||||
|     // It better to toggle the value directly. | ||||
|     mobileActionsOverlayVisible.value = v; | ||||
|   } | ||||
| 
 | ||||
|   loadMobileActionsOverlayVisible() { | ||||
|     mobileActionsOverlayVisible.value = | ||||
|         bind.getLocalFlutterOption(k: kOptionShowMobileAction) != 'N'; | ||||
|   } | ||||
| 
 | ||||
|   void setOverlayState(OverlayKeyState overlayKeyState) { | ||||
|     _overlayKeyState = overlayKeyState; | ||||
| @ -865,14 +879,14 @@ class OverlayDialogManager { | ||||
|     ); | ||||
|     overlayState.insert(overlay); | ||||
|     _mobileActionsOverlayEntry = overlay; | ||||
|     mobileActionsOverlayVisible.value = true; | ||||
|     setMobileActionsOverlayVisible(true); | ||||
|   } | ||||
| 
 | ||||
|   void hideMobileActionsOverlay() { | ||||
|   void hideMobileActionsOverlay({store = true}) { | ||||
|     if (_mobileActionsOverlayEntry != null) { | ||||
|       _mobileActionsOverlayEntry!.remove(); | ||||
|       _mobileActionsOverlayEntry = null; | ||||
|       mobileActionsOverlayVisible.value = false; | ||||
|       setMobileActionsOverlayVisible(false, store: store); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| @ -891,21 +905,27 @@ 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); | ||||
|     computeOverlayPosition() { | ||||
|       final screenW = MediaQuery.of(context).size.width; | ||||
|       final screenH = MediaQuery.of(context).size.height; | ||||
|       final left = (screenW - overlayW * scale) / 2; | ||||
|       final top = screenH - (overlayH + 80) * scale; | ||||
|       return Offset(left, top); | ||||
|     } | ||||
| 
 | ||||
|     if (draggablePositions.mobileActions.isInvalid()) { | ||||
|       draggablePositions.mobileActions.update(computeOverlayPosition()); | ||||
|     } else { | ||||
|       draggablePositions.mobileActions.tryAdjust(overlayW, overlayH, scale); | ||||
|     } | ||||
|     return DraggableMobileActions( | ||||
|       scale: scale, | ||||
|       position: position, | ||||
|       position: draggablePositions.mobileActions, | ||||
|       width: overlayW, | ||||
|       height: overlayH, | ||||
|       onBackPressed: () => session.inputModel.tap(MouseButtons.right), | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| import 'package:auto_size_text/auto_size_text.dart'; | ||||
| import 'package:debounce_throttle/debounce_throttle.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hbb/common.dart'; | ||||
| import 'package:flutter_hbb/models/platform_model.dart'; | ||||
| import 'package:get/get.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| 
 | ||||
| @ -26,9 +28,12 @@ class DraggableChatWindow extends StatelessWidget { | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (draggablePositions.chatWindow.isInvalid()) { | ||||
|       draggablePositions.chatWindow.update(position); | ||||
|     } | ||||
|     return isIOS | ||||
|         ? IOSDraggable( | ||||
|             position: position, | ||||
|             position: draggablePositions.chatWindow, | ||||
|             chatModel: chatModel, | ||||
|             width: width, | ||||
|             height: height, | ||||
| @ -45,7 +50,7 @@ class DraggableChatWindow extends StatelessWidget { | ||||
|           ) | ||||
|         : Draggable( | ||||
|             checkKeyboard: true, | ||||
|             position: SimpleWrapper(position), | ||||
|             position: draggablePositions.chatWindow, | ||||
|             width: width, | ||||
|             height: height, | ||||
|             chatModel: chatModel, | ||||
| @ -176,7 +181,7 @@ class DraggableMobileActions extends StatelessWidget { | ||||
|       required this.scale}); | ||||
| 
 | ||||
|   final double scale; | ||||
|   final SimpleWrapper<Offset> position; | ||||
|   final DraggableKeyPosition position; | ||||
|   final double width; | ||||
|   final double height; | ||||
|   final VoidCallback? onBackPressed; | ||||
| @ -241,6 +246,92 @@ class DraggableMobileActions extends StatelessWidget { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class DraggableKeyPosition { | ||||
|   final String key; | ||||
|   Offset _pos; | ||||
|   late Debouncer<int> _debouncerStore; | ||||
|   DraggableKeyPosition(this.key) | ||||
|       : _pos = DraggablePositions.kInvalidDraggablePosition; | ||||
| 
 | ||||
|   get pos => _pos; | ||||
| 
 | ||||
|   _loadPosition(String k) { | ||||
|     final value = bind.getLocalFlutterOption(k: k); | ||||
|     if (value.isNotEmpty) { | ||||
|       final parts = value.split(','); | ||||
|       if (parts.length == 2) { | ||||
|         return Offset(double.parse(parts[0]), double.parse(parts[1])); | ||||
|       } | ||||
|     } | ||||
|     return DraggablePositions.kInvalidDraggablePosition; | ||||
|   } | ||||
| 
 | ||||
|   load() { | ||||
|     _pos = _loadPosition(key); | ||||
|     _debouncerStore = Debouncer<int>(const Duration(milliseconds: 500), | ||||
|         onChanged: (v) => _store(), initialValue: 0); | ||||
|   } | ||||
| 
 | ||||
|   update(Offset pos) { | ||||
|     _pos = pos; | ||||
|     _triggerStore(); | ||||
|   } | ||||
| 
 | ||||
|   // Adjust position to keep it in the screen | ||||
|   // Only used for desktop and web desktop | ||||
|   tryAdjust(double w, double h, double scale) { | ||||
|     final size = MediaQuery.of(Get.context!).size; | ||||
|     w = w * scale; | ||||
|     h = h * scale; | ||||
|     double x = _pos.dx; | ||||
|     double y = _pos.dy; | ||||
|     if (x + w > size.width) { | ||||
|       x = size.width - w; | ||||
|     } | ||||
|     final tabBarHeight = isDesktop ? kDesktopRemoteTabBarHeight : 0; | ||||
|     if (y + h > (size.height - tabBarHeight)) { | ||||
|       y = size.height - tabBarHeight - h; | ||||
|     } | ||||
|     if (x < 0) { | ||||
|       x = 0; | ||||
|     } | ||||
|     if (y < 0) { | ||||
|       y = 0; | ||||
|     } | ||||
|     if (x != _pos.dx || y != _pos.dy) { | ||||
|       update(Offset(x, y)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   isInvalid() { | ||||
|     return _pos == DraggablePositions.kInvalidDraggablePosition; | ||||
|   } | ||||
| 
 | ||||
|   _triggerStore() => _debouncerStore.value = _debouncerStore.value + 1; | ||||
|   _store() { | ||||
|     bind.setLocalFlutterOption(k: key, v: '${_pos.dx},${_pos.dy}'); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class DraggablePositions { | ||||
|   static const kChatWindow = 'draggablePositionChat'; | ||||
|   static const kMobileActions = 'draggablePositionMobile'; | ||||
|   static const kIOSDraggable = 'draggablePositionIOS'; | ||||
| 
 | ||||
|   static const kInvalidDraggablePosition = Offset(-999999, -999999); | ||||
|   final chatWindow = DraggableKeyPosition(kChatWindow); | ||||
|   final mobileActions = DraggableKeyPosition(kMobileActions); | ||||
|   final iOSDraggable = DraggableKeyPosition(kIOSDraggable); | ||||
| 
 | ||||
|   load() { | ||||
|     chatWindow.load(); | ||||
|     mobileActions.load(); | ||||
|     iOSDraggable.load(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| DraggablePositions draggablePositions = DraggablePositions(); | ||||
| 
 | ||||
| class Draggable extends StatefulWidget { | ||||
|   Draggable( | ||||
|       {Key? key, | ||||
| @ -255,7 +346,7 @@ class Draggable extends StatefulWidget { | ||||
| 
 | ||||
|   final bool checkKeyboard; | ||||
|   final bool checkScreenSize; | ||||
|   final SimpleWrapper<Offset> position; | ||||
|   final DraggableKeyPosition position; | ||||
|   final double width; | ||||
|   final double height; | ||||
|   final ChatModel? chatModel; | ||||
| @ -277,7 +368,7 @@ class _DraggableState extends State<Draggable> { | ||||
|     _chatModel = widget.chatModel; | ||||
|   } | ||||
| 
 | ||||
|   get position => widget.position.value; | ||||
|   get position => widget.position.pos; | ||||
| 
 | ||||
|   void onPanUpdate(DragUpdateDetails d) { | ||||
|     final offset = d.delta; | ||||
| @ -301,7 +392,7 @@ class _DraggableState extends State<Draggable> { | ||||
|       y = position.dy + offset.dy; | ||||
|     } | ||||
|     setState(() { | ||||
|       widget.position.value = Offset(x, y); | ||||
|       widget.position.update(Offset(x, y)); | ||||
|     }); | ||||
|     _chatModel?.setChatWindowPosition(position); | ||||
|   } | ||||
| @ -320,7 +411,7 @@ class _DraggableState extends State<Draggable> { | ||||
|     // reset | ||||
|     if (_lastBottomHeight > 0 && bottomHeight == 0) { | ||||
|       setState(() { | ||||
|         widget.position.value = Offset(position.dx, _saveHeight); | ||||
|         widget.position.update(Offset(position.dx, _saveHeight)); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
| @ -331,7 +422,7 @@ class _DraggableState extends State<Draggable> { | ||||
|       if (sumHeight + position.dy > contextHeight) { | ||||
|         final y = contextHeight - sumHeight; | ||||
|         setState(() { | ||||
|           widget.position.value = Offset(position.dx, y); | ||||
|           widget.position.update(Offset(position.dx, y)); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
| @ -362,14 +453,14 @@ class _DraggableState extends State<Draggable> { | ||||
| class IOSDraggable extends StatefulWidget { | ||||
|   const IOSDraggable( | ||||
|       {Key? key, | ||||
|       this.position = Offset.zero, | ||||
|       this.chatModel, | ||||
|       required this.position, | ||||
|       required this.width, | ||||
|       required this.height, | ||||
|       required this.builder}) | ||||
|       : super(key: key); | ||||
| 
 | ||||
|   final Offset position; | ||||
|   final DraggableKeyPosition position; | ||||
|   final ChatModel? chatModel; | ||||
|   final double width; | ||||
|   final double height; | ||||
| @ -380,7 +471,6 @@ class IOSDraggable extends StatefulWidget { | ||||
| } | ||||
| 
 | ||||
| class IOSDraggableState extends State<IOSDraggable> { | ||||
|   late Offset _position; | ||||
|   late ChatModel? _chatModel; | ||||
|   late double _width; | ||||
|   late double _height; | ||||
| @ -391,25 +481,26 @@ class IOSDraggableState extends State<IOSDraggable> { | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _position = widget.position; | ||||
|     _chatModel = widget.chatModel; | ||||
|     _width = widget.width; | ||||
|     _height = widget.height; | ||||
|   } | ||||
| 
 | ||||
|   get position => widget.position; | ||||
| 
 | ||||
|   checkKeyboard() { | ||||
|     final bottomHeight = MediaQuery.of(context).viewInsets.bottom; | ||||
|     final currentVisible = bottomHeight != 0; | ||||
| 
 | ||||
|     // save | ||||
|     if (!_keyboardVisible && currentVisible) { | ||||
|       _saveHeight = _position.dy; | ||||
|       _saveHeight = position.value.dy; | ||||
|     } | ||||
| 
 | ||||
|     // reset | ||||
|     if (_lastBottomHeight > 0 && bottomHeight == 0) { | ||||
|       setState(() { | ||||
|         _position = Offset(_position.dx, _saveHeight); | ||||
|         position.value = Offset(position.value.dx, _saveHeight); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
| @ -417,10 +508,10 @@ class IOSDraggableState extends State<IOSDraggable> { | ||||
|     if (_keyboardVisible && currentVisible) { | ||||
|       final sumHeight = bottomHeight + _height; | ||||
|       final contextHeight = MediaQuery.of(context).size.height; | ||||
|       if (sumHeight + _position.dy > contextHeight) { | ||||
|       if (sumHeight + position.value.dy > contextHeight) { | ||||
|         final y = contextHeight - sumHeight; | ||||
|         setState(() { | ||||
|           _position = Offset(_position.dx, y); | ||||
|           position.value = Offset(position.value.dx, y); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
| @ -435,14 +526,14 @@ class IOSDraggableState extends State<IOSDraggable> { | ||||
|     return Stack( | ||||
|       children: [ | ||||
|         Positioned( | ||||
|           left: _position.dx, | ||||
|           top: _position.dy, | ||||
|           left: position.value.dx, | ||||
|           top: position.value.dy, | ||||
|           child: GestureDetector( | ||||
|             onPanUpdate: (details) { | ||||
|               setState(() { | ||||
|                 _position += details.delta; | ||||
|                 position.value += details.delta; | ||||
|               }); | ||||
|               _chatModel?.setChatWindowPosition(_position); | ||||
|               _chatModel?.setChatWindowPosition(position.value); | ||||
|             }, | ||||
|             child: Material( | ||||
|               child: Container( | ||||
|  | ||||
| @ -142,6 +142,8 @@ const String kOptionDisableFloatingWindow = "disable-floating-window"; | ||||
| 
 | ||||
| const String kOptionKeepScreenOn = "keep-screen-on"; | ||||
| 
 | ||||
| const String kOptionShowMobileAction = "showMobileActions"; | ||||
| 
 | ||||
| const String kUrlActionClose = "close"; | ||||
| 
 | ||||
| const String kTabLabelHomePage = "Home"; | ||||
|  | ||||
| @ -134,6 +134,7 @@ class _RemotePageState extends State<RemotePage> | ||||
|     _ffi.ffiModel.updateEventListener(sessionId, widget.id); | ||||
|     if (!isWeb) bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote); | ||||
|     _ffi.qualityMonitorModel.checkShowQualityMonitor(sessionId); | ||||
|     _ffi.dialogManager.loadMobileActionsOverlayVisible(); | ||||
|     // Session option should be set after models.dart/FFI.start | ||||
|     _showRemoteCursor.value = bind.sessionGetToggleOptionSync( | ||||
|         sessionId: sessionId, arg: 'show-remote-cursor'); | ||||
| @ -322,13 +323,6 @@ class _RemotePageState extends State<RemotePage> | ||||
|                       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, | ||||
|  | ||||
| @ -579,8 +579,8 @@ class _MobileActionMenu extends StatelessWidget { | ||||
|     return Obx(() => _IconMenuButton( | ||||
|           assetName: 'assets/actions_mobile.svg', | ||||
|           tooltip: 'Mobile Actions', | ||||
|           onPressed: () => | ||||
|               ffi.dialogManager.mobileActionsOverlayVisible.toggle(), | ||||
|           onPressed: () => ffi.dialogManager.setMobileActionsOverlayVisible( | ||||
|               !ffi.dialogManager.mobileActionsOverlayVisible.value), | ||||
|           color: ffi.dialogManager.mobileActionsOverlayVisible.isTrue | ||||
|               ? _ToolbarTheme.blueColor | ||||
|               : _ToolbarTheme.inactiveColor, | ||||
|  | ||||
| @ -6,6 +6,7 @@ import 'package:bot_toast/bot_toast.dart'; | ||||
| import 'package:desktop_multi_window/desktop_multi_window.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter_hbb/common/widgets/overlay.dart'; | ||||
| import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; | ||||
| import 'package:flutter_hbb/desktop/pages/install_page.dart'; | ||||
| import 'package:flutter_hbb/desktop/pages/server_page.dart'; | ||||
| @ -156,6 +157,7 @@ void runMobileApp() async { | ||||
|   await initEnv(kAppTypeMain); | ||||
|   if (isAndroid) androidChannelInit(); | ||||
|   if (isAndroid) platformFFI.syncAndroidServiceAppDirConfigPath(); | ||||
|   draggablePositions.load(); | ||||
|   await Future.wait([gFFI.abModel.loadCache(), gFFI.groupModel.loadCache()]); | ||||
|   gFFI.userModel.refreshCurrentUser(); | ||||
|   runApp(App()); | ||||
| @ -176,6 +178,7 @@ void runMultiWindow( | ||||
|   late Widget widget; | ||||
|   switch (appType) { | ||||
|     case kAppTypeDesktopRemote: | ||||
|       draggablePositions.load(); | ||||
|       widget = DesktopRemoteScreen( | ||||
|         params: argument, | ||||
|       ); | ||||
|  | ||||
| @ -82,13 +82,14 @@ class _RemotePageState extends State<RemotePage> { | ||||
|         .changeCurrentKey(MessageKey(widget.id, ChatModel.clientModeID)); | ||||
|     gFFI.chatModel.voiceCallStatus.value = VoiceCallStatus.notStarted; | ||||
|     _blockableOverlayState.applyFfi(gFFI); | ||||
|     gFFI.dialogManager.loadMobileActionsOverlayVisible(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> dispose() async { | ||||
|     // https://github.com/flutter/flutter/issues/64935 | ||||
|     super.dispose(); | ||||
|     gFFI.dialogManager.hideMobileActionsOverlay(); | ||||
|     gFFI.dialogManager.hideMobileActionsOverlay(store: false); | ||||
|     gFFI.inputModel.listenToMouse(false); | ||||
|     gFFI.imageModel.disposeImage(); | ||||
|     gFFI.cursorModel.disposeImages(); | ||||
|  | ||||
| @ -917,10 +917,12 @@ class FfiModel with ChangeNotifier { | ||||
|       if (parent.target?.connType == ConnType.defaultConn && | ||||
|           parent.target != null && | ||||
|           parent.target!.ffiModel.permissions['keyboard'] != false) { | ||||
|         Timer( | ||||
|             Duration(milliseconds: delayMSecs), | ||||
|             () => parent.target!.dialogManager | ||||
|                 .showMobileActionsOverlay(ffi: parent.target!)); | ||||
|         Timer(Duration(milliseconds: delayMSecs), () { | ||||
|           if (parent.target!.dialogManager.mobileActionsOverlayVisible.isTrue) { | ||||
|             parent.target!.dialogManager | ||||
|                 .showMobileActionsOverlay(ffi: parent.target!); | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| @ -1587,10 +1589,13 @@ class CanvasModel with ChangeNotifier { | ||||
|     // (focalPoint.dx - _x_1) / s1 + displayOriginX = (focalPoint.dx - _x_2) / s2 + displayOriginX | ||||
|     // _x_2 = focalPoint.dx - (focalPoint.dx - _x_1) / s1 * s2 | ||||
|     _x = focalPoint.dx - (focalPoint.dx - _x) / s * _scale; | ||||
|     final adjustForKeyboard = parent.target?.cursorModel.adjustForKeyboard() ?? 0.0; | ||||
|     final adjustForKeyboard = | ||||
|         parent.target?.cursorModel.adjustForKeyboard() ?? 0.0; | ||||
|     // (focalPoint.dy - _y_1 + adjust) / s1 + displayOriginY = (focalPoint.dy - _y_2 + adjust) / s2 + displayOriginY | ||||
|     // _y_2 = focalPoint.dy + adjust - (focalPoint.dy - _y_1 + adjust) / s1 * s2 | ||||
|     _y = focalPoint.dy + adjustForKeyboard - (focalPoint.dy - _y + adjustForKeyboard) / s * _scale; | ||||
|     _y = focalPoint.dy + | ||||
|         adjustForKeyboard - | ||||
|         (focalPoint.dy - _y + adjustForKeyboard) / s * _scale; | ||||
|     notifyListeners(); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user