[webview_flutter] Implementation of the app facing WebViewWidget for v4 (#6367)

diff --git a/packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_widget.dart b/packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_widget.dart
new file mode 100644
index 0000000..06e4f78
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter/lib/src/v4/src/webview_widget.dart
@@ -0,0 +1,64 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart';
+
+import 'webview_controller.dart';
+
+/// Displays a native WebView as a Widget.
+class WebViewWidget extends StatelessWidget {
+  /// Constructs a [WebViewWidget].
+  WebViewWidget({
+    Key? key,
+    required WebViewController controller,
+    TextDirection layoutDirection = TextDirection.ltr,
+    Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers =
+        const <Factory<OneSequenceGestureRecognizer>>{},
+  }) : this.fromPlatformCreationParams(
+          key: key,
+          params: PlatformWebViewWidgetCreationParams(
+            controller: controller.platform,
+            layoutDirection: layoutDirection,
+            gestureRecognizers: gestureRecognizers,
+          ),
+        );
+
+  /// Constructs a [WebViewWidget] from creation params for a specific
+  /// platform.
+  WebViewWidget.fromPlatformCreationParams({
+    Key? key,
+    required PlatformWebViewWidgetCreationParams params,
+  }) : this.fromPlatform(key: key, platform: PlatformWebViewWidget(params));
+
+  /// Constructs a [WebViewWidget] from a specific platform implementation.
+  WebViewWidget.fromPlatform({Key? key, required this.platform})
+      : super(key: key);
+
+  /// Implementation of [PlatformWebViewWidget] for the current platform.
+  final PlatformWebViewWidget platform;
+
+  /// The layout direction to use for the embedded WebView.
+  late final TextDirection layoutDirection = platform.params.layoutDirection;
+
+  /// Specifies which gestures should be consumed by the web view.
+  ///
+  /// It is possible for other gesture recognizers to be competing with the web
+  /// view on pointer events, e.g if the web view is inside a [ListView] the
+  /// [ListView] will want to handle vertical drags. The web view will claim
+  /// gestures that are recognized by any of the recognizers on this list.
+  ///
+  /// When `gestureRecognizers` is empty (default), the web view will only
+  /// handle pointer events for gestures that were not claimed by any other
+  /// gesture recognizer.
+  late final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers =
+      platform.params.gestureRecognizers;
+
+  @override
+  Widget build(BuildContext context) {
+    return platform.build(context);
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter/lib/src/v4/webview_flutter.dart b/packages/webview_flutter/webview_flutter/lib/src/v4/webview_flutter.dart
index f3caf84..f4a0b20 100644
--- a/packages/webview_flutter/webview_flutter/lib/src/v4/webview_flutter.dart
+++ b/packages/webview_flutter/webview_flutter/lib/src/v4/webview_flutter.dart
@@ -9,3 +9,4 @@
 
 export 'src/webview_controller.dart';
 export 'src/webview_cookie_manager.dart';
+export 'src/webview_widget.dart';
diff --git a/packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.dart b/packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.dart
new file mode 100644
index 0000000..455d8b3
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.dart
@@ -0,0 +1,87 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+import 'package:webview_flutter/src/v4/webview_flutter.dart';
+import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart';
+
+import 'webview_widget_test.mocks.dart';
+
+@GenerateMocks(<Type>[PlatformWebViewController, PlatformWebViewWidget])
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  group('WebViewWidget', () {
+    testWidgets('build', (WidgetTester tester) async {
+      final MockPlatformWebViewWidget mockPlatformWebViewWidget =
+          MockPlatformWebViewWidget();
+      when(mockPlatformWebViewWidget.build(any)).thenReturn(Container());
+
+      await tester.pumpWidget(WebViewWidget.fromPlatform(
+        platform: mockPlatformWebViewWidget,
+      ));
+
+      expect(find.byType(Container), findsOneWidget);
+    });
+
+    testWidgets(
+        'constructor parameters are correctly passed to creation params',
+        (WidgetTester tester) async {
+      WebViewPlatform.instance = TestWebViewPlatform();
+
+      final MockPlatformWebViewController mockPlatformWebViewController =
+          MockPlatformWebViewController();
+      final WebViewController webViewController =
+          WebViewController.fromPlatform(
+        mockPlatformWebViewController,
+      );
+
+      final WebViewWidget webViewWidget = WebViewWidget(
+        key: GlobalKey(),
+        controller: webViewController,
+        layoutDirection: TextDirection.rtl,
+        gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
+          Factory<OneSequenceGestureRecognizer>(() => EagerGestureRecognizer()),
+        },
+      );
+
+      // The key passed to the default constructor is used by the super class
+      // and not passed to the platform implementation.
+      expect(webViewWidget.platform.params.key, isNull);
+      expect(
+        webViewWidget.platform.params.controller,
+        webViewController.platform,
+      );
+      expect(webViewWidget.platform.params.layoutDirection, TextDirection.rtl);
+      expect(
+        webViewWidget.platform.params.gestureRecognizers.isNotEmpty,
+        isTrue,
+      );
+    });
+  });
+}
+
+class TestWebViewPlatform extends WebViewPlatform {
+  @override
+  PlatformWebViewWidget createPlatformWebViewWidget(
+    PlatformWebViewWidgetCreationParams params,
+  ) {
+    return TestPlatformWebViewWidget(params);
+  }
+}
+
+class TestPlatformWebViewWidget extends PlatformWebViewWidget {
+  TestPlatformWebViewWidget(PlatformWebViewWidgetCreationParams params)
+      : super.implementation(params);
+
+  @override
+  Widget build(BuildContext context) {
+    throw UnimplementedError();
+  }
+}
diff --git a/packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.mocks.dart
new file mode 100644
index 0000000..e481d75
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter/test/v4/webview_widget_test.mocks.dart
@@ -0,0 +1,246 @@
+// Mocks generated by Mockito 5.3.0 from annotations
+// in webview_flutter/test/v4/webview_widget_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i7;
+import 'dart:math' as _i3;
+import 'dart:ui' as _i9;
+
+import 'package:flutter/foundation.dart' as _i5;
+import 'package:flutter/widgets.dart' as _i4;
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:webview_flutter_platform_interface/v4/src/platform_navigation_delegate.dart'
+    as _i8;
+import 'package:webview_flutter_platform_interface/v4/src/platform_webview_controller.dart'
+    as _i6;
+import 'package:webview_flutter_platform_interface/v4/src/platform_webview_widget.dart'
+    as _i10;
+import 'package:webview_flutter_platform_interface/v4/src/webview_platform.dart'
+    as _i2;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakePlatformWebViewControllerCreationParams_0 extends _i1.SmartFake
+    implements _i2.PlatformWebViewControllerCreationParams {
+  _FakePlatformWebViewControllerCreationParams_0(
+      Object parent, Invocation parentInvocation)
+      : super(parent, parentInvocation);
+}
+
+class _FakePoint_1<T extends num> extends _i1.SmartFake
+    implements _i3.Point<T> {
+  _FakePoint_1(Object parent, Invocation parentInvocation)
+      : super(parent, parentInvocation);
+}
+
+class _FakePlatformWebViewWidgetCreationParams_2 extends _i1.SmartFake
+    implements _i2.PlatformWebViewWidgetCreationParams {
+  _FakePlatformWebViewWidgetCreationParams_2(
+      Object parent, Invocation parentInvocation)
+      : super(parent, parentInvocation);
+}
+
+class _FakeWidget_3 extends _i1.SmartFake implements _i4.Widget {
+  _FakeWidget_3(Object parent, Invocation parentInvocation)
+      : super(parent, parentInvocation);
+
+  @override
+  String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) =>
+      super.toString();
+}
+
+/// A class which mocks [PlatformWebViewController].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockPlatformWebViewController extends _i1.Mock
+    implements _i6.PlatformWebViewController {
+  MockPlatformWebViewController() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  _i2.PlatformWebViewControllerCreationParams get params =>
+      (super.noSuchMethod(Invocation.getter(#params),
+              returnValue: _FakePlatformWebViewControllerCreationParams_0(
+                  this, Invocation.getter(#params)))
+          as _i2.PlatformWebViewControllerCreationParams);
+  @override
+  _i7.Future<void> loadFile(String? absoluteFilePath) => (super.noSuchMethod(
+      Invocation.method(#loadFile, [absoluteFilePath]),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> loadFlutterAsset(String? key) => (super.noSuchMethod(
+      Invocation.method(#loadFlutterAsset, [key]),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> loadHtmlString(String? html, {String? baseUrl}) =>
+      (super.noSuchMethod(
+              Invocation.method(#loadHtmlString, [html], {#baseUrl: baseUrl}),
+              returnValue: _i7.Future<void>.value(),
+              returnValueForMissingStub: _i7.Future<void>.value())
+          as _i7.Future<void>);
+  @override
+  _i7.Future<void> loadRequest(_i2.LoadRequestParams? params) =>
+      (super.noSuchMethod(Invocation.method(#loadRequest, [params]),
+              returnValue: _i7.Future<void>.value(),
+              returnValueForMissingStub: _i7.Future<void>.value())
+          as _i7.Future<void>);
+  @override
+  _i7.Future<String?> currentUrl() =>
+      (super.noSuchMethod(Invocation.method(#currentUrl, []),
+          returnValue: _i7.Future<String?>.value()) as _i7.Future<String?>);
+  @override
+  _i7.Future<bool> canGoBack() =>
+      (super.noSuchMethod(Invocation.method(#canGoBack, []),
+          returnValue: _i7.Future<bool>.value(false)) as _i7.Future<bool>);
+  @override
+  _i7.Future<bool> canGoForward() =>
+      (super.noSuchMethod(Invocation.method(#canGoForward, []),
+          returnValue: _i7.Future<bool>.value(false)) as _i7.Future<bool>);
+  @override
+  _i7.Future<void> goBack() => (super.noSuchMethod(
+      Invocation.method(#goBack, []),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> goForward() => (super.noSuchMethod(
+      Invocation.method(#goForward, []),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> reload() => (super.noSuchMethod(
+      Invocation.method(#reload, []),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> clearCache() => (super.noSuchMethod(
+      Invocation.method(#clearCache, []),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> clearLocalStorage() => (super.noSuchMethod(
+      Invocation.method(#clearLocalStorage, []),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> setPlatformNavigationDelegate(
+          _i8.PlatformNavigationDelegate? handler) =>
+      (super.noSuchMethod(
+              Invocation.method(#setPlatformNavigationDelegate, [handler]),
+              returnValue: _i7.Future<void>.value(),
+              returnValueForMissingStub: _i7.Future<void>.value())
+          as _i7.Future<void>);
+  @override
+  _i7.Future<void> runJavaScript(String? javaScript) => (super.noSuchMethod(
+      Invocation.method(#runJavaScript, [javaScript]),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<String> runJavaScriptReturningResult(String? javaScript) =>
+      (super.noSuchMethod(
+          Invocation.method(#runJavaScriptReturningResult, [javaScript]),
+          returnValue: _i7.Future<String>.value('')) as _i7.Future<String>);
+  @override
+  _i7.Future<void> addJavaScriptChannel(
+          _i6.JavaScriptChannelParams? javaScriptChannelParams) =>
+      (super.noSuchMethod(
+          Invocation.method(#addJavaScriptChannel, [javaScriptChannelParams]),
+          returnValue: _i7.Future<void>.value(),
+          returnValueForMissingStub:
+              _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> removeJavaScriptChannel(String? javaScriptChannelName) =>
+      (super.noSuchMethod(
+          Invocation.method(#removeJavaScriptChannel, [javaScriptChannelName]),
+          returnValue: _i7.Future<void>.value(),
+          returnValueForMissingStub:
+              _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<String?> getTitle() =>
+      (super.noSuchMethod(Invocation.method(#getTitle, []),
+          returnValue: _i7.Future<String?>.value()) as _i7.Future<String?>);
+  @override
+  _i7.Future<void> scrollTo(int? x, int? y) => (super.noSuchMethod(
+      Invocation.method(#scrollTo, [x, y]),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> scrollBy(int? x, int? y) => (super.noSuchMethod(
+      Invocation.method(#scrollBy, [x, y]),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<_i3.Point<int>> getScrollPosition() =>
+      (super.noSuchMethod(Invocation.method(#getScrollPosition, []),
+              returnValue: _i7.Future<_i3.Point<int>>.value(_FakePoint_1<int>(
+                  this, Invocation.method(#getScrollPosition, []))))
+          as _i7.Future<_i3.Point<int>>);
+  @override
+  _i7.Future<void> enableDebugging(bool? enabled) => (super.noSuchMethod(
+      Invocation.method(#enableDebugging, [enabled]),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> enableGestureNavigation(bool? enabled) => (super
+          .noSuchMethod(Invocation.method(#enableGestureNavigation, [enabled]),
+              returnValue: _i7.Future<void>.value(),
+              returnValueForMissingStub: _i7.Future<void>.value())
+      as _i7.Future<void>);
+  @override
+  _i7.Future<void> enableZoom(bool? enabled) => (super.noSuchMethod(
+      Invocation.method(#enableZoom, [enabled]),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> setBackgroundColor(_i9.Color? color) => (super.noSuchMethod(
+      Invocation.method(#setBackgroundColor, [color]),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+  @override
+  _i7.Future<void> setJavaScriptMode(_i2.JavaScriptMode? javaScriptMode) =>
+      (super.noSuchMethod(
+              Invocation.method(#setJavaScriptMode, [javaScriptMode]),
+              returnValue: _i7.Future<void>.value(),
+              returnValueForMissingStub: _i7.Future<void>.value())
+          as _i7.Future<void>);
+  @override
+  _i7.Future<void> setUserAgent(String? userAgent) => (super.noSuchMethod(
+      Invocation.method(#setUserAgent, [userAgent]),
+      returnValue: _i7.Future<void>.value(),
+      returnValueForMissingStub: _i7.Future<void>.value()) as _i7.Future<void>);
+}
+
+/// A class which mocks [PlatformWebViewWidget].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockPlatformWebViewWidget extends _i1.Mock
+    implements _i10.PlatformWebViewWidget {
+  MockPlatformWebViewWidget() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  _i2.PlatformWebViewWidgetCreationParams get params =>
+      (super.noSuchMethod(Invocation.getter(#params),
+              returnValue: _FakePlatformWebViewWidgetCreationParams_2(
+                  this, Invocation.getter(#params)))
+          as _i2.PlatformWebViewWidgetCreationParams);
+  @override
+  _i4.Widget build(_i4.BuildContext? context) =>
+      (super.noSuchMethod(Invocation.method(#build, [context]),
+              returnValue:
+                  _FakeWidget_3(this, Invocation.method(#build, [context])))
+          as _i4.Widget);
+}