@capgo/capacitor-inappbrowser 8.19.0 → 8.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -4
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/CapgoInAppBrowserPlugin.java +63 -3
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/FullscreenWindowState.java +72 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java +10 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +271 -21
- package/android/src/main/res/drawable/ic_fullscreen_exit.xml +10 -0
- package/android/src/main/res/layout/fullscreen_exit_button.xml +24 -0
- package/android/src/main/res/values/fullscreen_exit.xml +10 -0
- package/android/src/main/res/values/strings.xml +1 -0
- package/android/src/main/res/xml/fullscreen_exit_shape.xml +6 -0
- package/dist/docs.json +95 -0
- package/dist/esm/definitions.d.ts +59 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +9 -0
- package/dist/esm/web.js +11 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +11 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +11 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/InAppBrowserPlugin/BrowserFullscreenSupport.swift +178 -0
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +83 -4
- package/ios/Sources/InAppBrowserPlugin/WKWebViewController.swift +36 -6
- package/package.json +2 -2
|
@@ -40,6 +40,7 @@ import android.util.Log;
|
|
|
40
40
|
import android.util.TypedValue;
|
|
41
41
|
import android.view.Gravity;
|
|
42
42
|
import android.view.KeyEvent;
|
|
43
|
+
import android.view.LayoutInflater;
|
|
43
44
|
import android.view.MotionEvent;
|
|
44
45
|
import android.view.View;
|
|
45
46
|
import android.view.ViewGroup;
|
|
@@ -74,6 +75,8 @@ import androidx.activity.OnBackPressedCallback;
|
|
|
74
75
|
import androidx.activity.result.ActivityResult;
|
|
75
76
|
import androidx.activity.result.ActivityResultLauncher;
|
|
76
77
|
import androidx.activity.result.contract.ActivityResultContracts;
|
|
78
|
+
import androidx.appcompat.view.ContextThemeWrapper;
|
|
79
|
+
import androidx.appcompat.widget.TooltipCompat;
|
|
77
80
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
|
78
81
|
import androidx.core.content.FileProvider;
|
|
79
82
|
import androidx.core.content.res.ResourcesCompat;
|
|
@@ -91,6 +94,8 @@ import com.caverock.androidsvg.SVG;
|
|
|
91
94
|
import com.caverock.androidsvg.SVGParseException;
|
|
92
95
|
import com.getcapacitor.JSObject;
|
|
93
96
|
import com.getcapacitor.PluginCall;
|
|
97
|
+
import com.google.android.material.button.MaterialButton;
|
|
98
|
+
import com.google.android.material.color.DynamicColors;
|
|
94
99
|
import java.io.ByteArrayInputStream;
|
|
95
100
|
import java.io.ByteArrayOutputStream;
|
|
96
101
|
import java.io.File;
|
|
@@ -338,6 +343,154 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
338
343
|
private SwipeRefreshLayout swipeRefreshLayout;
|
|
339
344
|
private boolean reloadFromGestureInProgress = false;
|
|
340
345
|
private WebView _webView;
|
|
346
|
+
private FullscreenWindowState nativeFullscreenWindow;
|
|
347
|
+
private FullscreenWindowState mediaFullscreenWindow;
|
|
348
|
+
private MaterialButton fullscreenExitButton;
|
|
349
|
+
private java.util.function.Consumer<Boolean> fullscreenChangeListener;
|
|
350
|
+
private final java.util.Map<View, Integer> fullscreenVisibility = new java.util.HashMap<>();
|
|
351
|
+
private int[] fullscreenPadding;
|
|
352
|
+
private CoordinatorLayout.Behavior<?> fullscreenContentBehavior;
|
|
353
|
+
private String fullscreenOrigin;
|
|
354
|
+
|
|
355
|
+
public void setFullscreenChangeListener(java.util.function.Consumer<Boolean> listener) {
|
|
356
|
+
fullscreenChangeListener = listener;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
public boolean isFullscreen() {
|
|
360
|
+
return nativeFullscreenWindow != null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
public void clearFullscreen() {
|
|
364
|
+
if (_options != null) _options.setFullscreen(false);
|
|
365
|
+
exitCustomFullscreenView();
|
|
366
|
+
setFullscreen(false);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
public void setFullscreen(boolean enabled) {
|
|
370
|
+
if (!enabled && _options != null) _options.setFullscreen(false);
|
|
371
|
+
if (enabled == isFullscreen()) return;
|
|
372
|
+
if (enabled) {
|
|
373
|
+
if (
|
|
374
|
+
_webView == null ||
|
|
375
|
+
!activeForBackNavigation ||
|
|
376
|
+
isDismissing ||
|
|
377
|
+
isHiddenModeActive ||
|
|
378
|
+
(_options != null &&
|
|
379
|
+
(_options.isHidden() ||
|
|
380
|
+
_options.isToBack() ||
|
|
381
|
+
_options.getWidth() != null ||
|
|
382
|
+
_options.getHeight() != null ||
|
|
383
|
+
_options.getX() != null ||
|
|
384
|
+
_options.getY() != null)) ||
|
|
385
|
+
backLayerActive ||
|
|
386
|
+
getWindow() == null
|
|
387
|
+
) {
|
|
388
|
+
throw new IllegalStateException("Fullscreen requires a visible, full-size front WebView");
|
|
389
|
+
}
|
|
390
|
+
// Finish media first: its saved window state must not outlive the browser baseline.
|
|
391
|
+
exitCustomFullscreenView();
|
|
392
|
+
nativeFullscreenWindow = new FullscreenWindowState(getWindow());
|
|
393
|
+
fullscreenOrigin = origin(_webView.getUrl() != null ? _webView.getUrl() : _options.getUrl());
|
|
394
|
+
for (int id : new int[] { R.id.app_bar_layout, R.id.status_bar_color_view }) {
|
|
395
|
+
View view = findBrowserContentDescendant(id);
|
|
396
|
+
if (view != null) {
|
|
397
|
+
fullscreenVisibility.put(view, view.getVisibility());
|
|
398
|
+
view.setVisibility(View.GONE);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
View container = findBrowserContentDescendant(R.id.content_browser_layout);
|
|
402
|
+
if (container != null) {
|
|
403
|
+
fullscreenPadding = new int[] {
|
|
404
|
+
container.getPaddingLeft(),
|
|
405
|
+
container.getPaddingTop(),
|
|
406
|
+
container.getPaddingRight(),
|
|
407
|
+
container.getPaddingBottom()
|
|
408
|
+
};
|
|
409
|
+
container.setPadding(0, 0, 0, 0);
|
|
410
|
+
if (container.getLayoutParams() instanceof CoordinatorLayout.LayoutParams params) {
|
|
411
|
+
// A hidden appbar retains its measured height; its scrolling behavior still reserves that space.
|
|
412
|
+
fullscreenContentBehavior = params.getBehavior();
|
|
413
|
+
params.setBehavior(null);
|
|
414
|
+
container.setLayoutParams(params);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
nativeFullscreenWindow.enter();
|
|
418
|
+
// Scope Material styling to the overlay, including hosts without a Material theme.
|
|
419
|
+
Context buttonContext = DynamicColors.wrapContextIfAvailable(
|
|
420
|
+
new ContextThemeWrapper(getContext(), com.google.android.material.R.style.Theme_Material3_DayNight_NoActionBar)
|
|
421
|
+
);
|
|
422
|
+
ViewGroup decor = (ViewGroup) getWindow().getDecorView();
|
|
423
|
+
fullscreenExitButton = (MaterialButton) LayoutInflater.from(buttonContext).inflate(
|
|
424
|
+
R.layout.fullscreen_exit_button,
|
|
425
|
+
decor,
|
|
426
|
+
false
|
|
427
|
+
);
|
|
428
|
+
TooltipCompat.setTooltipText(fullscreenExitButton, getContext().getString(R.string.exit_fullscreen));
|
|
429
|
+
fullscreenExitButton.setOnClickListener((v) -> clearFullscreen());
|
|
430
|
+
decor.addView(fullscreenExitButton);
|
|
431
|
+
ViewCompat.setOnApplyWindowInsetsListener(fullscreenExitButton, (v, insets) -> {
|
|
432
|
+
updateFullscreenExitInsets(insets);
|
|
433
|
+
return insets;
|
|
434
|
+
});
|
|
435
|
+
ViewCompat.requestApplyInsets(fullscreenExitButton);
|
|
436
|
+
} else {
|
|
437
|
+
exitCustomFullscreenView();
|
|
438
|
+
FullscreenWindowState baseline = nativeFullscreenWindow;
|
|
439
|
+
nativeFullscreenWindow = null;
|
|
440
|
+
if (fullscreenExitButton != null && fullscreenExitButton.getParent() instanceof ViewGroup parent) parent.removeView(
|
|
441
|
+
fullscreenExitButton
|
|
442
|
+
);
|
|
443
|
+
fullscreenExitButton = null;
|
|
444
|
+
for (java.util.Map.Entry<View, Integer> entry : fullscreenVisibility.entrySet()) entry.getKey().setVisibility(entry.getValue());
|
|
445
|
+
fullscreenVisibility.clear();
|
|
446
|
+
View container = findBrowserContentDescendant(R.id.content_browser_layout);
|
|
447
|
+
if (container != null && fullscreenPadding != null) container.setPadding(
|
|
448
|
+
fullscreenPadding[0],
|
|
449
|
+
fullscreenPadding[1],
|
|
450
|
+
fullscreenPadding[2],
|
|
451
|
+
fullscreenPadding[3]
|
|
452
|
+
);
|
|
453
|
+
fullscreenPadding = null;
|
|
454
|
+
if (container != null && container.getLayoutParams() instanceof CoordinatorLayout.LayoutParams params) {
|
|
455
|
+
params.setBehavior(fullscreenContentBehavior);
|
|
456
|
+
container.setLayoutParams(params);
|
|
457
|
+
}
|
|
458
|
+
fullscreenContentBehavior = null;
|
|
459
|
+
baseline.restore();
|
|
460
|
+
reapplyInsetsFromWindowRoot();
|
|
461
|
+
}
|
|
462
|
+
if (fullscreenChangeListener != null) fullscreenChangeListener.accept(enabled);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private void updateFullscreenExitInsets(WindowInsetsCompat insets) {
|
|
466
|
+
if (fullscreenExitButton == null || insets == null) return;
|
|
467
|
+
// Hidden status bars need no space in landscape; cutouts and navigation gestures always do.
|
|
468
|
+
Insets safe = Insets.max(
|
|
469
|
+
insets.getInsets(WindowInsetsCompat.Type.systemBars()),
|
|
470
|
+
insets.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.navigationBars() | WindowInsetsCompat.Type.displayCutout())
|
|
471
|
+
);
|
|
472
|
+
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) fullscreenExitButton.getLayoutParams();
|
|
473
|
+
int margin = getContext().getResources().getDimensionPixelSize(R.dimen.fullscreen_exit_margin);
|
|
474
|
+
params.topMargin = safe.top + margin;
|
|
475
|
+
params.rightMargin = safe.right + margin;
|
|
476
|
+
fullscreenExitButton.setLayoutParams(params);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private static String origin(String url) {
|
|
480
|
+
if (url == null) return null;
|
|
481
|
+
try {
|
|
482
|
+
URI uri = new URI(url);
|
|
483
|
+
String scheme = uri.getScheme();
|
|
484
|
+
String host = uri.getHost();
|
|
485
|
+
if (scheme == null || host == null) return url;
|
|
486
|
+
int port = uri.getPort();
|
|
487
|
+
if (port == -1) port = "https".equalsIgnoreCase(scheme) ? 443 : 80;
|
|
488
|
+
return scheme.toLowerCase(java.util.Locale.ROOT) + "://" + host.toLowerCase(java.util.Locale.ROOT) + ":" + port;
|
|
489
|
+
} catch (Exception error) {
|
|
490
|
+
return url;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
341
494
|
// HTML5/iframe fullscreen (e.g. embedded YouTube) routes through WebChromeClient custom views.
|
|
342
495
|
private View customFullscreenView;
|
|
343
496
|
private WebChromeClient.CustomViewCallback customViewCallback;
|
|
@@ -428,6 +581,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
428
581
|
private ActivityResultLauncher<Intent> fileChooserLauncher;
|
|
429
582
|
private boolean openWebViewResolved;
|
|
430
583
|
private boolean isDismissing = false;
|
|
584
|
+
private AlertDialog backCloseConfirmDialog;
|
|
431
585
|
private PermissionRequest pendingCameraLaunchPermissionRequest;
|
|
432
586
|
|
|
433
587
|
// Temporary URI for storing camera capture
|
|
@@ -469,11 +623,24 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
469
623
|
@Override
|
|
470
624
|
protected void onStart() {
|
|
471
625
|
super.onStart();
|
|
626
|
+
applyStartupFullscreen();
|
|
472
627
|
// ComponentDialog registers its own back callback in onCreate; re-add ours last so
|
|
473
628
|
// handleBrowserBackNavigation runs before the built-in cancel callback (LIFO order).
|
|
474
629
|
ensureDialogBackHandlerOnTop();
|
|
475
630
|
}
|
|
476
631
|
|
|
632
|
+
private void applyStartupFullscreen() {
|
|
633
|
+
if (_options != null && _options.isFullscreen() && !_options.isHidden()) {
|
|
634
|
+
_options.setFullscreen(false);
|
|
635
|
+
try {
|
|
636
|
+
setFullscreen(true);
|
|
637
|
+
} catch (IllegalStateException error) {
|
|
638
|
+
// Deferred presentation or page-initiated show may target an inactive WebView.
|
|
639
|
+
Log.w("InAppBrowser", "Skipping deferred fullscreen entry: " + error.getMessage());
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
477
644
|
@Override
|
|
478
645
|
public void show() {
|
|
479
646
|
super.show();
|
|
@@ -698,6 +865,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
698
865
|
}
|
|
699
866
|
|
|
700
867
|
public boolean sendToBack(boolean transparentBackground) {
|
|
868
|
+
clearFullscreen();
|
|
701
869
|
if (_options != null) {
|
|
702
870
|
_options.setToBack(true);
|
|
703
871
|
_options.setTransparentBackground(transparentBackground);
|
|
@@ -900,6 +1068,10 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
900
1068
|
|
|
901
1069
|
activity.runOnUiThread(() -> {
|
|
902
1070
|
try {
|
|
1071
|
+
if (isDismissing) {
|
|
1072
|
+
// Dismissal already started (e.g. close button or back); do not close twice.
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
903
1075
|
String currentUrl = getUrl();
|
|
904
1076
|
dismiss();
|
|
905
1077
|
|
|
@@ -925,7 +1097,10 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
925
1097
|
Log.e("InAppBrowser", "Cannot hide - activity is null");
|
|
926
1098
|
return;
|
|
927
1099
|
}
|
|
928
|
-
activity.runOnUiThread(() ->
|
|
1100
|
+
activity.runOnUiThread(() -> {
|
|
1101
|
+
clearFullscreen();
|
|
1102
|
+
setHidden(true);
|
|
1103
|
+
});
|
|
929
1104
|
}
|
|
930
1105
|
|
|
931
1106
|
@JavascriptInterface
|
|
@@ -2162,7 +2337,10 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
2162
2337
|
setContentView(R.layout.activity_browser);
|
|
2163
2338
|
|
|
2164
2339
|
// If custom dimensions are set, configure for touch passthrough
|
|
2165
|
-
if (
|
|
2340
|
+
if (
|
|
2341
|
+
_options != null &&
|
|
2342
|
+
(_options.getWidth() != null || _options.getHeight() != null || _options.getX() != null || _options.getY() != null)
|
|
2343
|
+
) {
|
|
2166
2344
|
Window window = getWindow();
|
|
2167
2345
|
if (window != null) {
|
|
2168
2346
|
// Make the dialog background transparent
|
|
@@ -2801,6 +2979,10 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
2801
2979
|
public void onCloseWindow(WebView window) {
|
|
2802
2980
|
Log.d("InAppBrowser", "onCloseWindow called");
|
|
2803
2981
|
if (window == _webView) {
|
|
2982
|
+
if (isDismissing) {
|
|
2983
|
+
// Dismissal already started (e.g. close button or back); do not close twice.
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2804
2986
|
String currentUrl = getUrl();
|
|
2805
2987
|
dismiss();
|
|
2806
2988
|
if (_options != null && _options.getCallbacks() != null) {
|
|
@@ -2850,9 +3032,9 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
2850
3032
|
}
|
|
2851
3033
|
|
|
2852
3034
|
try {
|
|
2853
|
-
loadInitialWebViewContent(requestHeaders);
|
|
2854
3035
|
setupToolbar();
|
|
2855
3036
|
setWebViewClient();
|
|
3037
|
+
loadInitialWebViewContent(requestHeaders);
|
|
2856
3038
|
|
|
2857
3039
|
if (this._options.isHidden()) {
|
|
2858
3040
|
if (_options.isPopupWindowMode() || _options.getInvisibilityMode() == Options.InvisibilityMode.FAKE_VISIBLE) {
|
|
@@ -3002,6 +3184,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3002
3184
|
if (activeForBackNavigation == active) {
|
|
3003
3185
|
return;
|
|
3004
3186
|
}
|
|
3187
|
+
if (!active && (isFullscreen() || customFullscreenView != null)) clearFullscreen();
|
|
3005
3188
|
activeForBackNavigation = active;
|
|
3006
3189
|
syncBackNavigationHandlers();
|
|
3007
3190
|
}
|
|
@@ -3030,10 +3213,18 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3030
3213
|
}
|
|
3031
3214
|
|
|
3032
3215
|
private boolean shouldConsumeBackPress() {
|
|
3033
|
-
return WebViewBackNavigationSupport.shouldConsumeBackPress(isShowing(), isHiddenModeActive, backLayerActive);
|
|
3216
|
+
return isFullscreen() || WebViewBackNavigationSupport.shouldConsumeBackPress(isShowing(), isHiddenModeActive, backLayerActive);
|
|
3034
3217
|
}
|
|
3035
3218
|
|
|
3036
3219
|
private void handleBrowserBackNavigation() {
|
|
3220
|
+
if (WebViewCustomFullscreenSupport.shouldConsumeBackPress(customFullscreenView != null)) {
|
|
3221
|
+
exitCustomFullscreenView();
|
|
3222
|
+
return;
|
|
3223
|
+
}
|
|
3224
|
+
if (isFullscreen()) {
|
|
3225
|
+
clearFullscreen();
|
|
3226
|
+
return;
|
|
3227
|
+
}
|
|
3037
3228
|
if (_options == null) {
|
|
3038
3229
|
dismiss();
|
|
3039
3230
|
return;
|
|
@@ -3061,6 +3252,26 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3061
3252
|
case DISMISS:
|
|
3062
3253
|
default:
|
|
3063
3254
|
String currentUrl = getUrl();
|
|
3255
|
+
Pattern urlPattern = _options.getCloseModalURLPattern();
|
|
3256
|
+
if (_options.getCloseModal() && (urlPattern == null || urlPattern.matcher(currentUrl).find())) {
|
|
3257
|
+
// Confirm like the toolbar close button; OK does the same close as below
|
|
3258
|
+
backCloseConfirmDialog = new AlertDialog.Builder(_context)
|
|
3259
|
+
.setTitle(_options.getCloseModalTitle())
|
|
3260
|
+
.setMessage(_options.getCloseModalDescription())
|
|
3261
|
+
.setPositiveButton(_options.getCloseModalOk(), (dialog, which) -> {
|
|
3262
|
+
if (isDismissing) {
|
|
3263
|
+
return;
|
|
3264
|
+
}
|
|
3265
|
+
if (_options.getCallbacks() != null) {
|
|
3266
|
+
_options.getCallbacks().confirmBtnClicked(currentUrl);
|
|
3267
|
+
_options.getCallbacks().closeEvent(currentUrl);
|
|
3268
|
+
}
|
|
3269
|
+
dismiss();
|
|
3270
|
+
})
|
|
3271
|
+
.setNegativeButton(_options.getCloseModalCancel(), null)
|
|
3272
|
+
.show();
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3064
3275
|
if (_options.getCallbacks() != null) {
|
|
3065
3276
|
_options.getCallbacks().closeEvent(currentUrl);
|
|
3066
3277
|
}
|
|
@@ -3106,8 +3317,9 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3106
3317
|
_webView.setVisibility(View.INVISIBLE);
|
|
3107
3318
|
}
|
|
3108
3319
|
|
|
3109
|
-
window
|
|
3110
|
-
|
|
3320
|
+
mediaFullscreenWindow = new FullscreenWindowState(window);
|
|
3321
|
+
mediaFullscreenWindow.enter();
|
|
3322
|
+
if (fullscreenExitButton != null) fullscreenExitButton.bringToFront();
|
|
3111
3323
|
|
|
3112
3324
|
if (WebViewCustomFullscreenSupport.shouldRegisterHostBackHandler(backLayerActive)) {
|
|
3113
3325
|
registerCustomFullscreenBackHandler();
|
|
@@ -3162,15 +3374,12 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3162
3374
|
_webView.setVisibility(View.VISIBLE);
|
|
3163
3375
|
}
|
|
3164
3376
|
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
WebViewCustomFullscreenSupport.exitImmersiveFullscreen(window, window.getDecorView());
|
|
3169
|
-
reapplyInsetsFromWindowRoot();
|
|
3170
|
-
if (SystemUiChromeSupport.requiresEdgeToEdgeChrome(Build.VERSION.SDK_INT)) {
|
|
3171
|
-
refreshEdgeToEdgeChrome();
|
|
3172
|
-
}
|
|
3377
|
+
if (mediaFullscreenWindow != null) {
|
|
3378
|
+
mediaFullscreenWindow.restore();
|
|
3379
|
+
mediaFullscreenWindow = null;
|
|
3173
3380
|
}
|
|
3381
|
+
if (nativeFullscreenWindow != null) nativeFullscreenWindow.enter();
|
|
3382
|
+
else reapplyInsetsFromWindowRoot();
|
|
3174
3383
|
|
|
3175
3384
|
unregisterCustomFullscreenBackHandler();
|
|
3176
3385
|
|
|
@@ -3275,6 +3484,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3275
3484
|
}
|
|
3276
3485
|
|
|
3277
3486
|
public void setHidden(boolean hidden) {
|
|
3487
|
+
if (hidden && (isFullscreen() || (_options != null && !_options.isHidden()))) clearFullscreen();
|
|
3278
3488
|
if (hidden) {
|
|
3279
3489
|
if (!isHiddenModeActive) {
|
|
3280
3490
|
if (getWindow() == null) {
|
|
@@ -3314,6 +3524,9 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3314
3524
|
}
|
|
3315
3525
|
if (_options != null) {
|
|
3316
3526
|
_options.setHidden(hidden);
|
|
3527
|
+
if (isShowing()) {
|
|
3528
|
+
applyStartupFullscreen();
|
|
3529
|
+
}
|
|
3317
3530
|
}
|
|
3318
3531
|
}
|
|
3319
3532
|
|
|
@@ -3508,6 +3721,11 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3508
3721
|
* after portrait↔landscape transitions.
|
|
3509
3722
|
*/
|
|
3510
3723
|
private void refreshLayoutForConfigurationChange() {
|
|
3724
|
+
if (isFullscreen()) {
|
|
3725
|
+
nativeFullscreenWindow.enter();
|
|
3726
|
+
updateFullscreenExitInsets(ViewCompat.getRootWindowInsets(getWindow().getDecorView()));
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3511
3729
|
if (isDismissing || _webView == null) {
|
|
3512
3730
|
return;
|
|
3513
3731
|
}
|
|
@@ -3684,6 +3902,18 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
3684
3902
|
return;
|
|
3685
3903
|
}
|
|
3686
3904
|
|
|
3905
|
+
if (isFullscreen()) {
|
|
3906
|
+
updateFullscreenExitInsets(windowInsets);
|
|
3907
|
+
View container = findBrowserContentDescendant(R.id.content_browser_layout);
|
|
3908
|
+
if (container != null) {
|
|
3909
|
+
// FLAG_FULLSCREEN disables adjustResize even before Android 15; keep only the keyboard inset.
|
|
3910
|
+
int imeBottom = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
|
|
3911
|
+
? windowInsets.getInsets(WindowInsetsCompat.Type.ime()).bottom
|
|
3912
|
+
: 0;
|
|
3913
|
+
container.setPadding(0, 0, 0, imeBottom);
|
|
3914
|
+
}
|
|
3915
|
+
return;
|
|
3916
|
+
}
|
|
3687
3917
|
Insets bars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
|
3688
3918
|
Insets navigationBars = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars());
|
|
3689
3919
|
Insets systemGestures = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
|
|
@@ -6316,9 +6546,20 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
6316
6546
|
super.onLoadResource(view, url);
|
|
6317
6547
|
}
|
|
6318
6548
|
|
|
6549
|
+
@Override
|
|
6550
|
+
public boolean onRenderProcessGone(WebView view, android.webkit.RenderProcessGoneDetail detail) {
|
|
6551
|
+
clearFullscreen();
|
|
6552
|
+
dismiss();
|
|
6553
|
+
return true;
|
|
6554
|
+
}
|
|
6555
|
+
|
|
6319
6556
|
@Override
|
|
6320
6557
|
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
|
6321
6558
|
super.onPageStarted(view, url, favicon);
|
|
6559
|
+
if (
|
|
6560
|
+
(isFullscreen() && !java.util.Objects.equals(fullscreenOrigin, origin(url))) ||
|
|
6561
|
+
(_options.isFullscreen() && !java.util.Objects.equals(origin(_options.getUrl()), origin(url)))
|
|
6562
|
+
) clearFullscreen();
|
|
6322
6563
|
if (view == null || _webView == null) {
|
|
6323
6564
|
return;
|
|
6324
6565
|
}
|
|
@@ -6361,13 +6602,13 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
6361
6602
|
@Override
|
|
6362
6603
|
public void onPageFinished(WebView view, String url) {
|
|
6363
6604
|
super.onPageFinished(view, url);
|
|
6364
|
-
if (view == null || _webView
|
|
6605
|
+
if (view == null || _webView != view || isDismissing) {
|
|
6365
6606
|
return;
|
|
6366
6607
|
}
|
|
6367
6608
|
if (!isInitialized) {
|
|
6368
6609
|
isInitialized = true;
|
|
6369
6610
|
_webView.clearHistory();
|
|
6370
|
-
if (_options.isPresentAfterPageLoad()) {
|
|
6611
|
+
if (_options.isPresentAfterPageLoad() && !_options.isHidden()) {
|
|
6371
6612
|
boolean usePreShowScript =
|
|
6372
6613
|
_options.getPreShowScript() != null &&
|
|
6373
6614
|
!_options.getPreShowScript().isEmpty() &&
|
|
@@ -6388,7 +6629,9 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
6388
6629
|
new Runnable() {
|
|
6389
6630
|
@Override
|
|
6390
6631
|
public void run() {
|
|
6391
|
-
|
|
6632
|
+
if (isDismissing || _webView != view) return;
|
|
6633
|
+
// Visibility can change while the pre-show script is running.
|
|
6634
|
+
if (!_options.isHidden()) showAccordingToLayerModeOrFallback();
|
|
6392
6635
|
resolveOpenWebViewIfNeeded();
|
|
6393
6636
|
}
|
|
6394
6637
|
}
|
|
@@ -6554,7 +6797,11 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
6554
6797
|
|
|
6555
6798
|
@Override
|
|
6556
6799
|
public void dismiss() {
|
|
6557
|
-
|
|
6800
|
+
if (backCloseConfirmDialog != null) {
|
|
6801
|
+
backCloseConfirmDialog.dismiss();
|
|
6802
|
+
backCloseConfirmDialog = null;
|
|
6803
|
+
}
|
|
6804
|
+
clearFullscreen();
|
|
6558
6805
|
unregisterConfigurationCallbacks();
|
|
6559
6806
|
scheduleHostWebViewInsetRestore();
|
|
6560
6807
|
detachBackLayer();
|
|
@@ -6962,6 +7209,8 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
6962
7209
|
? Base64.encodeToString(httpBody.getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP)
|
|
6963
7210
|
: "";
|
|
6964
7211
|
|
|
7212
|
+
// View.post waits for attachment; deferred presentation waits for this first load to finish.
|
|
7213
|
+
// Dispatch through the main looper so hidden and present-after-load WebViews can start loading.
|
|
6965
7214
|
executorService.execute(() -> {
|
|
6966
7215
|
NativeRequestContext requestContext = new NativeRequestContext(
|
|
6967
7216
|
initialUrl,
|
|
@@ -6984,7 +7233,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
6984
7233
|
if (_webView == null) {
|
|
6985
7234
|
return;
|
|
6986
7235
|
}
|
|
6987
|
-
|
|
7236
|
+
mainHandler.post(() -> loadInitialUrlDirect(initialDirectHeaders, initialMethod, httpBody));
|
|
6988
7237
|
return;
|
|
6989
7238
|
}
|
|
6990
7239
|
|
|
@@ -7000,7 +7249,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
7000
7249
|
return;
|
|
7001
7250
|
}
|
|
7002
7251
|
|
|
7003
|
-
|
|
7252
|
+
mainHandler.post(() -> {
|
|
7004
7253
|
if (_webView == null) {
|
|
7005
7254
|
return;
|
|
7006
7255
|
}
|
|
@@ -7015,7 +7264,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
7015
7264
|
ProxyRequestSupport.createNativeRequestFailureBody(initialUrl, error),
|
|
7016
7265
|
StandardCharsets.UTF_8
|
|
7017
7266
|
);
|
|
7018
|
-
|
|
7267
|
+
mainHandler.post(() -> {
|
|
7019
7268
|
if (_webView == null) {
|
|
7020
7269
|
return;
|
|
7021
7270
|
}
|
|
@@ -7700,6 +7949,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
|
|
|
7700
7949
|
* Update dimensions at runtime
|
|
7701
7950
|
*/
|
|
7702
7951
|
public void updateDimensions(Integer width, Integer height, Integer x, Integer y) {
|
|
7952
|
+
clearFullscreen();
|
|
7703
7953
|
// Update options
|
|
7704
7954
|
if (width != null) {
|
|
7705
7955
|
_options.setWidth(width);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Google Material Icons: fullscreen_exit (Apache License 2.0). -->
|
|
3
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
4
|
+
android:width="24dp"
|
|
5
|
+
android:height="24dp"
|
|
6
|
+
android:viewportWidth="24"
|
|
7
|
+
android:viewportHeight="24">
|
|
8
|
+
<path android:fillColor="#FF000000"
|
|
9
|
+
android:pathData="M5,16h3v3h2v-5H5v2zM8,8H5v2h5V5H8v3zM14,19h2v-3h3v-2h-5v5zM16,8V5h-2v5h5V8h-3z" />
|
|
10
|
+
</vector>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- The native exit control stays physically top-right in every layout direction. -->
|
|
3
|
+
<com.google.android.material.button.MaterialButton
|
|
4
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
5
|
+
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
6
|
+
xmlns:tools="http://schemas.android.com/tools"
|
|
7
|
+
tools:ignore="RtlHardcoded"
|
|
8
|
+
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
|
|
9
|
+
android:layout_width="48dp"
|
|
10
|
+
android:layout_height="48dp"
|
|
11
|
+
android:layout_gravity="top|right"
|
|
12
|
+
android:layout_marginTop="@dimen/fullscreen_exit_margin"
|
|
13
|
+
android:layout_marginRight="@dimen/fullscreen_exit_margin"
|
|
14
|
+
android:contentDescription="@string/exit_fullscreen"
|
|
15
|
+
android:insetLeft="4dp"
|
|
16
|
+
android:insetTop="4dp"
|
|
17
|
+
android:insetRight="4dp"
|
|
18
|
+
android:insetBottom="4dp"
|
|
19
|
+
android:padding="12dp"
|
|
20
|
+
app:icon="@drawable/ic_fullscreen_exit"
|
|
21
|
+
app:iconGravity="textStart"
|
|
22
|
+
app:iconSize="24dp"
|
|
23
|
+
app:iconPadding="0dp"
|
|
24
|
+
app:shapeAppearance="@xml/fullscreen_exit_shape" />
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
<!-- With the button's 4dp visual inset, this leaves an 8dp gap inside the safe area. -->
|
|
4
|
+
<dimen name="fullscreen_exit_margin">4dp</dimen>
|
|
5
|
+
<style name="FullscreenExitShape" parent="ShapeAppearance.Material3.Corner.Full" />
|
|
6
|
+
<style name="FullscreenExitPressedShape" parent="">
|
|
7
|
+
<item name="cornerFamily">rounded</item>
|
|
8
|
+
<item name="cornerSize">8dp</item>
|
|
9
|
+
</style>
|
|
10
|
+
</resources>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<string name="my_string">Just a simple string</string>
|
|
4
4
|
<string name="title_activity_browser">BrowserActivity</string>
|
|
5
5
|
<string name="title_activity_basic">BasicActivity</string>
|
|
6
|
+
<string name="exit_fullscreen">Exit fullscreen</string>
|
|
6
7
|
<string name="close_button">Close Button</string>
|
|
7
8
|
<string name="button_near_done">Button near "done"</string>
|
|
8
9
|
<string name="reload_button">Reload Button</string>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
4
|
+
<item android:state_pressed="true" app:shapeAppearance="@style/FullscreenExitPressedShape" />
|
|
5
|
+
<item app:shapeAppearance="@style/FullscreenExitShape" />
|
|
6
|
+
</selector>
|