Skip to content

[webview_flutter_android][webview_flutter_wkwebview] Adds platform implementations to set over-scroll mode #9101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.4.0

* Adds support to set the over-scroll mode for the WebView. See `AndroidWebViewController.setOverScrollMode`.

## 4.3.5

* Adds internal wrapper methods for native `WebViewClient`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,7 @@ private class AndroidWebkitLibraryPigeonProxyApiBaseCodec(
value is String ||
value is FileChooserMode ||
value is ConsoleMessageLevel ||
value is OverScrollMode ||
value is SslErrorType ||
value == null) {
super.writeValue(stream, value)
Expand Down Expand Up @@ -814,6 +815,31 @@ enum class ConsoleMessageLevel(val raw: Int) {
}
}

/**
* The over-scroll mode for a view.
*
* See https://developer.android.com/reference/android/view/View#OVER_SCROLL_ALWAYS.
*/
enum class OverScrollMode(val raw: Int) {
/** Always allow a user to over-scroll this view, provided it is a view that can scroll. */
ALWAYS(0),
/**
* Allow a user to over-scroll this view only if the content is large enough to meaningfully
* scroll, provided it is a view that can scroll.
*/
IF_CONTENT_SCROLLS(1),
/** Never allow a user to over-scroll this view. */
NEVER(2),
/** The type is not recognized by this wrapper. */
UNKNOWN(3);

companion object {
fun ofRaw(raw: Int): OverScrollMode? {
return values().firstOrNull { it.raw == raw }
}
}
}

/**
* Type of error for a SslCertificate.
*
Expand Down Expand Up @@ -852,6 +878,9 @@ private open class AndroidWebkitLibraryPigeonCodec : StandardMessageCodec() {
return (readValue(buffer) as Long?)?.let { ConsoleMessageLevel.ofRaw(it.toInt()) }
}
131.toByte() -> {
return (readValue(buffer) as Long?)?.let { OverScrollMode.ofRaw(it.toInt()) }
}
132.toByte() -> {
return (readValue(buffer) as Long?)?.let { SslErrorType.ofRaw(it.toInt()) }
}
else -> super.readValueOfType(type, buffer)
Expand All @@ -868,10 +897,14 @@ private open class AndroidWebkitLibraryPigeonCodec : StandardMessageCodec() {
stream.write(130)
writeValue(stream, value.raw)
}
is SslErrorType -> {
is OverScrollMode -> {
stream.write(131)
writeValue(stream, value.raw)
}
is SslErrorType -> {
stream.write(132)
writeValue(stream, value.raw)
}
else -> super.writeValue(stream, value)
}
}
Expand Down Expand Up @@ -4780,6 +4813,9 @@ abstract class PigeonApiView(
/** Return the scrolled position of this view. */
abstract fun getScrollPosition(pigeon_instance: android.view.View): WebViewPoint

/** Set the over-scroll mode for this view. */
abstract fun setOverScrollMode(pigeon_instance: android.view.View, mode: OverScrollMode)

companion object {
@Suppress("LocalVariableName")
fun setUpMessageHandlers(binaryMessenger: BinaryMessenger, api: PigeonApiView?) {
Expand Down Expand Up @@ -4852,6 +4888,30 @@ abstract class PigeonApiView(
channel.setMessageHandler(null)
}
}
run {
val channel =
BasicMessageChannel<Any?>(
binaryMessenger,
"dev.flutter.pigeon.webview_flutter_android.View.setOverScrollMode",
codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val pigeon_instanceArg = args[0] as android.view.View
val modeArg = args[1] as OverScrollMode
val wrapped: List<Any?> =
try {
api.setOverScrollMode(pigeon_instanceArg, modeArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ public ViewProxyApi(@NonNull ProxyApiRegistrar pigeonRegistrar) {
super(pigeonRegistrar);
}

@NonNull
@Override
public ProxyApiRegistrar getPigeonRegistrar() {
return (ProxyApiRegistrar) super.getPigeonRegistrar();
}

@Override
public void scrollTo(@NonNull View pigeon_instance, long x, long y) {
pigeon_instance.scrollTo((int) x, (int) y);
Expand All @@ -34,4 +40,21 @@ public void scrollBy(@NonNull View pigeon_instance, long x, long y) {
public WebViewPoint getScrollPosition(@NonNull View pigeon_instance) {
return new WebViewPoint(pigeon_instance.getScrollX(), pigeon_instance.getScrollY());
}

@Override
public void setOverScrollMode(@NonNull View pigeon_instance, @NonNull OverScrollMode mode) {
switch (mode) {
case ALWAYS:
pigeon_instance.setOverScrollMode(View.OVER_SCROLL_ALWAYS);
break;
case IF_CONTENT_SCROLLS:
pigeon_instance.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS);
break;
case NEVER:
pigeon_instance.setOverScrollMode(View.OVER_SCROLL_NEVER);
break;
case UNKNOWN:
throw getPigeonRegistrar().createUnknownEnumException(OverScrollMode.UNKNOWN);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,15 @@ public void getScrollPosition() {
assertEquals(value.getX(), api.getScrollPosition(instance).getX());
assertEquals(value.getY(), api.getScrollPosition(instance).getY());
}

@Test
public void setOverScrollMode() {
final PigeonApiView api = new TestProxyApiRegistrar().getPigeonApiView();

final View instance = mock(View.class);
final OverScrollMode mode = io.flutter.plugins.webviewflutter.OverScrollMode.ALWAYS;
api.setOverScrollMode(instance, mode);

verify(instance).setOverScrollMode(View.OVER_SCROLL_ALWAYS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies:
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
webview_flutter_platform_interface: ^2.10.0
webview_flutter_platform_interface: ^2.11.0

dev_dependencies:
espresso: ^0.4.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,25 @@ enum ConsoleMessageLevel {
unknown,
}

/// The over-scroll mode for a view.
///
/// See https://developer.android.com/reference/android/view/View#OVER_SCROLL_ALWAYS.
enum OverScrollMode {
/// Always allow a user to over-scroll this view, provided it is a view that
/// can scroll.
always,

/// Allow a user to over-scroll this view only if the content is large enough
/// to meaningfully scroll, provided it is a view that can scroll.
ifContentScrolls,

/// Never allow a user to over-scroll this view.
never,

/// The type is not recognized by this wrapper.
unknown,
}

/// Type of error for a SslCertificate.
///
/// See https://developer.android.com/reference/android/net/http/SslError#SSL_DATE_INVALID.
Expand Down Expand Up @@ -560,9 +579,12 @@ class _PigeonCodec extends StandardMessageCodec {
} else if (value is ConsoleMessageLevel) {
buffer.putUint8(130);
writeValue(buffer, value.index);
} else if (value is SslErrorType) {
} else if (value is OverScrollMode) {
buffer.putUint8(131);
writeValue(buffer, value.index);
} else if (value is SslErrorType) {
buffer.putUint8(132);
writeValue(buffer, value.index);
} else {
super.writeValue(buffer, value);
}
Expand All @@ -578,6 +600,9 @@ class _PigeonCodec extends StandardMessageCodec {
final int? value = readValue(buffer) as int?;
return value == null ? null : ConsoleMessageLevel.values[value];
case 131:
final int? value = readValue(buffer) as int?;
return value == null ? null : OverScrollMode.values[value];
case 132:
final int? value = readValue(buffer) as int?;
return value == null ? null : SslErrorType.values[value];
default:
Expand Down Expand Up @@ -6586,6 +6611,36 @@ class View extends PigeonInternalProxyApiBaseClass {
}
}

/// Set the over-scroll mode for this view.
Future<void> setOverScrollMode(OverScrollMode mode) async {
final _PigeonInternalProxyApiBaseCodec pigeonChannelCodec =
_pigeonVar_codecView;
final BinaryMessenger? pigeonVar_binaryMessenger = pigeon_binaryMessenger;
const String pigeonVar_channelName =
'dev.flutter.pigeon.webview_flutter_android.View.setOverScrollMode';
final BasicMessageChannel<Object?> pigeonVar_channel =
BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture =
pigeonVar_channel.send(<Object?>[this, mode]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}

@override
View pigeon_copy() {
return View.pigeon_detached(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,24 @@ class AndroidWebViewController extends PlatformWebViewController {
_onJavaScriptPrompt = onJavaScriptTextInputDialog;
return _webChromeClient.setSynchronousReturnValueForOnJsPrompt(true);
}

@override
Future<void> setOverScrollMode(WebViewOverScrollMode mode) {
return switch (mode) {
WebViewOverScrollMode.always => _webView.setOverScrollMode(
android_webview.OverScrollMode.always,
),
WebViewOverScrollMode.ifContentScrolls => _webView.setOverScrollMode(
android_webview.OverScrollMode.ifContentScrolls,
),
WebViewOverScrollMode.never => _webView.setOverScrollMode(
android_webview.OverScrollMode.never,
),
// This prevents future additions from causing a breaking change.
// ignore: unreachable_switch_case
_ => throw UnsupportedError('Android does not support $mode.'),
};
}
}

/// Android implementation of [PlatformWebViewPermissionRequest].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ enum ConsoleMessageLevel {
unknown,
}

/// The over-scroll mode for a view.
///
/// See https://developer.android.com/reference/android/view/View#OVER_SCROLL_ALWAYS.
enum OverScrollMode {
/// Always allow a user to over-scroll this view, provided it is a view that
/// can scroll.
always,

/// Allow a user to over-scroll this view only if the content is large enough
/// to meaningfully scroll, provided it is a view that can scroll.
ifContentScrolls,

/// Never allow a user to over-scroll this view.
never,

/// The type is not recognized by this wrapper.
unknown,
}

/// Type of error for a SslCertificate.
///
/// See https://developer.android.com/reference/android/net/http/SslError#SSL_DATE_INVALID.
Expand Down Expand Up @@ -832,6 +851,9 @@ abstract class View {

/// Return the scrolled position of this view.
WebViewPoint getScrollPosition();

/// Set the over-scroll mode for this view.
void setOverScrollMode(OverScrollMode mode);
}

/// A callback interface used by the host application to set the Geolocation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: webview_flutter_android
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: 4.3.5
version: 4.4.0

environment:
sdk: ^3.6.0
Expand All @@ -20,14 +20,14 @@ flutter:
dependencies:
flutter:
sdk: flutter
webview_flutter_platform_interface: ^2.10.0
webview_flutter_platform_interface: ^2.11.0

dev_dependencies:
build_runner: ^2.1.4
flutter_test:
sdk: flutter
mockito: ^5.4.4
pigeon: ^25.2.0
pigeon: ^25.3.0

topics:
- html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,21 @@ void main() {
verify(mockSettings.setTextZoom(100)).called(1);
});

test('setOverScrollMode', () async {
final MockWebView mockWebView = MockWebView();
final AndroidWebViewController controller = createControllerWithMocks(
mockWebView: mockWebView,
);

await controller.setOverScrollMode(WebViewOverScrollMode.always);

verify(
mockWebView.setOverScrollMode(
android_webview.OverScrollMode.always,
),
).called(1);
});

test('webViewIdentifier', () {
final MockWebView mockWebView = MockWebView();

Expand Down
Loading