[web] change viewinsets instead of physical size for keyboard (#18328)
* changing viewInsets instead of physicalSize during text editing on mobile devices
* fix indentation
* fixing rotation issues. recalculating insets after physical size change. addressing reviewer comments
* make ui.windowpadding abstract. adding windowpadding implementation to engine.
diff --git a/lib/web_ui/lib/src/engine/browser_detection.dart b/lib/web_ui/lib/src/engine/browser_detection.dart
index 1c7e805..38670b5 100644
--- a/lib/web_ui/lib/src/engine/browser_detection.dart
+++ b/lib/web_ui/lib/src/engine/browser_detection.dart
@@ -155,3 +155,9 @@
///
/// See [_desktopOperatingSystems].
bool get isDesktop => _desktopOperatingSystems.contains(operatingSystem);
+
+/// A flag to check if the current browser is running on a mobile device.
+///
+/// See [_desktopOperatingSystems].
+/// See [isDesktop].
+bool get isMobile => !isDesktop;
diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart
index b6f1cd0..7884a54 100644
--- a/lib/web_ui/lib/src/engine/dom_renderer.dart
+++ b/lib/web_ui/lib/src/engine/dom_renderer.dart
@@ -465,9 +465,22 @@
}
/// Called immediately after browser window metrics change.
+ ///
+ /// When there is a text editing going on in mobile devices, do not change
+ /// the physicalSize, change the [window.viewInsets]. See:
+ /// https://api.flutter.dev/flutter/dart-ui/Window/viewInsets.html
+ /// https://api.flutter.dev/flutter/dart-ui/Window/physicalSize.html
+ ///
+ /// Note: always check for rotations for a mobile device. Update the physical
+ /// size if the change is caused by a rotation.
void _metricsDidChange(html.Event event) {
- window._computePhysicalSize();
- if (window._onMetricsChanged != null) {
+ if(isMobile && !window.isRotation() && textEditing.isEditing) {
+ window.computeOnScreenKeyboardInsets();
+ window.invokeOnMetricsChanged();
+ } else {
+ window._computePhysicalSize();
+ // When physical size changes this value has to be recalculated.
+ window.computeOnScreenKeyboardInsets();
window.invokeOnMetricsChanged();
}
}
diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart
index 298f79e..7c94fff 100644
--- a/lib/web_ui/lib/src/engine/window.dart
+++ b/lib/web_ui/lib/src/engine/window.dart
@@ -62,10 +62,10 @@
if (!override) {
double windowInnerWidth;
double windowInnerHeight;
- if (html.window.visualViewport != null) {
- windowInnerWidth = html.window.visualViewport.width * devicePixelRatio;
- windowInnerHeight =
- html.window.visualViewport.height * devicePixelRatio;
+ final html.VisualViewport viewport = html.window.visualViewport;
+ if (viewport != null) {
+ windowInnerWidth = viewport.width * devicePixelRatio;
+ windowInnerHeight = viewport.height * devicePixelRatio;
} else {
windowInnerWidth = html.window.innerWidth * devicePixelRatio;
windowInnerHeight = html.window.innerHeight * devicePixelRatio;
@@ -77,6 +77,60 @@
}
}
+ void computeOnScreenKeyboardInsets() {
+ double windowInnerHeight;
+ final html.VisualViewport viewport = html.window.visualViewport;
+ if (viewport != null) {
+ windowInnerHeight = viewport.height * devicePixelRatio;
+ } else {
+ windowInnerHeight = html.window.innerHeight * devicePixelRatio;
+ }
+ final double bottomPadding = _physicalSize.height - windowInnerHeight;
+ _viewInsets =
+ WindowPadding(bottom: bottomPadding, left: 0, right: 0, top: 0);
+ }
+
+ /// Uses the previous physical size and current innerHeight/innerWidth
+ /// values to decide if a device is rotating.
+ ///
+ /// During a rotation the height and width values will (almost) swap place.
+ /// Values can slightly differ due to space occupied by the browser header.
+ /// For example the following values are collected for Pixel 3 rotation:
+ ///
+ /// height: 658 width: 393
+ /// new height: 313 new width: 738
+ ///
+ /// The following values are from a changed caused by virtual keyboard.
+ ///
+ /// height: 658 width: 393
+ /// height: 368 width: 393
+ bool isRotation() {
+ double height = 0;
+ double width = 0;
+ if (html.window.visualViewport != null) {
+ height = html.window.visualViewport.height * devicePixelRatio;
+ width = html.window.visualViewport.width * devicePixelRatio;
+ } else {
+ height = html.window.innerHeight * devicePixelRatio;
+ width = html.window.innerWidth * devicePixelRatio;
+ }
+ // First confirm both heught and width is effected.
+ if (_physicalSize.height != height && _physicalSize.width != width) {
+ // If prior to rotation height is bigger than width it should be the
+ // opposite after the rotation and vice versa.
+ if ((_physicalSize.height > _physicalSize.width && height < width) ||
+ (_physicalSize.width > _physicalSize.height && width < height)) {
+ // Rotation detected
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @override
+ WindowPadding get viewInsets => _viewInsets;
+ WindowPadding _viewInsets = ui.WindowPadding.zero;
+
/// Lazily populated and cleared at the end of the frame.
ui.Size _physicalSize;
@@ -155,7 +209,9 @@
/// Engine code should use this method instead of the callback directly.
/// Otherwise zones won't work properly.
void invokeOnMetricsChanged() {
- _invoke(_onMetricsChanged, _onMetricsChangedZone);
+ if (window._onMetricsChanged != null) {
+ _invoke(_onMetricsChanged, _onMetricsChangedZone);
+ }
}
@override
@@ -617,3 +673,18 @@
/// API surface, providing Web-specific functionality that the standard
/// `dart:ui` version does not.
final EngineWindow window = EngineWindow();
+
+/// The Web implementation of [ui.WindowPadding].
+class WindowPadding implements ui.WindowPadding {
+ const WindowPadding({
+ this.left,
+ this.top,
+ this.right,
+ this.bottom,
+ });
+
+ final double left;
+ final double top;
+ final double right;
+ final double bottom;
+}
diff --git a/lib/web_ui/lib/src/ui/window.dart b/lib/web_ui/lib/src/ui/window.dart
index c46efc4..07fcae0 100644
--- a/lib/web_ui/lib/src/ui/window.dart
+++ b/lib/web_ui/lib/src/ui/window.dart
@@ -89,6 +89,8 @@
/// [Window.padding]. View insets and padding are preferably read via
/// [MediaQuery.of].
///
+/// For the engine implementation of this class see the [engine.WindowPadding].
+///
/// For a generic class that represents distances around a rectangle, see the
/// [EdgeInsets] class.
///
@@ -99,8 +101,12 @@
/// * [MediaQuery.of], for the preferred mechanism for accessing these values.
/// * [Scaffold], which automatically applies the padding in material design
/// applications.
-class WindowPadding {
- const WindowPadding._({this.left, this.top, this.right, this.bottom});
+abstract class WindowPadding {
+ const factory WindowPadding._(
+ {double left,
+ double top,
+ double right,
+ double bottom}) = engine.WindowPadding;
/// The distance from the left edge to the first unpadded pixel, in physical
/// pixels.