From 8deea8ed042ceb39f4e0bca09515edec3643f353 Mon Sep 17 00:00:00 2001 From: nturgut Date: Thu, 14 May 2020 16:59:31 -0700 Subject: [PATCH] [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. --- .../lib/src/engine/browser_detection.dart | 6 ++ lib/web_ui/lib/src/engine/dom_renderer.dart | 17 +++- lib/web_ui/lib/src/engine/window.dart | 81 +++++++++++++++++-- lib/web_ui/lib/src/ui/window.dart | 10 ++- 4 files changed, 105 insertions(+), 9 deletions(-) diff --git a/lib/web_ui/lib/src/engine/browser_detection.dart b/lib/web_ui/lib/src/engine/browser_detection.dart index 1c7e805a3a7..38670b5ed6d 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 @@ const Set _desktopOperatingSystems = { /// /// 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 b6f1cd06c43..7884a543cde 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 @@ flt-glass-pane * { } /// 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 298f79ec0c8..7c94fff833c 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -62,10 +62,10 @@ class EngineWindow extends ui.Window { 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 @@ class EngineWindow extends ui.Window { } } + 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 @@ class EngineWindow extends ui.Window { /// 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 @@ void _invoke3(void callback(A1 a1, A2 a2, A3 a3), Zone zone, A1 arg1 /// 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 c46efc466fe..07fcae08955 100644 --- a/lib/web_ui/lib/src/ui/window.dart +++ b/lib/web_ui/lib/src/ui/window.dart @@ -89,6 +89,8 @@ enum AppLifecycleState { /// [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 @@ enum AppLifecycleState { /// * [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.