@capgo/capacitor-inappbrowser 8.7.9 → 8.8.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.
package/README.md CHANGED
@@ -148,6 +148,34 @@ await InAppBrowser.hide({ id: first.id });
148
148
  await InAppBrowser.show({ id: second.id });
149
149
  ```
150
150
 
151
+ #### Hide from the native close button and brand the toolbar title
152
+
153
+ Use `closeAction: CloseAction.HIDE` when the toolbar close button should hide the WebView instead of destroying it. Listen to `hideEvent` if your app needs to update Ionic state, then call `show()` later to bring the same browser session back.
154
+
155
+ ```js
156
+ import { CloseAction, InAppBrowser } from '@capgo/capacitor-inappbrowser';
157
+
158
+ await InAppBrowser.addListener('hideEvent', ({ id, url }) => {
159
+ console.log('Browser hidden', id, url);
160
+ });
161
+
162
+ const { id } = await InAppBrowser.openWebView({
163
+ url: 'https://example.com/checkout',
164
+ title: 'Secure checkout',
165
+ closeAction: CloseAction.HIDE,
166
+ titleFontFamily: 'Inter',
167
+ titleIcon: {
168
+ ios: { iconType: 'sf-symbol', icon: 'lock.fill' },
169
+ android: { iconType: 'vector', icon: 'ic_lock', width: 20, height: 20 },
170
+ },
171
+ });
172
+
173
+ // Later, after the toolbar close button hides it:
174
+ await InAppBrowser.show({ id });
175
+ ```
176
+
177
+ For iOS, `titleFontFamily` must match a registered font family name and `titleIcon` can use either an SF Symbol or bundled asset. For Android, `titleFontFamily` first checks `res/font` and then falls back to a system font family; `titleIcon` can use a vector drawable or bundled SVG asset.
178
+
151
179
  #### Embed advanced web app flows
152
180
 
153
181
  Use proxy rules and `handleDownloads` for web apps that need request control, file uploads, downloads, or controlled popup behavior inside the managed browser. Typical examples include document portals, support desks, payment pages, Google Pay flows on Android, and apps that need to keep `_blank` links inside the same managed WebView.
@@ -520,6 +548,7 @@ The W3C Payment Request API (used by Google Pay) requires Android WebView 120+.
520
548
  * [`addListener('urlChangeEvent', ...)`](#addlistenerurlchangeevent-)
521
549
  * [`addListener('buttonNearDoneClick', ...)`](#addlistenerbuttonneardoneclick-)
522
550
  * [`addListener('closeEvent', ...)`](#addlistenercloseevent-)
551
+ * [`addListener('hideEvent', ...)`](#addlistenerhideevent-)
523
552
  * [`addListener('confirmBtnClicked', ...)`](#addlistenerconfirmbtnclicked-)
524
553
  * [`addListener('messageFromWebview', ...)`](#addlistenermessagefromwebview-)
525
554
  * [`addListener('screenshotTaken', ...)`](#addlistenerscreenshottaken-)
@@ -943,6 +972,26 @@ Listen for close click only for openWebView
943
972
  --------------------
944
973
 
945
974
 
975
+ ### addListener('hideEvent', ...)
976
+
977
+ ```typescript
978
+ addListener(eventName: 'hideEvent', listenerFunc: UrlChangeListener) => Promise<PluginListenerHandle>
979
+ ```
980
+
981
+ Listen for webviews hidden by the toolbar close button when closeAction is <a href="#closeaction">CloseAction.HIDE</a>.
982
+
983
+ | Param | Type |
984
+ | ------------------ | --------------------------------------------------------------- |
985
+ | **`eventName`** | <code>'hideEvent'</code> |
986
+ | **`listenerFunc`** | <code><a href="#urlchangelistener">UrlChangeListener</a></code> |
987
+
988
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
989
+
990
+ **Since:** 8.7.7
991
+
992
+ --------------------
993
+
994
+
946
995
  ### addListener('confirmBtnClicked', ...)
947
996
 
948
997
  ```typescript
@@ -1430,6 +1479,8 @@ And in the AndroidManifest.xml file:
1430
1479
  | **`toolbarType`** | <code><a href="#toolbartype">ToolBarType</a></code> | Toolbar type determines the appearance and behavior of the browser's toolbar - "activity": Shows a simple toolbar with just a close button and share button - "navigation": Shows a full navigation toolbar with back/forward buttons - "blank": Shows no toolbar - "": Default toolbar with close button | <code>ToolBarType.DEFAULT</code> | 0.1.0 |
1431
1480
  | **`shareSubject`** | <code>string</code> | Subject text for sharing. Required when using shareDisclaimer. This text will be used as the subject line when sharing content. | | 0.1.0 |
1432
1481
  | **`title`** | <code>string</code> | Title of the browser | <code>"New Window"</code> | 0.1.0 |
1482
+ | **`titleFontFamily`** | <code>string</code> | Native toolbar title font family. On iOS, use the registered font family name. On Android, the plugin first tries a res/font resource name, then falls back to a system font family name. | | 8.7.7 |
1483
+ | **`titleIcon`** | <code><a href="#toolbartitleiconoptions">ToolbarTitleIconOptions</a></code> | Native toolbar title icon displayed before the title text. For Android: - iconType can be "asset" for a bundled SVG asset or "vector" for a drawable resource - icon path should be in the public folder for assets (e.g. "brand.svg") - width and height are optional and default to 24dp For iOS: - iconType can be "sf-symbol" or "asset" - for sf-symbol, icon should be the symbol name - for asset, icon should be the asset name or bundled web asset path | | 8.7.7 |
1433
1484
  | **`backgroundColor`** | <code><a href="#backgroundcolor">BackgroundColor</a></code> | Background color of the browser | <code>BackgroundColor.BLACK</code> | 0.1.0 |
1434
1485
  | **`activeNativeNavigationForWebview`** | <code>boolean</code> | If true, enables native navigation gestures within the webview. - Android: Native back button navigates within webview history - iOS: Enables swipe left/right gestures for back/forward navigation | <code>false (Android), true (iOS - enabled by default)</code> | |
1435
1486
  | **`disableGoBackOnNativeApplication`** | <code>boolean</code> | Disable the possibility to go back on native application, useful to force user to stay on the webview, Android only | <code>false</code> | |
@@ -1437,6 +1488,7 @@ And in the AndroidManifest.xml file:
1437
1488
  | **`isInspectable`** | <code>boolean</code> | Whether the website in the webview is inspectable or not, ios only | <code>false</code> | |
1438
1489
  | **`isAnimated`** | <code>boolean</code> | Whether the webview opening is animated or not, ios only | <code>true</code> | |
1439
1490
  | **`showReloadButton`** | <code>boolean</code> | Shows a reload button that reloads the web page | <code>false</code> | 1.0.15 |
1491
+ | **`closeAction`** | <code><a href="#closeaction">CloseAction</a></code> | closeAction controls what happens when the native toolbar close button is pressed. This does not change the behavior of close(), JavaScript window.mobileApp.close(), or native back navigation. | <code>CloseAction.CLOSE</code> | 8.7.7 |
1440
1492
  | **`closeModal`** | <code>boolean</code> | CloseModal: if true a confirm will be displayed when user clicks on close button, if false the browser will be closed immediately. | <code>false</code> | 1.1.0 |
1441
1493
  | **`closeModalTitle`** | <code>string</code> | CloseModalTitle: title of the confirm when user clicks on close button | <code>"Close"</code> | 1.1.0 |
1442
1494
  | **`closeModalDescription`** | <code>string</code> | CloseModalDescription: description of the confirm when user clicks on close button | <code>"Are you sure you want to close this window?"</code> | 1.1.0 |
@@ -1498,6 +1550,14 @@ And in the AndroidManifest.xml file:
1498
1550
  | **`cancelBtn`** | <code>string</code> | Text for the cancel button | <code>"Cancel"</code> |
1499
1551
 
1500
1552
 
1553
+ #### ToolbarTitleIconOptions
1554
+
1555
+ | Prop | Type |
1556
+ | ------------- | ---------------------------------------------------------------------------------------------- |
1557
+ | **`ios`** | <code>{ iconType: 'sf-symbol' \| 'asset'; icon: string; }</code> |
1558
+ | **`android`** | <code>{ iconType: 'asset' \| 'vector'; icon: string; width?: number; height?: number; }</code> |
1559
+
1560
+
1501
1561
  #### NativeProxyRule
1502
1562
 
1503
1563
  Native-first proxy rule used on Android and iOS.
@@ -1815,6 +1875,14 @@ Native handling mode used after a managed download finishes.
1815
1875
  | **`BLACK`** | <code>'black'</code> |
1816
1876
 
1817
1877
 
1878
+ #### CloseAction
1879
+
1880
+ | Members | Value | Description |
1881
+ | ----------- | -------------------- | -------------------------------------------------------------------- |
1882
+ | **`CLOSE`** | <code>'close'</code> | The toolbar close button closes and destroys the webview. |
1883
+ | **`HIDE`** | <code>'hide'</code> | The toolbar close button hides the webview so it can be shown again. |
1884
+
1885
+
1818
1886
  #### InvisibilityMode
1819
1887
 
1820
1888
  | Members | Value | Description |
@@ -48,6 +48,7 @@ import java.util.Deque;
48
48
  import java.util.HashMap;
49
49
  import java.util.Iterator;
50
50
  import java.util.List;
51
+ import java.util.Locale;
51
52
  import java.util.Map;
52
53
  import java.util.UUID;
53
54
  import java.util.concurrent.ExecutorService;
@@ -168,6 +169,11 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
168
169
  unregisterWebView(webViewId);
169
170
  }
170
171
 
172
+ @Override
173
+ public void hideEvent(String url) {
174
+ notifyListeners("hideEvent", new JSObject().put("id", webViewId).put("url", url));
175
+ }
176
+
171
177
  @Override
172
178
  public void pageLoaded() {
173
179
  notifyListeners("browserPageLoaded", new JSObject().put("id", webViewId));
@@ -1020,6 +1026,40 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
1020
1026
  options.setToolbarColor(call.getString("toolbarColor", "#ffffff"));
1021
1027
  options.setBackgroundColor(call.getString("backgroundColor", "white"));
1022
1028
  options.setToolbarTextColor(call.getString("toolbarTextColor"));
1029
+ String closeAction = call.getString("closeAction", "close").toLowerCase(Locale.ROOT);
1030
+ if (!"close".equals(closeAction) && !"hide".equals(closeAction)) {
1031
+ call.reject("closeAction must be 'close' or 'hide'");
1032
+ return;
1033
+ }
1034
+ options.setCloseAction(closeAction);
1035
+ options.setTitleFontFamily(call.getString("titleFontFamily"));
1036
+ JSObject titleIconObj = call.getObject("titleIcon");
1037
+ if (titleIconObj != null) {
1038
+ JSObject androidTitleIconObj = titleIconObj.getJSObject("android");
1039
+ if (androidTitleIconObj != null) {
1040
+ try {
1041
+ String titleIconType = androidTitleIconObj.getString("iconType", "asset");
1042
+ String titleIconName = androidTitleIconObj.getString("icon", "");
1043
+ if ("vector".equals(titleIconType)) {
1044
+ int resourceId = getContext()
1045
+ .getResources()
1046
+ .getIdentifier(titleIconName, "drawable", getContext().getPackageName());
1047
+ if (resourceId == 0) {
1048
+ Log.e("InAppBrowser", "Title icon vector resource not found: " + titleIconName);
1049
+ call.reject("titleIcon validation failed: vector resource not found: " + titleIconName);
1050
+ return;
1051
+ }
1052
+ }
1053
+ options.setTitleIcon(
1054
+ Options.ButtonNearDone.generateFromAndroidObject(androidTitleIconObj, getContext().getAssets(), "titleIcon.android")
1055
+ );
1056
+ } catch (Exception e) {
1057
+ Log.e("InAppBrowser", "Error setting titleIcon: " + e.getMessage(), e);
1058
+ call.reject("titleIcon validation failed: " + e.getMessage());
1059
+ return;
1060
+ }
1061
+ }
1062
+ }
1023
1063
  options.setArrow(Boolean.TRUE.equals(call.getBoolean("showArrow", false)));
1024
1064
  options.setIgnoreUntrustedSSLError(Boolean.TRUE.equals(call.getBoolean("ignoreUntrustedSSLError", false)));
1025
1065
  options.setClientCertificatePrompt(resolveClientCertificatePrompt(call));
@@ -57,7 +57,6 @@ public class Options {
57
57
  throws IllegalArgumentException, RuntimeException {
58
58
  JSObject buttonNearDone = call.getObject("buttonNearDone");
59
59
  if (buttonNearDone == null) {
60
- // Return null when "buttonNearDone" isn't configured, else throw an error
61
60
  return null;
62
61
  }
63
62
 
@@ -66,42 +65,41 @@ public class Options {
66
65
  throw new IllegalArgumentException("buttonNearDone.android is null");
67
66
  }
68
67
 
68
+ return generateFromAndroidObject(android, assetManager, "buttonNearDone.android");
69
+ }
70
+
71
+ public static ButtonNearDone generateFromAndroidObject(JSObject android, AssetManager assetManager, String optionName)
72
+ throws IllegalArgumentException, RuntimeException {
69
73
  String iconType = android.getString("iconType", "asset");
70
74
  AllIconTypes iconTypeEnum;
71
75
 
72
- // Validate and process icon type
73
76
  if ("asset".equals(iconType)) {
74
77
  iconTypeEnum = AllIconTypes.ASSET;
75
78
  } else if ("vector".equals(iconType)) {
76
79
  iconTypeEnum = AllIconTypes.VECTOR;
77
80
  } else {
78
- throw new IllegalArgumentException("buttonNearDone.android.iconType must be 'asset' or 'vector'");
81
+ throw new IllegalArgumentException(optionName + ".iconType must be 'asset' or 'vector'");
79
82
  }
80
83
 
81
84
  String icon = android.getString("icon");
82
85
  if (icon == null) {
83
- throw new IllegalArgumentException("buttonNearDone.android.icon is null");
86
+ throw new IllegalArgumentException(optionName + ".icon is null");
84
87
  }
85
88
 
86
- // For asset type, verify the file exists
87
89
  if (iconTypeEnum == AllIconTypes.ASSET) {
88
90
  InputStream fileInputString = null;
89
91
 
90
92
  try {
91
- // Try to find in public folder first
92
93
  try {
93
94
  fileInputString = assetManager.open("public/" + icon);
94
95
  } catch (IOException e) {
95
- // If not in public, try in root assets
96
96
  try {
97
97
  fileInputString = assetManager.open(icon);
98
98
  } catch (IOException e2) {
99
- throw new IllegalArgumentException("buttonNearDone.android.icon cannot be found in the assetManager");
99
+ throw new IllegalArgumentException(optionName + ".icon cannot be found in the assetManager", e2);
100
100
  }
101
101
  }
102
- // File exists, do nothing
103
102
  } finally {
104
- // Close the input stream if it was opened
105
103
  if (fileInputString != null) {
106
104
  try {
107
105
  fileInputString.close();
@@ -110,18 +108,14 @@ public class Options {
110
108
  }
111
109
  }
112
110
  }
113
- }
114
- // For vector type, we don't validate here since resources are checked at runtime
115
- else if (iconTypeEnum == AllIconTypes.VECTOR) {
116
- // Vector resources will be validated when used
111
+ } else if (iconTypeEnum == AllIconTypes.VECTOR) {
117
112
  System.out.println("Vector resource will be validated at runtime: " + icon);
118
113
  }
119
114
 
120
115
  Integer width = android.getInteger("width", 24);
121
116
  Integer height = android.getInteger("height", 24);
122
117
 
123
- final ButtonNearDone buttonNearDone1 = new ButtonNearDone(iconTypeEnum, iconType, icon, height, width);
124
- return buttonNearDone1;
118
+ return new ButtonNearDone(iconTypeEnum, iconType, icon, height, width);
125
119
  }
126
120
 
127
121
  public AllIconTypes getIconTypeEnum() {
@@ -151,6 +145,9 @@ public class Options {
151
145
  private String CloseModalDescription;
152
146
  private String CloseModalCancel;
153
147
  private ButtonNearDone buttonNearDone;
148
+ private String closeAction = "close";
149
+ private String titleFontFamily;
150
+ private ButtonNearDone titleIcon;
154
151
  private String CloseModalOk;
155
152
  private Pattern closeModalURLPattern;
156
153
  private String url;
@@ -418,6 +415,30 @@ public class Options {
418
415
  return this.buttonNearDone;
419
416
  }
420
417
 
418
+ public String getCloseAction() {
419
+ return closeAction;
420
+ }
421
+
422
+ public void setCloseAction(String closeAction) {
423
+ this.closeAction = closeAction;
424
+ }
425
+
426
+ public String getTitleFontFamily() {
427
+ return titleFontFamily;
428
+ }
429
+
430
+ public void setTitleFontFamily(String titleFontFamily) {
431
+ this.titleFontFamily = titleFontFamily;
432
+ }
433
+
434
+ public ButtonNearDone getTitleIcon() {
435
+ return titleIcon;
436
+ }
437
+
438
+ public void setTitleIcon(ButtonNearDone titleIcon) {
439
+ this.titleIcon = titleIcon;
440
+ }
441
+
421
442
  public String getCloseModalCancel() {
422
443
  return CloseModalCancel;
423
444
  }
@@ -713,6 +734,9 @@ public class Options {
713
734
  copy.setCloseModalOk(CloseModalOk);
714
735
  copy.setCloseModalURLPattern(closeModalURLPattern);
715
736
  copy.setButtonNearDone(buttonNearDone);
737
+ copy.setCloseAction(closeAction);
738
+ copy.setTitleFontFamily(titleFontFamily);
739
+ copy.setTitleIcon(titleIcon);
716
740
  copy.setUrl("about:blank");
717
741
  copy.setHeaders(headers);
718
742
  copy.setCredentials(credentials);
@@ -7,6 +7,8 @@ public interface WebViewCallbacks {
7
7
 
8
8
  public void closeEvent(String url);
9
9
 
10
+ public void hideEvent(String url);
11
+
10
12
  public void pageLoaded();
11
13
 
12
14
  public void pageLoadError();
@@ -16,6 +16,8 @@ import android.graphics.Color;
16
16
  import android.graphics.Paint;
17
17
  import android.graphics.PorterDuff;
18
18
  import android.graphics.PorterDuffColorFilter;
19
+ import android.graphics.Typeface;
20
+ import android.graphics.drawable.BitmapDrawable;
19
21
  import android.graphics.drawable.ColorDrawable;
20
22
  import android.graphics.drawable.Drawable;
21
23
  import android.net.Uri;
@@ -69,6 +71,7 @@ import androidx.activity.result.ActivityResult;
69
71
  import androidx.activity.result.contract.ActivityResultContracts;
70
72
  import androidx.coordinatorlayout.widget.CoordinatorLayout;
71
73
  import androidx.core.content.FileProvider;
74
+ import androidx.core.content.res.ResourcesCompat;
72
75
  import androidx.core.graphics.Insets;
73
76
  import androidx.core.view.ViewCompat;
74
77
  import androidx.core.view.WindowInsetsCompat;
@@ -344,6 +347,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
344
347
  private final Map<String, BlobDownloadSession> blobDownloadSessions = new ConcurrentHashMap<>();
345
348
  private final Map<String, ClientCertificateIdentity> clientCertificateIdentities = new ConcurrentHashMap<>();
346
349
  private int iconColor = Color.BLACK; // Default icon color
350
+ private Typeface cachedTitleTypeface;
351
+ private String cachedTitleFontFamily;
352
+ private Drawable cachedTitleIconDrawable;
353
+ private boolean cachedTitleIconResolved;
347
354
  private boolean isHiddenModeActive = false;
348
355
  private WindowManager.LayoutParams previousWindowAttributes;
349
356
  private Drawable previousWindowBackground;
@@ -3863,6 +3870,156 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3863
3870
  } else {
3864
3871
  textView.setText("");
3865
3872
  }
3873
+ applyTitleTextOptions(textView);
3874
+ }
3875
+
3876
+ private void performToolbarCloseAction(String currentUrl) {
3877
+ if (_options != null && "hide".equals(_options.getCloseAction())) {
3878
+ setHidden(true);
3879
+ if (_options.getCallbacks() != null) {
3880
+ _options.getCallbacks().hideEvent(currentUrl);
3881
+ }
3882
+ return;
3883
+ }
3884
+
3885
+ dismiss();
3886
+ if (_options != null && _options.getCallbacks() != null) {
3887
+ _options.getCallbacks().closeEvent(currentUrl);
3888
+ }
3889
+ }
3890
+
3891
+ private void applyTitleTextOptions(TextView titleText) {
3892
+ if (titleText == null || _options == null) {
3893
+ return;
3894
+ }
3895
+
3896
+ String fontFamily = _options.getTitleFontFamily();
3897
+ if (!TextUtils.isEmpty(fontFamily)) {
3898
+ if (!fontFamily.equals(cachedTitleFontFamily)) {
3899
+ cachedTitleFontFamily = fontFamily;
3900
+ cachedTitleTypeface = resolveTitleTypeface(fontFamily);
3901
+ }
3902
+ if (cachedTitleTypeface != null) {
3903
+ titleText.setTypeface(cachedTitleTypeface);
3904
+ }
3905
+ } else {
3906
+ cachedTitleFontFamily = null;
3907
+ cachedTitleTypeface = null;
3908
+ }
3909
+
3910
+ Options.ButtonNearDone titleIcon = _options.getTitleIcon();
3911
+ if (titleIcon == null) {
3912
+ titleText.setCompoundDrawablesRelative(null, null, null, null);
3913
+ cachedTitleIconDrawable = null;
3914
+ cachedTitleIconResolved = false;
3915
+ return;
3916
+ }
3917
+
3918
+ if (!cachedTitleIconResolved) {
3919
+ cachedTitleIconDrawable = loadTitleIconDrawable(titleIcon);
3920
+ cachedTitleIconResolved = true;
3921
+ }
3922
+
3923
+ if (cachedTitleIconDrawable != null) {
3924
+ titleText.setCompoundDrawablesRelative(cachedTitleIconDrawable, null, null, null);
3925
+ titleText.setCompoundDrawablePadding(Math.round(6 * _context.getResources().getDisplayMetrics().density));
3926
+ } else {
3927
+ titleText.setCompoundDrawablesRelative(null, null, null, null);
3928
+ }
3929
+ }
3930
+
3931
+ private Typeface resolveTitleTypeface(String fontFamily) {
3932
+ try {
3933
+ int fontResourceId = _context.getResources().getIdentifier(fontFamily, "font", _context.getPackageName());
3934
+ if (fontResourceId != 0) {
3935
+ Typeface typeface = ResourcesCompat.getFont(_context, fontResourceId);
3936
+ if (typeface != null) {
3937
+ return typeface;
3938
+ }
3939
+ }
3940
+ return Typeface.create(fontFamily, Typeface.NORMAL);
3941
+ } catch (Exception e) {
3942
+ Log.e("InAppBrowser", "Error loading title font: " + e.getMessage());
3943
+ return Typeface.create(fontFamily, Typeface.NORMAL);
3944
+ }
3945
+ }
3946
+
3947
+ private Drawable loadTitleIconDrawable(Options.ButtonNearDone titleIcon) {
3948
+ int width = Math.round(
3949
+ (titleIcon.getWidth() > 0 ? titleIcon.getWidth() : 24) * _context.getResources().getDisplayMetrics().density
3950
+ );
3951
+ int height = Math.round(
3952
+ (titleIcon.getHeight() > 0 ? titleIcon.getHeight() : 24) * _context.getResources().getDisplayMetrics().density
3953
+ );
3954
+
3955
+ if ("vector".equals(titleIcon.getIconType())) {
3956
+ try {
3957
+ String iconName = titleIcon.getIcon();
3958
+ if (iconName.endsWith(".xml")) {
3959
+ iconName = iconName.substring(0, iconName.length() - 4);
3960
+ }
3961
+ int resourceId = _context.getResources().getIdentifier(iconName, "drawable", _context.getPackageName());
3962
+ if (resourceId == 0) {
3963
+ Log.e("InAppBrowser", "Title icon vector drawable not found: " + iconName);
3964
+ return null;
3965
+ }
3966
+ Drawable drawable = ResourcesCompat.getDrawable(_context.getResources(), resourceId, _context.getTheme());
3967
+ if (drawable == null) {
3968
+ return null;
3969
+ }
3970
+ drawable = drawable.mutate();
3971
+ drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
3972
+ drawable.setBounds(0, 0, width, height);
3973
+ return drawable;
3974
+ } catch (Exception e) {
3975
+ Log.e("InAppBrowser", "Error loading title vector icon: " + e.getMessage());
3976
+ return null;
3977
+ }
3978
+ }
3979
+
3980
+ if ("asset".equals(titleIcon.getIconType())) {
3981
+ InputStream inputStream = null;
3982
+ try {
3983
+ AssetManager assetManager = _context.getAssets();
3984
+ try {
3985
+ inputStream = assetManager.open("public/" + titleIcon.getIcon());
3986
+ } catch (IOException e) {
3987
+ inputStream = assetManager.open(titleIcon.getIcon());
3988
+ }
3989
+
3990
+ SVG svg = SVG.getFromInputStream(inputStream);
3991
+ if (svg == null) {
3992
+ return null;
3993
+ }
3994
+
3995
+ svg.setDocumentWidth(width);
3996
+ svg.setDocumentHeight(height);
3997
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
3998
+ Canvas canvas = new Canvas(bitmap);
3999
+ svg.renderToCanvas(canvas);
4000
+
4001
+ Paint paint = new Paint();
4002
+ paint.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.SRC_IN));
4003
+ Canvas colorFilterCanvas = new Canvas(bitmap);
4004
+ colorFilterCanvas.drawBitmap(bitmap, 0, 0, paint);
4005
+
4006
+ BitmapDrawable drawable = new BitmapDrawable(_context.getResources(), bitmap);
4007
+ drawable.setBounds(0, 0, width, height);
4008
+ return drawable;
4009
+ } catch (IOException | SVGParseException e) {
4010
+ Log.e("InAppBrowser", "Error loading title asset icon: " + e.getMessage());
4011
+ } finally {
4012
+ if (inputStream != null) {
4013
+ try {
4014
+ inputStream.close();
4015
+ } catch (IOException e) {
4016
+ Log.e("InAppBrowser", "Error closing title icon stream: " + e.getMessage());
4017
+ }
4018
+ }
4019
+ }
4020
+ }
4021
+
4022
+ return null;
3866
4023
  }
3867
4024
 
3868
4025
  private void setupToolbar() {
@@ -3878,6 +4035,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3878
4035
  TextView titleText = _toolbar.findViewById(R.id.titleText);
3879
4036
 
3880
4037
  // Determine icon and text color
4038
+ cachedTitleIconDrawable = null;
4039
+ cachedTitleIconResolved = false;
4040
+
3881
4041
  int iconColor;
3882
4042
  if (_options.getToolbarTextColor() != null && !_options.getToolbarTextColor().isEmpty()) {
3883
4043
  try {
@@ -3938,30 +4098,21 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3938
4098
  _options.getCloseModalOk(),
3939
4099
  new OnClickListener() {
3940
4100
  public void onClick(DialogInterface dialog, int which) {
3941
- // Close button clicked, do something
3942
- dismiss();
3943
4101
  if (_options != null && _options.getCallbacks() != null) {
3944
- // Notify that confirm was clicked
3945
4102
  _options.getCallbacks().confirmBtnClicked(currentUrl);
3946
- _options.getCallbacks().closeEvent(currentUrl);
3947
4103
  }
4104
+ performToolbarCloseAction(currentUrl);
3948
4105
  }
3949
4106
  }
3950
4107
  )
3951
4108
  .setNegativeButton(_options.getCloseModalCancel(), null)
3952
4109
  .show();
3953
4110
  } else {
3954
- dismiss();
3955
- if (_options != null && _options.getCallbacks() != null) {
3956
- _options.getCallbacks().closeEvent(currentUrl);
3957
- }
4111
+ performToolbarCloseAction(currentUrl);
3958
4112
  }
3959
4113
  } else {
3960
4114
  String currentUrl = getUrl();
3961
- dismiss();
3962
- if (_options != null && _options.getCallbacks() != null) {
3963
- _options.getCallbacks().closeEvent(currentUrl);
3964
- }
4115
+ performToolbarCloseAction(currentUrl);
3965
4116
  }
3966
4117
  }
3967
4118
  }