@capgo/capacitor-inappbrowser 8.18.1 → 8.20.0

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.
@@ -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;
@@ -469,11 +622,24 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
469
622
  @Override
470
623
  protected void onStart() {
471
624
  super.onStart();
625
+ applyStartupFullscreen();
472
626
  // ComponentDialog registers its own back callback in onCreate; re-add ours last so
473
627
  // handleBrowserBackNavigation runs before the built-in cancel callback (LIFO order).
474
628
  ensureDialogBackHandlerOnTop();
475
629
  }
476
630
 
631
+ private void applyStartupFullscreen() {
632
+ if (_options != null && _options.isFullscreen() && !_options.isHidden()) {
633
+ _options.setFullscreen(false);
634
+ try {
635
+ setFullscreen(true);
636
+ } catch (IllegalStateException error) {
637
+ // Deferred presentation or page-initiated show may target an inactive WebView.
638
+ Log.w("InAppBrowser", "Skipping deferred fullscreen entry: " + error.getMessage());
639
+ }
640
+ }
641
+ }
642
+
477
643
  @Override
478
644
  public void show() {
479
645
  super.show();
@@ -698,6 +864,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
698
864
  }
699
865
 
700
866
  public boolean sendToBack(boolean transparentBackground) {
867
+ clearFullscreen();
701
868
  if (_options != null) {
702
869
  _options.setToBack(true);
703
870
  _options.setTransparentBackground(transparentBackground);
@@ -925,7 +1092,10 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
925
1092
  Log.e("InAppBrowser", "Cannot hide - activity is null");
926
1093
  return;
927
1094
  }
928
- activity.runOnUiThread(() -> setHidden(true));
1095
+ activity.runOnUiThread(() -> {
1096
+ clearFullscreen();
1097
+ setHidden(true);
1098
+ });
929
1099
  }
930
1100
 
931
1101
  @JavascriptInterface
@@ -2162,7 +2332,10 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
2162
2332
  setContentView(R.layout.activity_browser);
2163
2333
 
2164
2334
  // If custom dimensions are set, configure for touch passthrough
2165
- if (_options != null && (_options.getWidth() != null || _options.getHeight() != null)) {
2335
+ if (
2336
+ _options != null &&
2337
+ (_options.getWidth() != null || _options.getHeight() != null || _options.getX() != null || _options.getY() != null)
2338
+ ) {
2166
2339
  Window window = getWindow();
2167
2340
  if (window != null) {
2168
2341
  // Make the dialog background transparent
@@ -2850,9 +3023,9 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
2850
3023
  }
2851
3024
 
2852
3025
  try {
2853
- loadInitialWebViewContent(requestHeaders);
2854
3026
  setupToolbar();
2855
3027
  setWebViewClient();
3028
+ loadInitialWebViewContent(requestHeaders);
2856
3029
 
2857
3030
  if (this._options.isHidden()) {
2858
3031
  if (_options.isPopupWindowMode() || _options.getInvisibilityMode() == Options.InvisibilityMode.FAKE_VISIBLE) {
@@ -3002,6 +3175,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3002
3175
  if (activeForBackNavigation == active) {
3003
3176
  return;
3004
3177
  }
3178
+ if (!active && (isFullscreen() || customFullscreenView != null)) clearFullscreen();
3005
3179
  activeForBackNavigation = active;
3006
3180
  syncBackNavigationHandlers();
3007
3181
  }
@@ -3030,10 +3204,18 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3030
3204
  }
3031
3205
 
3032
3206
  private boolean shouldConsumeBackPress() {
3033
- return WebViewBackNavigationSupport.shouldConsumeBackPress(isShowing(), isHiddenModeActive, backLayerActive);
3207
+ return isFullscreen() || WebViewBackNavigationSupport.shouldConsumeBackPress(isShowing(), isHiddenModeActive, backLayerActive);
3034
3208
  }
3035
3209
 
3036
3210
  private void handleBrowserBackNavigation() {
3211
+ if (WebViewCustomFullscreenSupport.shouldConsumeBackPress(customFullscreenView != null)) {
3212
+ exitCustomFullscreenView();
3213
+ return;
3214
+ }
3215
+ if (isFullscreen()) {
3216
+ clearFullscreen();
3217
+ return;
3218
+ }
3037
3219
  if (_options == null) {
3038
3220
  dismiss();
3039
3221
  return;
@@ -3106,8 +3288,9 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3106
3288
  _webView.setVisibility(View.INVISIBLE);
3107
3289
  }
3108
3290
 
3109
- window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
3110
- WebViewCustomFullscreenSupport.enterImmersiveFullscreen(window, decorView);
3291
+ mediaFullscreenWindow = new FullscreenWindowState(window);
3292
+ mediaFullscreenWindow.enter();
3293
+ if (fullscreenExitButton != null) fullscreenExitButton.bringToFront();
3111
3294
 
3112
3295
  if (WebViewCustomFullscreenSupport.shouldRegisterHostBackHandler(backLayerActive)) {
3113
3296
  registerCustomFullscreenBackHandler();
@@ -3162,15 +3345,12 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3162
3345
  _webView.setVisibility(View.VISIBLE);
3163
3346
  }
3164
3347
 
3165
- Window window = customFullscreenWindow != null ? customFullscreenWindow : getWindow();
3166
- if (window != null) {
3167
- window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
3168
- WebViewCustomFullscreenSupport.exitImmersiveFullscreen(window, window.getDecorView());
3169
- reapplyInsetsFromWindowRoot();
3170
- if (SystemUiChromeSupport.requiresEdgeToEdgeChrome(Build.VERSION.SDK_INT)) {
3171
- refreshEdgeToEdgeChrome();
3172
- }
3348
+ if (mediaFullscreenWindow != null) {
3349
+ mediaFullscreenWindow.restore();
3350
+ mediaFullscreenWindow = null;
3173
3351
  }
3352
+ if (nativeFullscreenWindow != null) nativeFullscreenWindow.enter();
3353
+ else reapplyInsetsFromWindowRoot();
3174
3354
 
3175
3355
  unregisterCustomFullscreenBackHandler();
3176
3356
 
@@ -3275,6 +3455,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3275
3455
  }
3276
3456
 
3277
3457
  public void setHidden(boolean hidden) {
3458
+ if (hidden && (isFullscreen() || (_options != null && !_options.isHidden()))) clearFullscreen();
3278
3459
  if (hidden) {
3279
3460
  if (!isHiddenModeActive) {
3280
3461
  if (getWindow() == null) {
@@ -3314,6 +3495,9 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3314
3495
  }
3315
3496
  if (_options != null) {
3316
3497
  _options.setHidden(hidden);
3498
+ if (isShowing()) {
3499
+ applyStartupFullscreen();
3500
+ }
3317
3501
  }
3318
3502
  }
3319
3503
 
@@ -3508,6 +3692,11 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3508
3692
  * after portrait↔landscape transitions.
3509
3693
  */
3510
3694
  private void refreshLayoutForConfigurationChange() {
3695
+ if (isFullscreen()) {
3696
+ nativeFullscreenWindow.enter();
3697
+ updateFullscreenExitInsets(ViewCompat.getRootWindowInsets(getWindow().getDecorView()));
3698
+ return;
3699
+ }
3511
3700
  if (isDismissing || _webView == null) {
3512
3701
  return;
3513
3702
  }
@@ -3684,6 +3873,18 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
3684
3873
  return;
3685
3874
  }
3686
3875
 
3876
+ if (isFullscreen()) {
3877
+ updateFullscreenExitInsets(windowInsets);
3878
+ View container = findBrowserContentDescendant(R.id.content_browser_layout);
3879
+ if (container != null) {
3880
+ // FLAG_FULLSCREEN disables adjustResize even before Android 15; keep only the keyboard inset.
3881
+ int imeBottom = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
3882
+ ? windowInsets.getInsets(WindowInsetsCompat.Type.ime()).bottom
3883
+ : 0;
3884
+ container.setPadding(0, 0, 0, imeBottom);
3885
+ }
3886
+ return;
3887
+ }
3687
3888
  Insets bars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
3688
3889
  Insets navigationBars = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars());
3689
3890
  Insets systemGestures = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures());
@@ -6316,9 +6517,20 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
6316
6517
  super.onLoadResource(view, url);
6317
6518
  }
6318
6519
 
6520
+ @Override
6521
+ public boolean onRenderProcessGone(WebView view, android.webkit.RenderProcessGoneDetail detail) {
6522
+ clearFullscreen();
6523
+ dismiss();
6524
+ return true;
6525
+ }
6526
+
6319
6527
  @Override
6320
6528
  public void onPageStarted(WebView view, String url, Bitmap favicon) {
6321
6529
  super.onPageStarted(view, url, favicon);
6530
+ if (
6531
+ (isFullscreen() && !java.util.Objects.equals(fullscreenOrigin, origin(url))) ||
6532
+ (_options.isFullscreen() && !java.util.Objects.equals(origin(_options.getUrl()), origin(url)))
6533
+ ) clearFullscreen();
6322
6534
  if (view == null || _webView == null) {
6323
6535
  return;
6324
6536
  }
@@ -6361,13 +6573,13 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
6361
6573
  @Override
6362
6574
  public void onPageFinished(WebView view, String url) {
6363
6575
  super.onPageFinished(view, url);
6364
- if (view == null || _webView == null) {
6576
+ if (view == null || _webView != view || isDismissing) {
6365
6577
  return;
6366
6578
  }
6367
6579
  if (!isInitialized) {
6368
6580
  isInitialized = true;
6369
6581
  _webView.clearHistory();
6370
- if (_options.isPresentAfterPageLoad()) {
6582
+ if (_options.isPresentAfterPageLoad() && !_options.isHidden()) {
6371
6583
  boolean usePreShowScript =
6372
6584
  _options.getPreShowScript() != null &&
6373
6585
  !_options.getPreShowScript().isEmpty() &&
@@ -6388,7 +6600,9 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
6388
6600
  new Runnable() {
6389
6601
  @Override
6390
6602
  public void run() {
6391
- showAccordingToLayerModeOrFallback();
6603
+ if (isDismissing || _webView != view) return;
6604
+ // Visibility can change while the pre-show script is running.
6605
+ if (!_options.isHidden()) showAccordingToLayerModeOrFallback();
6392
6606
  resolveOpenWebViewIfNeeded();
6393
6607
  }
6394
6608
  }
@@ -6554,7 +6768,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
6554
6768
 
6555
6769
  @Override
6556
6770
  public void dismiss() {
6557
- exitCustomFullscreenView();
6771
+ clearFullscreen();
6558
6772
  unregisterConfigurationCallbacks();
6559
6773
  scheduleHostWebViewInsetRestore();
6560
6774
  detachBackLayer();
@@ -6962,6 +7176,8 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
6962
7176
  ? Base64.encodeToString(httpBody.getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP)
6963
7177
  : "";
6964
7178
 
7179
+ // View.post waits for attachment; deferred presentation waits for this first load to finish.
7180
+ // Dispatch through the main looper so hidden and present-after-load WebViews can start loading.
6965
7181
  executorService.execute(() -> {
6966
7182
  NativeRequestContext requestContext = new NativeRequestContext(
6967
7183
  initialUrl,
@@ -6984,7 +7200,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
6984
7200
  if (_webView == null) {
6985
7201
  return;
6986
7202
  }
6987
- _webView.post(() -> loadInitialUrlDirect(initialDirectHeaders, initialMethod, httpBody));
7203
+ mainHandler.post(() -> loadInitialUrlDirect(initialDirectHeaders, initialMethod, httpBody));
6988
7204
  return;
6989
7205
  }
6990
7206
 
@@ -7000,7 +7216,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
7000
7216
  return;
7001
7217
  }
7002
7218
 
7003
- _webView.post(() -> {
7219
+ mainHandler.post(() -> {
7004
7220
  if (_webView == null) {
7005
7221
  return;
7006
7222
  }
@@ -7015,7 +7231,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
7015
7231
  ProxyRequestSupport.createNativeRequestFailureBody(initialUrl, error),
7016
7232
  StandardCharsets.UTF_8
7017
7233
  );
7018
- _webView.post(() -> {
7234
+ mainHandler.post(() -> {
7019
7235
  if (_webView == null) {
7020
7236
  return;
7021
7237
  }
@@ -7700,6 +7916,7 @@ public class WebViewDialog extends ComponentDialog implements ProxyResponseRouti
7700
7916
  * Update dimensions at runtime
7701
7917
  */
7702
7918
  public void updateDimensions(Integer width, Integer height, Integer x, Integer y) {
7919
+ clearFullscreen();
7703
7920
  // Update options
7704
7921
  if (width != null) {
7705
7922
  _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>