From b3f43f55c1c00f287b8baf22152f3d1b88b51fb6 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Wed, 11 Mar 2026 18:28:37 +0800 Subject: [PATCH] fix(mobile): restore canvas offset after hidding the soft keyboard (#14506) * fix(mobile): restore canvas offset after hidding the soft keyboard Signed-off-by: fufesou * fix(mobile): ingore mobileFocusCanvasCursor in didChangeMetrics Signed-off-by: fufesou * fix(mobile): remove unused code Signed-off-by: fufesou * refact(mobile): simple refactor Signed-off-by: fufesou * fix(mobile): restore canvas, cancel focus timer Signed-off-by: fufesou --------- Signed-off-by: fufesou --- flutter/lib/mobile/pages/remote_page.dart | 24 -------------- flutter/lib/models/model.dart | 40 +++++++++++++++++++++-- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index b379a5591..9102d163c 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -65,9 +64,7 @@ class _RemotePageState extends State with WidgetsBindingObserver { bool _showGestureHelp = false; String _value = ''; Orientation? _currentOrientation; - double _viewInsetsBottom = 0; final _uniqueKey = UniqueKey(); - Timer? _timerDidChangeMetrics; Timer? _iosKeyboardWorkaroundTimer; final _blockableOverlayState = BlockableOverlayState(); @@ -140,7 +137,6 @@ class _RemotePageState extends State with WidgetsBindingObserver { _physicalFocusNode.dispose(); await gFFI.close(); _timer?.cancel(); - _timerDidChangeMetrics?.cancel(); _iosKeyboardWorkaroundTimer?.cancel(); gFFI.dialogManager.dismissAll(); await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, @@ -167,26 +163,6 @@ class _RemotePageState extends State with WidgetsBindingObserver { gFFI.invokeMethod("try_sync_clipboard"); } - @override - void didChangeMetrics() { - // If the soft keyboard is visible and the canvas has been changed(panned or scaled) - // Don't try reset the view style and focus the cursor. - if (gFFI.cursorModel.lastKeyboardIsVisible && - gFFI.canvasModel.isMobileCanvasChanged) { - return; - } - - final newBottom = MediaQueryData.fromView(ui.window).viewInsets.bottom; - _timerDidChangeMetrics?.cancel(); - _timerDidChangeMetrics = Timer(Duration(milliseconds: 100), () async { - // We need this comparation because poping up the floating action will also trigger `didChangeMetrics()`. - if (newBottom != _viewInsetsBottom) { - gFFI.canvasModel.mobileFocusCanvasCursor(); - _viewInsetsBottom = newBottom; - } - }); - } - // to-do: It should be better to use transparent color instead of the bgColor. // But for now, the transparent color will cause the canvas to be white. // I'm sure that the white color is caused by the Overlay widget in BlockableOverlay. diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index ff298c380..de41a2a78 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -2152,6 +2152,9 @@ class CanvasModel with ChangeNotifier { ViewStyle _lastViewStyle = ViewStyle.defaultViewStyle(); Timer? _timerMobileFocusCanvasCursor; + Timer? _timerMobileRestoreCanvasOffset; + Offset? _offsetBeforeMobileSoftKeyboard; + double? _scaleBeforeMobileSoftKeyboard; // `isMobileCanvasChanged` is used to avoid canvas reset when changing the input method // after showing the soft keyboard. @@ -2639,6 +2642,9 @@ class CanvasModel with ChangeNotifier { _scale = 1.0; _lastViewStyle = ViewStyle.defaultViewStyle(); _timerMobileFocusCanvasCursor?.cancel(); + _timerMobileRestoreCanvasOffset?.cancel(); + _offsetBeforeMobileSoftKeyboard = null; + _scaleBeforeMobileSoftKeyboard = null; } updateScrollPercent() { @@ -2667,6 +2673,31 @@ class CanvasModel with ChangeNotifier { }); } + void saveMobileOffsetBeforeSoftKeyboard() { + _timerMobileRestoreCanvasOffset?.cancel(); + _offsetBeforeMobileSoftKeyboard = Offset(_x, _y); + _scaleBeforeMobileSoftKeyboard = _scale; + } + + void restoreMobileOffsetAfterSoftKeyboard() { + _timerMobileRestoreCanvasOffset?.cancel(); + _timerMobileFocusCanvasCursor?.cancel(); + final targetOffset = _offsetBeforeMobileSoftKeyboard; + final targetScale = _scaleBeforeMobileSoftKeyboard; + if (targetOffset == null || targetScale == null) { + return; + } + _timerMobileRestoreCanvasOffset = Timer(Duration(milliseconds: 100), () { + updateSize(); + _x = targetOffset.dx; + _y = targetOffset.dy; + _scale = targetScale; + _offsetBeforeMobileSoftKeyboard = null; + _scaleBeforeMobileSoftKeyboard = null; + notifyListeners(); + }); + } + // mobile only // Move the canvas to make the cursor visible(center) on the screen. void _moveToCenterCursor() { @@ -2919,8 +2950,13 @@ class CursorModel with ChangeNotifier { _lastIsBlocked = true; } if (isMobile && _lastKeyboardIsVisible != keyboardIsVisible) { - parent.target?.canvasModel.mobileFocusCanvasCursor(); - parent.target?.canvasModel.isMobileCanvasChanged = false; + if (keyboardIsVisible) { + parent.target?.canvasModel.saveMobileOffsetBeforeSoftKeyboard(); + parent.target?.canvasModel.mobileFocusCanvasCursor(); + parent.target?.canvasModel.isMobileCanvasChanged = false; + } else { + parent.target?.canvasModel.restoreMobileOffsetAfterSoftKeyboard(); + } } _lastKeyboardIsVisible = keyboardIsVisible; }