[webview_flutter] Add android `webSettings.setTextZoom` api (#3298)

[webview_flutter] Add android `webSettings.setTextZoom` api
diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index c5564af..6bd5861 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 3.4.0
 
+* Adds support to set text zoom of a page. See `AndroidWebViewController.setTextZoom`.
 * Aligns Dart and Flutter SDK constraints.
 
 ## 3.3.2
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
index 9897472..6fa20fe 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
@@ -1603,6 +1603,8 @@
 
     void setAllowFileAccess(@NonNull Long instanceId, @NonNull Boolean enabled);
 
+    void setTextZoom(@NonNull Long instanceId, @NonNull Long textZoom);
+
     /** The codec used by WebSettingsHostApi. */
     static MessageCodec<Object> getCodec() {
       return new StandardMessageCodec();
@@ -2050,6 +2052,39 @@
           channel.setMessageHandler(null);
         }
       }
+      {
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger, "dev.flutter.pigeon.WebSettingsHostApi.setTextZoom", getCodec());
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                ArrayList<Object> wrapped = new ArrayList<Object>();
+                try {
+                  ArrayList<Object> args = (ArrayList<Object>) message;
+                  assert args != null;
+                  Number instanceIdArg = (Number) args.get(0);
+                  if (instanceIdArg == null) {
+                    throw new NullPointerException("instanceIdArg unexpectedly null.");
+                  }
+                  Number textZoomArg = (Number) args.get(1);
+                  if (textZoomArg == null) {
+                    throw new NullPointerException("textZoomArg unexpectedly null.");
+                  }
+                  api.setTextZoom(
+                      (instanceIdArg == null) ? null : instanceIdArg.longValue(),
+                      (textZoomArg == null) ? null : textZoomArg.longValue());
+                  wrapped.add(0, null);
+                } catch (Error | RuntimeException exception) {
+                  ArrayList<Object> wrappedError = wrapError(exception);
+                  wrapped = wrappedError;
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
     }
   }
   /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java
index 98fd4fc..2215320 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java
@@ -120,4 +120,10 @@
     final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId);
     webSettings.setAllowFileAccess(enabled);
   }
+
+  @Override
+  public void setTextZoom(Long instanceId, Long textZoom) {
+    final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId);
+    webSettings.setTextZoom(textZoom.intValue());
+  }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java
index 3217316..3abb424 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java
@@ -107,4 +107,10 @@
     testHostApiImpl.setBuiltInZoomControls(0L, true);
     verify(mockWebSettings).setBuiltInZoomControls(true);
   }
+
+  @Test
+  public void setTextZoom() {
+    testHostApiImpl.setTextZoom(0L, 100L);
+    verify(mockWebSettings).setTextZoom(100);
+  }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
index 1ab30a9..1f80e51 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
@@ -591,6 +591,13 @@
     return api.setAllowFileAccessFromInstance(this, enabled);
   }
 
+  /// Sets the text zoom of the page in percent.
+  ///
+  /// The default is 100. See https://developer.android.com/reference/android/webkit/WebSettings#setTextZoom(int)
+  Future<void> setTextZoom(int textZoom) {
+    return api.setSetTextZoomFromInstance(this, textZoom);
+  }
+
   @override
   WebSettings copy() {
     return WebSettings.detached();
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart
index 5210879..6c9ba8e 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart
@@ -1250,6 +1250,28 @@
       return;
     }
   }
+
+  Future<void> setTextZoom(int arg_instanceId, int arg_textZoom) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.WebSettingsHostApi.setTextZoom', codec,
+        binaryMessenger: _binaryMessenger);
+    final List<Object?>? replyList = await channel
+        .send(<Object?>[arg_instanceId, arg_textZoom]) as List<Object?>?;
+    if (replyList == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyList.length > 1) {
+      throw PlatformException(
+        code: replyList[0]! as String,
+        message: replyList[1] as String?,
+        details: replyList[2],
+      );
+    } else {
+      return;
+    }
+  }
 }
 
 class JavaScriptChannelHostApi {
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
index 127a2fa..0db0f04 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
@@ -435,6 +435,14 @@
   }
 
   /// Helper method to convert instances ids to objects.
+  Future<void> setSetTextZoomFromInstance(
+    WebSettings instance,
+    int textZoom,
+  ) {
+    return setTextZoom(instanceManager.getIdentifier(instance)!, textZoom);
+  }
+
+  /// Helper method to convert instances ids to objects.
   Future<void> setLoadWithOverviewModeFromInstance(
     WebSettings instance,
     bool overview,
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
index 6bd3dc0..3a7d077 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart
@@ -355,6 +355,12 @@
     return _webView.settings.setMediaPlaybackRequiresUserGesture(require);
   }
 
+  /// Sets the text zoom of the page in percent.
+  ///
+  /// The default is 100.
+  Future<void> setTextZoom(int textZoom) =>
+      _webView.settings.setTextZoom(textZoom);
+
   /// Sets the callback that is invoked when the client should show a file
   /// selector.
   Future<void> setOnShowFileSelector(
diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
index 7f4d362..b8c22e5 100644
--- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
@@ -218,6 +218,8 @@
   void setBuiltInZoomControls(int instanceId, bool enabled);
 
   void setAllowFileAccess(int instanceId, bool enabled);
+
+  void setTextZoom(int instanceId, int textZoom);
 }
 
 @HostApi(dartHostTestHandler: 'TestJavaScriptChannelHostApi')
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 16d9eda..3217263 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget on Android.
 repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 3.3.2
+version: 3.4.0
 
 environment:
   sdk: ">=2.17.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart
index 43bab38..a9a4b0e 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart
@@ -885,6 +885,22 @@
     verify(mockSettings.setMediaPlaybackRequiresUserGesture(true)).called(1);
   });
 
+  test('setTextZoom', () async {
+    final MockWebView mockWebView = MockWebView();
+    final MockWebSettings mockSettings = MockWebSettings();
+    final AndroidWebViewController controller = createControllerWithMocks(
+      mockWebView: mockWebView,
+      mockSettings: mockSettings,
+    );
+
+    clearInteractions(mockWebView);
+
+    await controller.setTextZoom(100);
+
+    verify(mockWebView.settings).called(1);
+    verify(mockSettings.setTextZoom(100)).called(1);
+  });
+
   test('webViewIdentifier', () {
     final MockWebView mockWebView = MockWebView();
     final InstanceManager instanceManager = InstanceManager(
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart
index 4bb2afb..2aaab4f 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart
@@ -654,6 +654,15 @@
         returnValueForMissingStub: _i9.Future<void>.value(),
       ) as _i9.Future<void>);
   @override
+  _i9.Future<void> setTextZoom(int? textZoom) => (super.noSuchMethod(
+        Invocation.method(
+          #setTextZoom,
+          [textZoom],
+        ),
+        returnValue: _i9.Future<void>.value(),
+        returnValueForMissingStub: _i9.Future<void>.value(),
+      ) as _i9.Future<void>);
+  @override
   _i9.Future<void> setOnShowFileSelector(
           _i9.Future<List<String>> Function(_i8.FileSelectorParams)?
               onShowFileSelector) =>
@@ -1737,6 +1746,15 @@
         returnValueForMissingStub: _i9.Future<void>.value(),
       ) as _i9.Future<void>);
   @override
+  _i9.Future<void> setTextZoom(int? textZoom) => (super.noSuchMethod(
+        Invocation.method(
+          #setTextZoom,
+          [textZoom],
+        ),
+        returnValue: _i9.Future<void>.value(),
+        returnValueForMissingStub: _i9.Future<void>.value(),
+      ) as _i9.Future<void>);
+  @override
   _i2.WebSettings copy() => (super.noSuchMethod(
         Invocation.method(
           #copy,
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
index 236d87d..79b5b3f 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
@@ -497,6 +497,14 @@
       test('copy', () {
         expect(webSettings.copy(), isA<WebSettings>());
       });
+
+      test('setTextZoom', () {
+        webSettings.setTextZoom(100);
+        verify(mockPlatformHostApi.setTextZoom(
+          webSettingsInstanceId,
+          100,
+        ));
+      });
     });
 
     group('JavaScriptChannel', () {
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
index 0b5afba..0de7194 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
@@ -525,6 +525,21 @@
         ),
         returnValueForMissingStub: null,
       );
+  @override
+  void setTextZoom(
+    int? instanceId,
+    int? textZoom,
+  ) =>
+      super.noSuchMethod(
+        Invocation.method(
+          #setTextZoom,
+          [
+            instanceId,
+            textZoom,
+          ],
+        ),
+        returnValueForMissingStub: null,
+      );
 }
 
 /// A class which mocks [TestWebStorageHostApi].
diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart
index 03489ce..0562bb7 100644
--- a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart
@@ -269,6 +269,15 @@
         returnValueForMissingStub: _i5.Future<void>.value(),
       ) as _i5.Future<void>);
   @override
+  _i5.Future<void> setTextZoom(int? textZoom) => (super.noSuchMethod(
+        Invocation.method(
+          #setTextZoom,
+          [textZoom],
+        ),
+        returnValue: _i5.Future<void>.value(),
+        returnValueForMissingStub: _i5.Future<void>.value(),
+      ) as _i5.Future<void>);
+  @override
   _i2.WebSettings copy() => (super.noSuchMethod(
         Invocation.method(
           #copy,
diff --git a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart
index 1b372e6..f1a19ec 100644
--- a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart
@@ -720,6 +720,8 @@
 
   void setAllowFileAccess(int instanceId, bool enabled);
 
+  void setTextZoom(int instanceId, int textZoom);
+
   static void setup(TestWebSettingsHostApi? api,
       {BinaryMessenger? binaryMessenger}) {
     {
@@ -1012,6 +1014,28 @@
         });
       }
     }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.WebSettingsHostApi.setTextZoom', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.WebSettingsHostApi.setTextZoom was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final int? arg_instanceId = (args[0] as int?);
+          assert(arg_instanceId != null,
+              'Argument for dev.flutter.pigeon.WebSettingsHostApi.setTextZoom was null, expected non-null int.');
+          final int? arg_textZoom = (args[1] as int?);
+          assert(arg_textZoom != null,
+              'Argument for dev.flutter.pigeon.WebSettingsHostApi.setTextZoom was null, expected non-null int.');
+          api.setTextZoom(arg_instanceId!, arg_textZoom!);
+          return <Object?>[];
+        });
+      }
+    }
   }
 }