@capgo/inappbrowser 7.3.0 → 7.4.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.
Files changed (29) hide show
  1. package/README.md +47 -41
  2. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java +149 -41
  3. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java +65 -22
  4. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewCallbacks.java +2 -0
  5. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +660 -102
  6. package/android/src/main/res/drawable/ic_share.xml +10 -0
  7. package/android/src/main/res/layout/activity_browser.xml +8 -0
  8. package/android/src/main/res/layout/tool_bar.xml +19 -7
  9. package/android/src/main/res/values/strings.xml +2 -0
  10. package/dist/docs.json +169 -35
  11. package/dist/esm/definitions.d.ts +222 -29
  12. package/dist/esm/definitions.js +12 -1
  13. package/dist/esm/definitions.js.map +1 -1
  14. package/dist/plugin.cjs.js +12 -1
  15. package/dist/plugin.cjs.js.map +1 -1
  16. package/dist/plugin.js +12 -1
  17. package/dist/plugin.js.map +1 -1
  18. package/ios/Plugin/InAppBrowserPlugin.swift +339 -44
  19. package/ios/Plugin/WKWebViewController.swift +478 -45
  20. package/package.json +1 -1
  21. package/ios/Plugin/Assets.xcassets/Back.imageset/Back.png +0 -0
  22. package/ios/Plugin/Assets.xcassets/Back.imageset/Back@2x.png +0 -0
  23. package/ios/Plugin/Assets.xcassets/Back.imageset/Back@3x.png +0 -0
  24. package/ios/Plugin/Assets.xcassets/Back.imageset/Contents.json +0 -26
  25. package/ios/Plugin/Assets.xcassets/Contents.json +0 -6
  26. package/ios/Plugin/Assets.xcassets/Forward.imageset/Contents.json +0 -26
  27. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward.png +0 -0
  28. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward@2x.png +0 -0
  29. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward@3x.png +0 -0
@@ -9,15 +9,21 @@ import android.content.Context;
9
9
  import android.content.DialogInterface;
10
10
  import android.content.Intent;
11
11
  import android.content.res.AssetManager;
12
+ import android.content.res.Resources;
12
13
  import android.graphics.Bitmap;
14
+ import android.graphics.Canvas;
13
15
  import android.graphics.Color;
16
+ import android.graphics.Paint;
14
17
  import android.graphics.Picture;
18
+ import android.graphics.PorterDuff;
19
+ import android.graphics.PorterDuffColorFilter;
15
20
  import android.graphics.drawable.PictureDrawable;
16
21
  import android.net.Uri;
17
22
  import android.net.http.SslError;
18
23
  import android.text.TextUtils;
19
24
  import android.util.Base64;
20
25
  import android.util.Log;
26
+ import android.util.TypedValue;
21
27
  import android.view.View;
22
28
  import android.view.Window;
23
29
  import android.view.WindowManager;
@@ -33,6 +39,7 @@ import android.webkit.WebResourceResponse;
33
39
  import android.webkit.WebView;
34
40
  import android.webkit.WebViewClient;
35
41
  import android.widget.ImageButton;
42
+ import android.widget.ImageView;
36
43
  import android.widget.TextView;
37
44
  import android.widget.Toast;
38
45
  import android.widget.Toolbar;
@@ -89,6 +96,7 @@ public class WebViewDialog extends Dialog {
89
96
  new HashMap<>();
90
97
  private final ExecutorService executorService =
91
98
  Executors.newCachedThreadPool();
99
+ private int iconColor = Color.BLACK; // Default icon color
92
100
 
93
101
  Semaphore preShowSemaphore = null;
94
102
  String preShowError = null;
@@ -145,13 +153,10 @@ public class WebViewDialog extends Dialog {
145
153
  if (activity != null) {
146
154
  activity.runOnUiThread(() -> {
147
155
  try {
156
+ String currentUrl = _webView != null ? _webView.getUrl() : "";
148
157
  dismiss();
149
- if (_webView != null) {
150
- String currentUrl = _webView.getUrl();
151
- if (_options != null && _options.getCallbacks() != null) {
152
- _options.getCallbacks().closeEvent(currentUrl);
153
- }
154
- _webView.destroy();
158
+ if (_options != null && _options.getCallbacks() != null) {
159
+ _options.getCallbacks().closeEvent(currentUrl);
155
160
  }
156
161
  } catch (Exception e) {
157
162
  Log.e("InAppBrowser", "Error closing WebView: " + e.getMessage());
@@ -203,14 +208,78 @@ public class WebViewDialog extends Dialog {
203
208
  setContentView(R.layout.activity_browser);
204
209
  getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
205
210
 
211
+ // Make status bar transparent
212
+ if (getWindow() != null) {
213
+ getWindow().setStatusBarColor(Color.TRANSPARENT);
214
+
215
+ // Add FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag to the window
216
+ getWindow()
217
+ .addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
218
+
219
+ // On Android 30+ clear FLAG_TRANSLUCENT_STATUS flag
220
+ getWindow()
221
+ .clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
222
+ }
223
+
206
224
  WindowInsetsControllerCompat insetsController =
207
- new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView());
208
- insetsController.setAppearanceLightStatusBars(false);
209
- getWindow()
210
- .getDecorView()
211
- .post(() -> {
212
- getWindow().setStatusBarColor(Color.BLACK);
213
- });
225
+ new WindowInsetsControllerCompat(
226
+ getWindow(),
227
+ getWindow() != null ? getWindow().getDecorView() : null
228
+ );
229
+
230
+ if (getWindow() != null) {
231
+ getWindow()
232
+ .getDecorView()
233
+ .post(() -> {
234
+ // Get status bar height
235
+ int statusBarHeight = 0;
236
+ int resourceId = getContext()
237
+ .getResources()
238
+ .getIdentifier("status_bar_height", "dimen", "android");
239
+ if (resourceId > 0) {
240
+ statusBarHeight = getContext()
241
+ .getResources()
242
+ .getDimensionPixelSize(resourceId);
243
+ }
244
+
245
+ // Find the status bar color view
246
+ View statusBarColorView = findViewById(R.id.status_bar_color_view);
247
+
248
+ // Set the height of the status bar color view
249
+ if (statusBarColorView != null) {
250
+ statusBarColorView.getLayoutParams().height = statusBarHeight;
251
+ statusBarColorView.requestLayout();
252
+
253
+ // Set color based on toolbar color or dark mode
254
+ if (
255
+ _options.getToolbarColor() != null &&
256
+ !_options.getToolbarColor().isEmpty()
257
+ ) {
258
+ try {
259
+ // Use explicitly provided toolbar color for status bar
260
+ int toolbarColor = Color.parseColor(_options.getToolbarColor());
261
+ statusBarColorView.setBackgroundColor(toolbarColor);
262
+
263
+ // Set status bar text to white or black based on background
264
+ boolean isDarkBackground = isDarkColor(toolbarColor);
265
+ insetsController.setAppearanceLightStatusBars(
266
+ !isDarkBackground
267
+ );
268
+ } catch (IllegalArgumentException e) {
269
+ // Fallback to default black if color parsing fails
270
+ statusBarColorView.setBackgroundColor(Color.BLACK);
271
+ insetsController.setAppearanceLightStatusBars(false);
272
+ }
273
+ } else {
274
+ // Follow system dark mode if no toolbar color provided
275
+ boolean isDarkTheme = isDarkThemeEnabled();
276
+ int statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
277
+ statusBarColorView.setBackgroundColor(statusBarColor);
278
+ insetsController.setAppearanceLightStatusBars(!isDarkTheme);
279
+ }
280
+ }
281
+ });
282
+ }
214
283
 
215
284
  getWindow()
216
285
  .setLayout(
@@ -249,9 +318,25 @@ public class WebViewDialog extends Dialog {
249
318
  ValueCallback<Uri[]> filePathCallback,
250
319
  FileChooserParams fileChooserParams
251
320
  ) {
321
+ // Get the accept type safely
322
+ String acceptType = "*/*"; // Default to all file types
323
+ if (
324
+ fileChooserParams.getAcceptTypes() != null &&
325
+ fileChooserParams.getAcceptTypes().length > 0 &&
326
+ !TextUtils.isEmpty(fileChooserParams.getAcceptTypes()[0])
327
+ ) {
328
+ acceptType = fileChooserParams.getAcceptTypes()[0];
329
+ }
330
+
331
+ // Check if the file chooser is already open
332
+ if (mFilePathCallback != null) {
333
+ mFilePathCallback.onReceiveValue(null);
334
+ mFilePathCallback = null;
335
+ }
336
+
252
337
  openFileChooser(
253
338
  filePathCallback,
254
- fileChooserParams.getAcceptTypes()[0],
339
+ acceptType,
255
340
  fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE
256
341
  );
257
342
  return true;
@@ -436,16 +521,100 @@ public class WebViewDialog extends Dialog {
436
521
  mFilePathCallback = filePathCallback;
437
522
  Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
438
523
  intent.addCategory(Intent.CATEGORY_OPENABLE);
439
- intent.setType(acceptType); // Default to */*
524
+
525
+ // Fix MIME type handling
526
+ if (
527
+ acceptType == null ||
528
+ acceptType.isEmpty() ||
529
+ acceptType.equals("undefined")
530
+ ) {
531
+ acceptType = "*/*";
532
+ } else {
533
+ // Handle common web input accept types
534
+ if (acceptType.equals("image/*")) {
535
+ // Keep as is - image/*
536
+ } else if (acceptType.contains("image/")) {
537
+ // Specific image type requested but keep it general for better compatibility
538
+ acceptType = "image/*";
539
+ } else if (
540
+ acceptType.equals("audio/*") || acceptType.contains("audio/")
541
+ ) {
542
+ acceptType = "audio/*";
543
+ } else if (
544
+ acceptType.equals("video/*") || acceptType.contains("video/")
545
+ ) {
546
+ acceptType = "video/*";
547
+ } else if (acceptType.startsWith(".") || acceptType.contains(",")) {
548
+ // Handle file extensions like ".pdf, .docx" by using a general mime type
549
+ if (acceptType.contains(".pdf")) {
550
+ acceptType = "application/pdf";
551
+ } else if (
552
+ acceptType.contains(".doc") || acceptType.contains(".docx")
553
+ ) {
554
+ acceptType = "application/msword";
555
+ } else if (
556
+ acceptType.contains(".xls") || acceptType.contains(".xlsx")
557
+ ) {
558
+ acceptType = "application/vnd.ms-excel";
559
+ } else if (
560
+ acceptType.contains(".txt") || acceptType.contains(".text")
561
+ ) {
562
+ acceptType = "text/plain";
563
+ } else {
564
+ // Default for extension lists
565
+ acceptType = "*/*";
566
+ }
567
+ }
568
+ }
569
+
570
+ Log.d("InAppBrowser", "File picker using MIME type: " + acceptType);
571
+ intent.setType(acceptType);
440
572
  intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultiple);
441
- activity.startActivityForResult(
442
- Intent.createChooser(intent, "Select File"),
443
- FILE_CHOOSER_REQUEST_CODE
444
- );
573
+
574
+ try {
575
+ activity.startActivityForResult(
576
+ Intent.createChooser(intent, "Select File"),
577
+ FILE_CHOOSER_REQUEST_CODE
578
+ );
579
+ } catch (ActivityNotFoundException e) {
580
+ // If no app can handle the specific MIME type, try with a more generic one
581
+ Log.e(
582
+ "InAppBrowser",
583
+ "No app available for type: " + acceptType + ", trying with */*"
584
+ );
585
+ intent.setType("*/*");
586
+ try {
587
+ activity.startActivityForResult(
588
+ Intent.createChooser(intent, "Select File"),
589
+ FILE_CHOOSER_REQUEST_CODE
590
+ );
591
+ } catch (ActivityNotFoundException ex) {
592
+ // If still failing, report error
593
+ Log.e("InAppBrowser", "No app can handle file picker", ex);
594
+ if (mFilePathCallback != null) {
595
+ mFilePathCallback.onReceiveValue(null);
596
+ mFilePathCallback = null;
597
+ }
598
+ }
599
+ }
445
600
  }
446
601
 
447
602
  public void reload() {
448
- _webView.reload();
603
+ if (_webView != null) {
604
+ // First stop any ongoing loading
605
+ _webView.stopLoading();
606
+
607
+ // Check if there's a URL to reload
608
+ if (_webView.getUrl() != null) {
609
+ // Reload the current page
610
+ _webView.reload();
611
+ Log.d("InAppBrowser", "Reloading page: " + _webView.getUrl());
612
+ } else if (_options != null && _options.getUrl() != null) {
613
+ // If webView URL is null but we have an initial URL, load that
614
+ setUrl(_options.getUrl());
615
+ Log.d("InAppBrowser", "Loading initial URL: " + _options.getUrl());
616
+ }
617
+ }
449
618
  }
450
619
 
451
620
  public void destroy() {
@@ -488,56 +657,72 @@ public class WebViewDialog extends Dialog {
488
657
  }
489
658
 
490
659
  private void setupToolbar() {
491
- _toolbar = this.findViewById(R.id.tool_bar);
492
- int color = Color.parseColor("#ffffff");
493
- try {
494
- color = Color.parseColor(_options.getToolbarColor());
495
- } catch (IllegalArgumentException e) {
496
- // Do nothing
497
- }
498
- _toolbar.setBackgroundColor(color);
499
- _toolbar.findViewById(R.id.backButton).setBackgroundColor(color);
500
- _toolbar.findViewById(R.id.forwardButton).setBackgroundColor(color);
501
- _toolbar.findViewById(R.id.closeButton).setBackgroundColor(color);
502
- _toolbar.findViewById(R.id.reloadButton).setBackgroundColor(color);
503
-
504
- if (!TextUtils.isEmpty(_options.getTitle())) {
505
- this.setTitle(_options.getTitle());
506
- } else {
660
+ _toolbar = findViewById(R.id.tool_bar);
661
+
662
+ // Apply toolbar color early, for ALL toolbar types, before any view configuration
663
+ if (
664
+ _options.getToolbarColor() != null &&
665
+ !_options.getToolbarColor().isEmpty()
666
+ ) {
507
667
  try {
508
- URI uri = new URI(_options.getUrl());
509
- this.setTitle(uri.getHost());
510
- } catch (URISyntaxException e) {
511
- this.setTitle(_options.getTitle());
512
- }
513
- }
668
+ int toolbarColor = Color.parseColor(_options.getToolbarColor());
669
+ _toolbar.setBackgroundColor(toolbarColor);
514
670
 
515
- View backButton = _toolbar.findViewById(R.id.backButton);
516
- backButton.setOnClickListener(
517
- new View.OnClickListener() {
518
- @Override
519
- public void onClick(View view) {
520
- if (_webView.canGoBack()) {
521
- _webView.goBack();
671
+ // Get toolbar title and ensure it gets the right color
672
+ TextView titleText = _toolbar.findViewById(R.id.titleText);
673
+
674
+ // Determine icon and text color
675
+ int iconColor;
676
+ if (
677
+ _options.getToolbarTextColor() != null &&
678
+ !_options.getToolbarTextColor().isEmpty()
679
+ ) {
680
+ try {
681
+ iconColor = Color.parseColor(_options.getToolbarTextColor());
682
+ } catch (IllegalArgumentException e) {
683
+ // Fallback to automatic detection if parsing fails
684
+ boolean isDarkBackground = isDarkColor(toolbarColor);
685
+ iconColor = isDarkBackground ? Color.WHITE : Color.BLACK;
522
686
  }
687
+ } else {
688
+ // No explicit toolbarTextColor, use automatic detection based on background
689
+ boolean isDarkBackground = isDarkColor(toolbarColor);
690
+ iconColor = isDarkBackground ? Color.WHITE : Color.BLACK;
523
691
  }
524
- }
525
- );
526
692
 
527
- View forwardButton = _toolbar.findViewById(R.id.forwardButton);
528
- forwardButton.setOnClickListener(
529
- new View.OnClickListener() {
530
- @Override
531
- public void onClick(View view) {
532
- if (_webView.canGoForward()) {
533
- _webView.goForward();
534
- }
693
+ // Store for later use with navigation buttons
694
+ this.iconColor = iconColor;
695
+
696
+ // Set title text color directly
697
+ titleText.setTextColor(iconColor);
698
+
699
+ // Apply colors to all buttons
700
+ applyColorToAllButtons(toolbarColor, iconColor);
701
+
702
+ // Also ensure status bar gets the color
703
+ if (getWindow() != null) {
704
+ // Set status bar color
705
+ getWindow().setStatusBarColor(toolbarColor);
706
+
707
+ // Determine proper status bar text color (light or dark icons)
708
+ boolean isDarkBackground = isDarkColor(toolbarColor);
709
+ WindowInsetsControllerCompat insetsController =
710
+ new WindowInsetsControllerCompat(
711
+ getWindow(),
712
+ getWindow().getDecorView()
713
+ );
714
+ insetsController.setAppearanceLightStatusBars(!isDarkBackground);
535
715
  }
716
+ } catch (IllegalArgumentException e) {
717
+ Log.e(
718
+ "InAppBrowser",
719
+ "Invalid toolbar color: " + _options.getToolbarColor()
720
+ );
536
721
  }
537
- );
722
+ }
538
723
 
539
- ImageButton closeButton = _toolbar.findViewById(R.id.closeButton);
540
- closeButton.setOnClickListener(
724
+ ImageButton closeButtonView = _toolbar.findViewById(R.id.closeButton);
725
+ closeButtonView.setOnClickListener(
541
726
  new View.OnClickListener() {
542
727
  @Override
543
728
  public void onClick(View view) {
@@ -551,96 +736,316 @@ public class WebViewDialog extends Dialog {
551
736
  new OnClickListener() {
552
737
  public void onClick(DialogInterface dialog, int which) {
553
738
  // Close button clicked, do something
739
+ String currentUrl = _webView != null
740
+ ? _webView.getUrl()
741
+ : "";
554
742
  dismiss();
555
- _options.getCallbacks().closeEvent(_webView.getUrl());
556
- _webView.destroy();
743
+ if (_options != null && _options.getCallbacks() != null) {
744
+ _options.getCallbacks().closeEvent(currentUrl);
745
+ }
557
746
  }
558
747
  }
559
748
  )
560
749
  .setNegativeButton(_options.getCloseModalCancel(), null)
561
750
  .show();
562
751
  } else {
752
+ String currentUrl = _webView != null ? _webView.getUrl() : "";
563
753
  dismiss();
564
- _options.getCallbacks().closeEvent(_webView.getUrl());
565
- _webView.destroy();
754
+ if (_options != null && _options.getCallbacks() != null) {
755
+ _options.getCallbacks().closeEvent(currentUrl);
756
+ }
566
757
  }
567
758
  }
568
759
  }
569
760
  );
570
761
 
571
762
  if (_options.showArrow()) {
572
- closeButton.setImageResource(R.drawable.arrow_back_enabled);
763
+ closeButtonView.setImageResource(R.drawable.arrow_back_enabled);
573
764
  }
574
765
 
575
- if (_options.getShowReloadButton()) {
576
- View reloadButton = _toolbar.findViewById(R.id.reloadButton);
577
- reloadButton.setVisibility(View.VISIBLE);
578
- reloadButton.setOnClickListener(
766
+ // Handle reload button visibility
767
+ if (
768
+ _options.getShowReloadButton() &&
769
+ !TextUtils.equals(_options.getToolbarType(), "activity")
770
+ ) {
771
+ View reloadButtonView = _toolbar.findViewById(R.id.reloadButton);
772
+ reloadButtonView.setVisibility(View.VISIBLE);
773
+ reloadButtonView.setOnClickListener(
579
774
  new View.OnClickListener() {
580
775
  @Override
581
776
  public void onClick(View view) {
582
- _webView.reload();
777
+ if (_webView != null) {
778
+ // First stop any ongoing loading
779
+ _webView.stopLoading();
780
+
781
+ // Check if there's a URL to reload
782
+ if (_webView.getUrl() != null) {
783
+ // Reload the current page
784
+ _webView.reload();
785
+ Log.d("InAppBrowser", "Reloading page: " + _webView.getUrl());
786
+ } else if (_options.getUrl() != null) {
787
+ // If webView URL is null but we have an initial URL, load that
788
+ setUrl(_options.getUrl());
789
+ Log.d(
790
+ "InAppBrowser",
791
+ "Loading initial URL: " + _options.getUrl()
792
+ );
793
+ }
794
+ }
583
795
  }
584
796
  }
585
797
  );
798
+ } else {
799
+ View reloadButtonView = _toolbar.findViewById(R.id.reloadButton);
800
+ reloadButtonView.setVisibility(View.GONE);
586
801
  }
587
802
 
588
803
  if (TextUtils.equals(_options.getToolbarType(), "activity")) {
804
+ // Activity mode should ONLY have:
805
+ // 1. Close button
806
+ // 2. Share button (if shareSubject is provided)
807
+
808
+ // Hide all navigation buttons
589
809
  _toolbar.findViewById(R.id.forwardButton).setVisibility(View.GONE);
590
810
  _toolbar.findViewById(R.id.backButton).setVisibility(View.GONE);
811
+
812
+ // Hide buttonNearDone
591
813
  ImageButton buttonNearDoneView = _toolbar.findViewById(
592
814
  R.id.buttonNearDone
593
815
  );
594
816
  buttonNearDoneView.setVisibility(View.GONE);
595
- //TODO: Add share button functionality
817
+
818
+ // In activity mode, always make the share button visible by setting a default shareSubject if not provided
819
+ if (
820
+ _options.getShareSubject() == null ||
821
+ _options.getShareSubject().isEmpty()
822
+ ) {
823
+ _options.setShareSubject("Share");
824
+ Log.d("InAppBrowser", "Activity mode: Setting default shareSubject");
825
+ }
826
+ // Status bar color is already set at the top of this method, no need to set again
827
+
828
+ // Share button visibility is handled separately later
596
829
  } else if (TextUtils.equals(_options.getToolbarType(), "navigation")) {
597
830
  ImageButton buttonNearDoneView = _toolbar.findViewById(
598
831
  R.id.buttonNearDone
599
832
  );
600
833
  buttonNearDoneView.setVisibility(View.GONE);
601
- //TODO: Remove share button when implemented
834
+ // Status bar color is already set at the top of this method, no need to set again
602
835
  } else if (TextUtils.equals(_options.getToolbarType(), "blank")) {
603
836
  _toolbar.setVisibility(View.GONE);
837
+
838
+ // Also set window background color to match status bar for blank toolbar
839
+ View statusBarColorView = findViewById(R.id.status_bar_color_view);
840
+ if (
841
+ _options.getToolbarColor() != null &&
842
+ !_options.getToolbarColor().isEmpty()
843
+ ) {
844
+ try {
845
+ int toolbarColor = Color.parseColor(_options.getToolbarColor());
846
+ if (getWindow() != null) {
847
+ getWindow().getDecorView().setBackgroundColor(toolbarColor);
848
+ }
849
+ // Also set status bar color view background if available
850
+ if (statusBarColorView != null) {
851
+ statusBarColorView.setBackgroundColor(toolbarColor);
852
+ }
853
+ } catch (IllegalArgumentException e) {
854
+ // Fallback to system default if color parsing fails
855
+ boolean isDarkTheme = isDarkThemeEnabled();
856
+ int windowBackgroundColor = isDarkTheme ? Color.BLACK : Color.WHITE;
857
+ if (getWindow() != null) {
858
+ getWindow()
859
+ .getDecorView()
860
+ .setBackgroundColor(windowBackgroundColor);
861
+ }
862
+ // Also set status bar color view background if available
863
+ if (statusBarColorView != null) {
864
+ statusBarColorView.setBackgroundColor(windowBackgroundColor);
865
+ }
866
+ }
867
+ } else {
868
+ // Follow system dark mode
869
+ boolean isDarkTheme = isDarkThemeEnabled();
870
+ int windowBackgroundColor = isDarkTheme ? Color.BLACK : Color.WHITE;
871
+ if (getWindow() != null) {
872
+ getWindow().getDecorView().setBackgroundColor(windowBackgroundColor);
873
+ }
874
+ // Also set status bar color view background if available
875
+ if (statusBarColorView != null) {
876
+ statusBarColorView.setBackgroundColor(windowBackgroundColor);
877
+ }
878
+ }
604
879
  } else {
605
880
  _toolbar.findViewById(R.id.forwardButton).setVisibility(View.GONE);
606
881
  _toolbar.findViewById(R.id.backButton).setVisibility(View.GONE);
607
882
 
883
+ // Status bar color is already set at the top of this method, no need to set again
884
+
608
885
  Options.ButtonNearDone buttonNearDone = _options.getButtonNearDone();
609
886
  if (buttonNearDone != null) {
610
- AssetManager assetManager = _context.getAssets();
611
-
612
- // Open the SVG file from assets
613
- InputStream inputStream = null;
614
- try {
615
- ImageButton buttonNearDoneView = _toolbar.findViewById(
616
- R.id.buttonNearDone
617
- );
618
- buttonNearDoneView.setVisibility(View.VISIBLE);
619
-
620
- inputStream = assetManager.open(buttonNearDone.getIcon());
887
+ ImageButton buttonNearDoneView = _toolbar.findViewById(
888
+ R.id.buttonNearDone
889
+ );
890
+ buttonNearDoneView.setVisibility(View.VISIBLE);
621
891
 
622
- SVG svg = SVG.getFromInputStream(inputStream);
623
- Picture picture = svg.renderToPicture(
624
- buttonNearDone.getWidth(),
625
- buttonNearDone.getHeight()
626
- );
627
- PictureDrawable pictureDrawable = new PictureDrawable(picture);
892
+ // Handle different icon types
893
+ String iconType = buttonNearDone.getIconType();
894
+ if ("vector".equals(iconType)) {
895
+ // Use native Android vector drawable
896
+ try {
897
+ String iconName = buttonNearDone.getIcon();
898
+ // Convert name to Android resource ID (remove file extension if present)
899
+ if (iconName.endsWith(".xml")) {
900
+ iconName = iconName.substring(0, iconName.length() - 4);
901
+ }
628
902
 
629
- buttonNearDoneView.setImageDrawable(pictureDrawable);
630
- buttonNearDoneView.setOnClickListener(view ->
631
- _options.getCallbacks().buttonNearDoneClicked()
632
- );
633
- } catch (IOException | SVGParseException e) {
634
- throw new RuntimeException(e);
635
- } finally {
636
- if (inputStream != null) {
903
+ // Get resource ID
904
+ int resourceId = _context
905
+ .getResources()
906
+ .getIdentifier(iconName, "drawable", _context.getPackageName());
907
+
908
+ if (resourceId != 0) {
909
+ // Set the vector drawable
910
+ buttonNearDoneView.setImageResource(resourceId);
911
+ // Apply color filter
912
+ buttonNearDoneView.setColorFilter(iconColor);
913
+ Log.d(
914
+ "InAppBrowser",
915
+ "Successfully loaded vector drawable: " + iconName
916
+ );
917
+ } else {
918
+ Log.e(
919
+ "InAppBrowser",
920
+ "Vector drawable not found: " + iconName + ", using fallback"
921
+ );
922
+ // Fallback to a common system icon
923
+ buttonNearDoneView.setImageResource(
924
+ android.R.drawable.ic_menu_info_details
925
+ );
926
+ buttonNearDoneView.setColorFilter(iconColor);
927
+ }
928
+ } catch (Exception e) {
929
+ Log.e(
930
+ "InAppBrowser",
931
+ "Error loading vector drawable: " + e.getMessage()
932
+ );
933
+ // Fallback to a common system icon
934
+ buttonNearDoneView.setImageResource(
935
+ android.R.drawable.ic_menu_info_details
936
+ );
937
+ buttonNearDoneView.setColorFilter(iconColor);
938
+ }
939
+ } else if ("asset".equals(iconType)) {
940
+ // Handle SVG from assets
941
+ AssetManager assetManager = _context.getAssets();
942
+ InputStream inputStream = null;
943
+ try {
944
+ // Try to load from public folder first
945
+ String iconPath = "public/" + buttonNearDone.getIcon();
637
946
  try {
638
- inputStream.close();
947
+ inputStream = assetManager.open(iconPath);
639
948
  } catch (IOException e) {
640
- e.printStackTrace();
949
+ // If not found in public, try root assets
950
+ try {
951
+ inputStream = assetManager.open(buttonNearDone.getIcon());
952
+ } catch (IOException e2) {
953
+ Log.e(
954
+ "InAppBrowser",
955
+ "SVG file not found in assets: " + buttonNearDone.getIcon()
956
+ );
957
+ buttonNearDoneView.setVisibility(View.GONE);
958
+ return;
959
+ }
960
+ }
961
+
962
+ if (inputStream == null) {
963
+ Log.e(
964
+ "InAppBrowser",
965
+ "Failed to load SVG icon: " + buttonNearDone.getIcon()
966
+ );
967
+ buttonNearDoneView.setVisibility(View.GONE);
968
+ return;
969
+ }
970
+
971
+ // Parse and render SVG
972
+ SVG svg = SVG.getFromInputStream(inputStream);
973
+ if (svg == null) {
974
+ Log.e(
975
+ "InAppBrowser",
976
+ "Failed to parse SVG icon: " + buttonNearDone.getIcon()
977
+ );
978
+ buttonNearDoneView.setVisibility(View.GONE);
979
+ return;
980
+ }
981
+
982
+ // Get the dimensions from options or use SVG's size
983
+ float width = buttonNearDone.getWidth() > 0
984
+ ? buttonNearDone.getWidth()
985
+ : 24;
986
+ float height = buttonNearDone.getHeight() > 0
987
+ ? buttonNearDone.getHeight()
988
+ : 24;
989
+
990
+ // Get density for proper scaling
991
+ float density = _context.getResources().getDisplayMetrics().density;
992
+ int targetWidth = Math.round(width * density);
993
+ int targetHeight = Math.round(height * density);
994
+
995
+ // Set document size
996
+ svg.setDocumentWidth(targetWidth);
997
+ svg.setDocumentHeight(targetHeight);
998
+
999
+ // Create a bitmap and render SVG to it for better quality
1000
+ Bitmap bitmap = Bitmap.createBitmap(
1001
+ targetWidth,
1002
+ targetHeight,
1003
+ Bitmap.Config.ARGB_8888
1004
+ );
1005
+ Canvas canvas = new Canvas(bitmap);
1006
+ svg.renderToCanvas(canvas);
1007
+
1008
+ // Apply color filter to the bitmap
1009
+ Paint paint = new Paint();
1010
+ paint.setColorFilter(
1011
+ new PorterDuffColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
1012
+ );
1013
+ Canvas colorFilterCanvas = new Canvas(bitmap);
1014
+ colorFilterCanvas.drawBitmap(bitmap, 0, 0, paint);
1015
+
1016
+ // Set the colored bitmap as image
1017
+ buttonNearDoneView.setImageBitmap(bitmap);
1018
+ buttonNearDoneView.setScaleType(ImageView.ScaleType.FIT_CENTER);
1019
+ buttonNearDoneView.setPadding(12, 12, 12, 12); // Standard button padding
1020
+ } catch (SVGParseException e) {
1021
+ Log.e(
1022
+ "InAppBrowser",
1023
+ "Error loading SVG icon: " + e.getMessage(),
1024
+ e
1025
+ );
1026
+ buttonNearDoneView.setVisibility(View.GONE);
1027
+ } finally {
1028
+ if (inputStream != null) {
1029
+ try {
1030
+ inputStream.close();
1031
+ } catch (IOException e) {
1032
+ Log.e(
1033
+ "InAppBrowser",
1034
+ "Error closing input stream: " + e.getMessage()
1035
+ );
1036
+ }
641
1037
  }
642
1038
  }
1039
+ } else {
1040
+ // Default fallback or unsupported type
1041
+ Log.e("InAppBrowser", "Unsupported icon type: " + iconType);
1042
+ buttonNearDoneView.setVisibility(View.GONE);
643
1043
  }
1044
+
1045
+ // Set the click listener
1046
+ buttonNearDoneView.setOnClickListener(view ->
1047
+ _options.getCallbacks().buttonNearDoneClicked()
1048
+ );
644
1049
  } else {
645
1050
  ImageButton buttonNearDoneView = _toolbar.findViewById(
646
1051
  R.id.buttonNearDone
@@ -648,6 +1053,94 @@ public class WebViewDialog extends Dialog {
648
1053
  buttonNearDoneView.setVisibility(View.GONE);
649
1054
  }
650
1055
  }
1056
+
1057
+ // Add share button functionality
1058
+ ImageButton shareButton = _toolbar.findViewById(R.id.shareButton);
1059
+ if (
1060
+ _options.getShareSubject() != null &&
1061
+ !_options.getShareSubject().isEmpty()
1062
+ ) {
1063
+ shareButton.setVisibility(View.VISIBLE);
1064
+ Log.d(
1065
+ "InAppBrowser",
1066
+ "Share button should be visible, shareSubject: " +
1067
+ _options.getShareSubject()
1068
+ );
1069
+
1070
+ // Apply the same color filter as other buttons to ensure visibility
1071
+ shareButton.setColorFilter(iconColor);
1072
+
1073
+ // The color filter is now applied in applyColorToAllButtons
1074
+ shareButton.setOnClickListener(view -> {
1075
+ JSObject shareDisclaimer = _options.getShareDisclaimer();
1076
+ if (shareDisclaimer != null) {
1077
+ new AlertDialog.Builder(_context)
1078
+ .setTitle(shareDisclaimer.getString("title", "Title"))
1079
+ .setMessage(shareDisclaimer.getString("message", "Message"))
1080
+ .setPositiveButton(
1081
+ shareDisclaimer.getString("confirmBtn", "Confirm"),
1082
+ (dialog, which) -> {
1083
+ _options.getCallbacks().confirmBtnClicked();
1084
+ shareUrl();
1085
+ }
1086
+ )
1087
+ .setNegativeButton(
1088
+ shareDisclaimer.getString("cancelBtn", "Cancel"),
1089
+ null
1090
+ )
1091
+ .show();
1092
+ } else {
1093
+ shareUrl();
1094
+ }
1095
+ });
1096
+ } else {
1097
+ shareButton.setVisibility(View.GONE);
1098
+ }
1099
+
1100
+ // Also color the title text
1101
+ TextView titleText = _toolbar.findViewById(R.id.titleText);
1102
+ if (titleText != null) {
1103
+ titleText.setTextColor(iconColor);
1104
+
1105
+ // Set the title text
1106
+ if (!TextUtils.isEmpty(_options.getTitle())) {
1107
+ this.setTitle(_options.getTitle());
1108
+ } else {
1109
+ try {
1110
+ URI uri = new URI(_options.getUrl());
1111
+ this.setTitle(uri.getHost());
1112
+ } catch (URISyntaxException e) {
1113
+ this.setTitle(_options.getTitle());
1114
+ }
1115
+ }
1116
+ }
1117
+ }
1118
+
1119
+ /**
1120
+ * Applies background and tint colors to all buttons in the toolbar
1121
+ */
1122
+ private void applyColorToAllButtons(int backgroundColor, int iconColor) {
1123
+ // Get all buttons
1124
+ ImageButton backButton = _toolbar.findViewById(R.id.backButton);
1125
+ ImageButton forwardButton = _toolbar.findViewById(R.id.forwardButton);
1126
+ ImageButton closeButton = _toolbar.findViewById(R.id.closeButton);
1127
+ ImageButton reloadButton = _toolbar.findViewById(R.id.reloadButton);
1128
+ ImageButton shareButton = _toolbar.findViewById(R.id.shareButton);
1129
+ ImageButton buttonNearDoneView = _toolbar.findViewById(R.id.buttonNearDone);
1130
+
1131
+ // Set button backgrounds
1132
+ backButton.setBackgroundColor(backgroundColor);
1133
+ forwardButton.setBackgroundColor(backgroundColor);
1134
+ closeButton.setBackgroundColor(backgroundColor);
1135
+ reloadButton.setBackgroundColor(backgroundColor);
1136
+
1137
+ // Apply tint colors to buttons
1138
+ backButton.setColorFilter(iconColor);
1139
+ forwardButton.setColorFilter(iconColor);
1140
+ closeButton.setColorFilter(iconColor);
1141
+ reloadButton.setColorFilter(iconColor);
1142
+ shareButton.setColorFilter(iconColor);
1143
+ buttonNearDoneView.setColorFilter(iconColor);
651
1144
  }
652
1145
 
653
1146
  public void handleProxyResultError(String result, String id) {
@@ -837,7 +1330,7 @@ public class WebViewDialog extends Dialog {
837
1330
  );
838
1331
  }
839
1332
  String s = String.format(
840
- "try {function getHeaders() {const h = {}; %s return h}; window.InAppBrowserProxyRequest(new Request(atob('%s'), {headers: getHeaders(), method: '%s'})).then(async (res) => Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({ok: true, result: (!!res ? {headers: Object.fromEntries(res.headers.entries()), code: res.status, body: (await res.text())} : null), id: '%s'})).catch((e) => Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({ok: false, result: e.toString(), id: '%s'}))} catch (e) {Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({ok: false, result: e.toString(), id: '%s'})}",
1333
+ "try {function getHeaders() {const h = {}; %s return h}; window.InAppBrowserProxyRequest(new Request(atob('%s'), {headers: getHeaders(), method: '%s'})).then(async (res) => Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({ok: true, result: (!!res ? {headers: Object.fromEntries(res.headers.entries()), code: res.status, body: (await res.text())} : null), id: '%s'})).catch((e) => Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({ok: false, result: e.toString(), id: '%s'})} catch (e) {Capacitor.Plugins.InAppBrowser.lsuakdchgbbaHandleProxiedRequest({ok: false, result: e.toString(), id: '%s'})}",
841
1334
  headers,
842
1335
  toBase64(request.getUrl().toString()),
843
1336
  request.getMethod(),
@@ -1020,18 +1513,36 @@ public class WebViewDialog extends Dialog {
1020
1513
  if (_webView.canGoBack()) {
1021
1514
  backButton.setImageResource(R.drawable.arrow_back_enabled);
1022
1515
  backButton.setEnabled(true);
1516
+ backButton.setColorFilter(iconColor);
1023
1517
  } else {
1024
1518
  backButton.setImageResource(R.drawable.arrow_back_disabled);
1025
1519
  backButton.setEnabled(false);
1520
+ backButton.setColorFilter(
1521
+ Color.argb(
1522
+ 128,
1523
+ Color.red(iconColor),
1524
+ Color.green(iconColor),
1525
+ Color.blue(iconColor)
1526
+ )
1527
+ );
1026
1528
  }
1027
1529
 
1028
1530
  ImageButton forwardButton = _toolbar.findViewById(R.id.forwardButton);
1029
1531
  if (_webView.canGoForward()) {
1030
1532
  forwardButton.setImageResource(R.drawable.arrow_forward_enabled);
1031
1533
  forwardButton.setEnabled(true);
1534
+ forwardButton.setColorFilter(iconColor);
1032
1535
  } else {
1033
1536
  forwardButton.setImageResource(R.drawable.arrow_forward_disabled);
1034
1537
  forwardButton.setEnabled(false);
1538
+ forwardButton.setColorFilter(
1539
+ Color.argb(
1540
+ 128,
1541
+ Color.red(iconColor),
1542
+ Color.green(iconColor),
1543
+ Color.blue(iconColor)
1544
+ )
1545
+ );
1035
1546
  }
1036
1547
 
1037
1548
  _options.getCallbacks().pageLoaded();
@@ -1177,4 +1688,51 @@ public class WebViewDialog extends Dialog {
1177
1688
  proxiedRequestsHashmap.remove(key);
1178
1689
  }
1179
1690
  }
1691
+
1692
+ private void shareUrl() {
1693
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
1694
+ shareIntent.setType("text/plain");
1695
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT, _options.getShareSubject());
1696
+ shareIntent.putExtra(Intent.EXTRA_TEXT, _options.getUrl());
1697
+ _context.startActivity(Intent.createChooser(shareIntent, "Share"));
1698
+ }
1699
+
1700
+ private boolean isDarkColor(int color) {
1701
+ int red = Color.red(color);
1702
+ int green = Color.green(color);
1703
+ int blue = Color.blue(color);
1704
+ double luminance = (0.299 * red + 0.587 * green + 0.114 * blue) / 255.0;
1705
+ return luminance < 0.5;
1706
+ }
1707
+
1708
+ private boolean isDarkThemeEnabled() {
1709
+ // This method checks if dark theme is currently enabled without using Configuration class
1710
+ try {
1711
+ // On Android 10+, check via resources for night mode
1712
+ Resources.Theme theme = _context.getTheme();
1713
+ TypedValue typedValue = new TypedValue();
1714
+
1715
+ if (
1716
+ theme.resolveAttribute(android.R.attr.isLightTheme, typedValue, true)
1717
+ ) {
1718
+ // isLightTheme exists - returns true if light, false if dark
1719
+ return typedValue.data != 1;
1720
+ }
1721
+
1722
+ // Fallback method - check background color of window
1723
+ if (
1724
+ theme.resolveAttribute(
1725
+ android.R.attr.windowBackground,
1726
+ typedValue,
1727
+ true
1728
+ )
1729
+ ) {
1730
+ int backgroundColor = typedValue.data;
1731
+ return isDarkColor(backgroundColor);
1732
+ }
1733
+ } catch (Exception e) {
1734
+ // Ignore and fallback to light theme
1735
+ }
1736
+ return false;
1737
+ }
1180
1738
  }