@capgo/inappbrowser 6.11.1 → 6.13.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 (30) hide show
  1. package/README.md +50 -42
  2. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java +161 -41
  3. package/android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java +83 -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 +749 -96
  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/content_browser.xml +10 -1
  9. package/android/src/main/res/layout/tool_bar.xml +19 -7
  10. package/android/src/main/res/values/strings.xml +2 -0
  11. package/android/src/main/res/values/themes.xml +27 -0
  12. package/dist/docs.json +211 -37
  13. package/dist/esm/definitions.d.ts +244 -31
  14. package/dist/esm/definitions.js +12 -1
  15. package/dist/esm/definitions.js.map +1 -1
  16. package/dist/plugin.cjs.js +12 -1
  17. package/dist/plugin.cjs.js.map +1 -1
  18. package/dist/plugin.js +12 -1
  19. package/dist/plugin.js.map +1 -1
  20. package/ios/Plugin/InAppBrowserPlugin.swift +344 -44
  21. package/ios/Plugin/WKWebViewController.swift +499 -45
  22. package/package.json +7 -8
  23. package/ios/Plugin/Assets.xcassets/Back.imageset/Back.png +0 -0
  24. package/ios/Plugin/Assets.xcassets/Back.imageset/Back@2x.png +0 -0
  25. package/ios/Plugin/Assets.xcassets/Back.imageset/Back@3x.png +0 -0
  26. package/ios/Plugin/Assets.xcassets/Back.imageset/Contents.json +0 -26
  27. package/ios/Plugin/Assets.xcassets/Forward.imageset/Contents.json +0 -26
  28. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward.png +0 -0
  29. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward@2x.png +0 -0
  30. package/ios/Plugin/Assets.xcassets/Forward.imageset/Forward@3x.png +0 -0
@@ -9,15 +9,19 @@ 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;
14
- import android.graphics.Picture;
15
- import android.graphics.drawable.PictureDrawable;
16
+ import android.graphics.Paint;
17
+ import android.graphics.PorterDuff;
18
+ import android.graphics.PorterDuffColorFilter;
16
19
  import android.net.Uri;
17
20
  import android.net.http.SslError;
18
21
  import android.text.TextUtils;
19
22
  import android.util.Base64;
20
23
  import android.util.Log;
24
+ import android.util.TypedValue;
21
25
  import android.view.View;
22
26
  import android.view.Window;
23
27
  import android.view.WindowManager;
@@ -33,6 +37,7 @@ import android.webkit.WebResourceResponse;
33
37
  import android.webkit.WebView;
34
38
  import android.webkit.WebViewClient;
35
39
  import android.widget.ImageButton;
40
+ import android.widget.ImageView;
36
41
  import android.widget.TextView;
37
42
  import android.widget.Toast;
38
43
  import android.widget.Toolbar;
@@ -84,11 +89,13 @@ public class WebViewDialog extends Dialog {
84
89
  private final Context _context;
85
90
  public Activity activity;
86
91
  private boolean isInitialized = false;
92
+ private boolean datePickerInjected = false; // Track if we've injected date picker fixes
87
93
  private final WebView capacitorWebView;
88
94
  private final Map<String, ProxiedRequest> proxiedRequestsHashmap =
89
95
  new HashMap<>();
90
96
  private final ExecutorService executorService =
91
97
  Executors.newCachedThreadPool();
98
+ private int iconColor = Color.BLACK; // Default icon color
92
99
 
93
100
  Semaphore preShowSemaphore = null;
94
101
  String preShowError = null;
@@ -113,7 +120,11 @@ public class WebViewDialog extends Dialog {
113
120
  PermissionHandler permissionHandler,
114
121
  WebView capacitorWebView
115
122
  ) {
116
- super(context, theme);
123
+ // Use Material theme only if materialPicker is enabled
124
+ super(
125
+ context,
126
+ options.getMaterialPicker() ? R.style.InAppBrowserMaterialTheme : theme
127
+ );
117
128
  this._options = options;
118
129
  this._context = context;
119
130
  this.permissionHandler = permissionHandler;
@@ -200,14 +211,78 @@ public class WebViewDialog extends Dialog {
200
211
  setContentView(R.layout.activity_browser);
201
212
  getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
202
213
 
214
+ // Make status bar transparent
215
+ if (getWindow() != null) {
216
+ getWindow().setStatusBarColor(Color.TRANSPARENT);
217
+
218
+ // Add FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag to the window
219
+ getWindow()
220
+ .addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
221
+
222
+ // On Android 30+ clear FLAG_TRANSLUCENT_STATUS flag
223
+ getWindow()
224
+ .clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
225
+ }
226
+
203
227
  WindowInsetsControllerCompat insetsController =
204
- new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView());
205
- insetsController.setAppearanceLightStatusBars(false);
206
- getWindow()
207
- .getDecorView()
208
- .post(() -> {
209
- getWindow().setStatusBarColor(Color.BLACK);
210
- });
228
+ new WindowInsetsControllerCompat(
229
+ getWindow(),
230
+ getWindow() != null ? getWindow().getDecorView() : null
231
+ );
232
+
233
+ if (getWindow() != null) {
234
+ getWindow()
235
+ .getDecorView()
236
+ .post(() -> {
237
+ // Get status bar height
238
+ int statusBarHeight = 0;
239
+ int resourceId = getContext()
240
+ .getResources()
241
+ .getIdentifier("status_bar_height", "dimen", "android");
242
+ if (resourceId > 0) {
243
+ statusBarHeight = getContext()
244
+ .getResources()
245
+ .getDimensionPixelSize(resourceId);
246
+ }
247
+
248
+ // Find the status bar color view
249
+ View statusBarColorView = findViewById(R.id.status_bar_color_view);
250
+
251
+ // Set the height of the status bar color view
252
+ if (statusBarColorView != null) {
253
+ statusBarColorView.getLayoutParams().height = statusBarHeight;
254
+ statusBarColorView.requestLayout();
255
+
256
+ // Set color based on toolbar color or dark mode
257
+ if (
258
+ _options.getToolbarColor() != null &&
259
+ !_options.getToolbarColor().isEmpty()
260
+ ) {
261
+ try {
262
+ // Use explicitly provided toolbar color for status bar
263
+ int toolbarColor = Color.parseColor(_options.getToolbarColor());
264
+ statusBarColorView.setBackgroundColor(toolbarColor);
265
+
266
+ // Set status bar text to white or black based on background
267
+ boolean isDarkBackground = isDarkColor(toolbarColor);
268
+ insetsController.setAppearanceLightStatusBars(
269
+ !isDarkBackground
270
+ );
271
+ } catch (IllegalArgumentException e) {
272
+ // Fallback to default black if color parsing fails
273
+ statusBarColorView.setBackgroundColor(Color.BLACK);
274
+ insetsController.setAppearanceLightStatusBars(false);
275
+ }
276
+ } else {
277
+ // Follow system dark mode if no toolbar color provided
278
+ boolean isDarkTheme = isDarkThemeEnabled();
279
+ int statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
280
+ statusBarColorView.setBackgroundColor(statusBarColor);
281
+ insetsController.setAppearanceLightStatusBars(!isDarkTheme);
282
+ }
283
+ }
284
+ });
285
+ }
211
286
 
212
287
  getWindow()
213
288
  .setLayout(
@@ -235,6 +310,52 @@ public class WebViewDialog extends Dialog {
235
310
  _webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
236
311
  _webView.getSettings().setMediaPlaybackRequiresUserGesture(false);
237
312
 
313
+ // Set text zoom if specified in options
314
+ if (_options.getTextZoom() > 0) {
315
+ _webView.getSettings().setTextZoom(_options.getTextZoom());
316
+ }
317
+
318
+ // Fix for Android 15 (API 35) navigation bar overlap issue
319
+ if (android.os.Build.VERSION.SDK_INT >= 35) {
320
+ // Get the navigation bar space view
321
+ View navigationBarSpace = findViewById(R.id.navigation_bar_space);
322
+
323
+ if (navigationBarSpace != null) {
324
+ // Calculate navigation bar height
325
+ int navigationBarHeight = 0;
326
+ int navBarId = getContext()
327
+ .getResources()
328
+ .getIdentifier("navigation_bar_height", "dimen", "android");
329
+
330
+ if (navBarId > 0) {
331
+ navigationBarHeight = getContext()
332
+ .getResources()
333
+ .getDimensionPixelSize(navBarId);
334
+
335
+ // Set the height of the navigation bar space
336
+ navigationBarSpace.getLayoutParams().height = navigationBarHeight;
337
+ navigationBarSpace.requestLayout();
338
+
339
+ // Set the background color to match the toolbar or system theme
340
+ int navBarColor;
341
+ if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
342
+ try {
343
+ navBarColor = Color.parseColor(_options.getToolbarColor());
344
+ } catch (IllegalArgumentException e) {
345
+ // Default to system coloring if parsing fails
346
+ boolean isDarkTheme = isDarkThemeEnabled();
347
+ navBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
348
+ }
349
+ } else {
350
+ // Follow system theme
351
+ boolean isDarkTheme = isDarkThemeEnabled();
352
+ navBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
353
+ }
354
+ navigationBarSpace.setBackgroundColor(navBarColor);
355
+ }
356
+ }
357
+ }
358
+
238
359
  _webView.setWebViewClient(new WebViewClient());
239
360
 
240
361
  _webView.setWebChromeClient(
@@ -246,9 +367,25 @@ public class WebViewDialog extends Dialog {
246
367
  ValueCallback<Uri[]> filePathCallback,
247
368
  FileChooserParams fileChooserParams
248
369
  ) {
370
+ // Get the accept type safely
371
+ String acceptType = "*/*"; // Default to all file types
372
+ if (
373
+ fileChooserParams.getAcceptTypes() != null &&
374
+ fileChooserParams.getAcceptTypes().length > 0 &&
375
+ !TextUtils.isEmpty(fileChooserParams.getAcceptTypes()[0])
376
+ ) {
377
+ acceptType = fileChooserParams.getAcceptTypes()[0];
378
+ }
379
+
380
+ // Check if the file chooser is already open
381
+ if (mFilePathCallback != null) {
382
+ mFilePathCallback.onReceiveValue(null);
383
+ mFilePathCallback = null;
384
+ }
385
+
249
386
  openFileChooser(
250
387
  filePathCallback,
251
- fileChooserParams.getAcceptTypes()[0],
388
+ acceptType,
252
389
  fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE
253
390
  );
254
391
  return true;
@@ -302,6 +439,22 @@ public class WebViewDialog extends Dialog {
302
439
  currentPermissionRequest = null;
303
440
  }
304
441
  }
442
+
443
+ // This method will be called at page load, a good place to inject customizations
444
+ @Override
445
+ public void onProgressChanged(WebView view, int newProgress) {
446
+ super.onProgressChanged(view, newProgress);
447
+
448
+ // When the page is almost loaded, inject our date picker customization
449
+ // Only if materialPicker option is enabled
450
+ if (
451
+ newProgress > 75 &&
452
+ !datePickerInjected &&
453
+ _options.getMaterialPicker()
454
+ ) {
455
+ injectDatePickerFixes();
456
+ }
457
+ }
305
458
  }
306
459
  );
307
460
 
@@ -433,16 +586,100 @@ public class WebViewDialog extends Dialog {
433
586
  mFilePathCallback = filePathCallback;
434
587
  Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
435
588
  intent.addCategory(Intent.CATEGORY_OPENABLE);
436
- intent.setType(acceptType); // Default to */*
589
+
590
+ // Fix MIME type handling
591
+ if (
592
+ acceptType == null ||
593
+ acceptType.isEmpty() ||
594
+ acceptType.equals("undefined")
595
+ ) {
596
+ acceptType = "*/*";
597
+ } else {
598
+ // Handle common web input accept types
599
+ if (acceptType.equals("image/*")) {
600
+ // Keep as is - image/*
601
+ } else if (acceptType.contains("image/")) {
602
+ // Specific image type requested but keep it general for better compatibility
603
+ acceptType = "image/*";
604
+ } else if (
605
+ acceptType.equals("audio/*") || acceptType.contains("audio/")
606
+ ) {
607
+ acceptType = "audio/*";
608
+ } else if (
609
+ acceptType.equals("video/*") || acceptType.contains("video/")
610
+ ) {
611
+ acceptType = "video/*";
612
+ } else if (acceptType.startsWith(".") || acceptType.contains(",")) {
613
+ // Handle file extensions like ".pdf, .docx" by using a general mime type
614
+ if (acceptType.contains(".pdf")) {
615
+ acceptType = "application/pdf";
616
+ } else if (
617
+ acceptType.contains(".doc") || acceptType.contains(".docx")
618
+ ) {
619
+ acceptType = "application/msword";
620
+ } else if (
621
+ acceptType.contains(".xls") || acceptType.contains(".xlsx")
622
+ ) {
623
+ acceptType = "application/vnd.ms-excel";
624
+ } else if (
625
+ acceptType.contains(".txt") || acceptType.contains(".text")
626
+ ) {
627
+ acceptType = "text/plain";
628
+ } else {
629
+ // Default for extension lists
630
+ acceptType = "*/*";
631
+ }
632
+ }
633
+ }
634
+
635
+ Log.d("InAppBrowser", "File picker using MIME type: " + acceptType);
636
+ intent.setType(acceptType);
437
637
  intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultiple);
438
- activity.startActivityForResult(
439
- Intent.createChooser(intent, "Select File"),
440
- FILE_CHOOSER_REQUEST_CODE
441
- );
638
+
639
+ try {
640
+ activity.startActivityForResult(
641
+ Intent.createChooser(intent, "Select File"),
642
+ FILE_CHOOSER_REQUEST_CODE
643
+ );
644
+ } catch (ActivityNotFoundException e) {
645
+ // If no app can handle the specific MIME type, try with a more generic one
646
+ Log.e(
647
+ "InAppBrowser",
648
+ "No app available for type: " + acceptType + ", trying with */*"
649
+ );
650
+ intent.setType("*/*");
651
+ try {
652
+ activity.startActivityForResult(
653
+ Intent.createChooser(intent, "Select File"),
654
+ FILE_CHOOSER_REQUEST_CODE
655
+ );
656
+ } catch (ActivityNotFoundException ex) {
657
+ // If still failing, report error
658
+ Log.e("InAppBrowser", "No app can handle file picker", ex);
659
+ if (mFilePathCallback != null) {
660
+ mFilePathCallback.onReceiveValue(null);
661
+ mFilePathCallback = null;
662
+ }
663
+ }
664
+ }
442
665
  }
443
666
 
444
667
  public void reload() {
445
- _webView.reload();
668
+ if (_webView != null) {
669
+ // First stop any ongoing loading
670
+ _webView.stopLoading();
671
+
672
+ // Check if there's a URL to reload
673
+ if (_webView.getUrl() != null) {
674
+ // Reload the current page
675
+ _webView.reload();
676
+ Log.d("InAppBrowser", "Reloading page: " + _webView.getUrl());
677
+ } else if (_options != null && _options.getUrl() != null) {
678
+ // If webView URL is null but we have an initial URL, load that
679
+ setUrl(_options.getUrl());
680
+ Log.d("InAppBrowser", "Loading initial URL: " + _options.getUrl());
681
+ }
682
+ }
446
683
  }
447
684
 
448
685
  public void destroy() {
@@ -485,56 +722,72 @@ public class WebViewDialog extends Dialog {
485
722
  }
486
723
 
487
724
  private void setupToolbar() {
488
- _toolbar = this.findViewById(R.id.tool_bar);
489
- int color = Color.parseColor("#ffffff");
490
- try {
491
- color = Color.parseColor(_options.getToolbarColor());
492
- } catch (IllegalArgumentException e) {
493
- // Do nothing
494
- }
495
- _toolbar.setBackgroundColor(color);
496
- _toolbar.findViewById(R.id.backButton).setBackgroundColor(color);
497
- _toolbar.findViewById(R.id.forwardButton).setBackgroundColor(color);
498
- _toolbar.findViewById(R.id.closeButton).setBackgroundColor(color);
499
- _toolbar.findViewById(R.id.reloadButton).setBackgroundColor(color);
500
-
501
- if (!TextUtils.isEmpty(_options.getTitle())) {
502
- this.setTitle(_options.getTitle());
503
- } else {
725
+ _toolbar = findViewById(R.id.tool_bar);
726
+
727
+ // Apply toolbar color early, for ALL toolbar types, before any view configuration
728
+ if (
729
+ _options.getToolbarColor() != null &&
730
+ !_options.getToolbarColor().isEmpty()
731
+ ) {
504
732
  try {
505
- URI uri = new URI(_options.getUrl());
506
- this.setTitle(uri.getHost());
507
- } catch (URISyntaxException e) {
508
- this.setTitle(_options.getTitle());
509
- }
510
- }
733
+ int toolbarColor = Color.parseColor(_options.getToolbarColor());
734
+ _toolbar.setBackgroundColor(toolbarColor);
511
735
 
512
- View backButton = _toolbar.findViewById(R.id.backButton);
513
- backButton.setOnClickListener(
514
- new View.OnClickListener() {
515
- @Override
516
- public void onClick(View view) {
517
- if (_webView.canGoBack()) {
518
- _webView.goBack();
736
+ // Get toolbar title and ensure it gets the right color
737
+ TextView titleText = _toolbar.findViewById(R.id.titleText);
738
+
739
+ // Determine icon and text color
740
+ int iconColor;
741
+ if (
742
+ _options.getToolbarTextColor() != null &&
743
+ !_options.getToolbarTextColor().isEmpty()
744
+ ) {
745
+ try {
746
+ iconColor = Color.parseColor(_options.getToolbarTextColor());
747
+ } catch (IllegalArgumentException e) {
748
+ // Fallback to automatic detection if parsing fails
749
+ boolean isDarkBackground = isDarkColor(toolbarColor);
750
+ iconColor = isDarkBackground ? Color.WHITE : Color.BLACK;
519
751
  }
752
+ } else {
753
+ // No explicit toolbarTextColor, use automatic detection based on background
754
+ boolean isDarkBackground = isDarkColor(toolbarColor);
755
+ iconColor = isDarkBackground ? Color.WHITE : Color.BLACK;
520
756
  }
521
- }
522
- );
523
757
 
524
- View forwardButton = _toolbar.findViewById(R.id.forwardButton);
525
- forwardButton.setOnClickListener(
526
- new View.OnClickListener() {
527
- @Override
528
- public void onClick(View view) {
529
- if (_webView.canGoForward()) {
530
- _webView.goForward();
531
- }
758
+ // Store for later use with navigation buttons
759
+ this.iconColor = iconColor;
760
+
761
+ // Set title text color directly
762
+ titleText.setTextColor(iconColor);
763
+
764
+ // Apply colors to all buttons
765
+ applyColorToAllButtons(toolbarColor, iconColor);
766
+
767
+ // Also ensure status bar gets the color
768
+ if (getWindow() != null) {
769
+ // Set status bar color
770
+ getWindow().setStatusBarColor(toolbarColor);
771
+
772
+ // Determine proper status bar text color (light or dark icons)
773
+ boolean isDarkBackground = isDarkColor(toolbarColor);
774
+ WindowInsetsControllerCompat insetsController =
775
+ new WindowInsetsControllerCompat(
776
+ getWindow(),
777
+ getWindow().getDecorView()
778
+ );
779
+ insetsController.setAppearanceLightStatusBars(!isDarkBackground);
532
780
  }
781
+ } catch (IllegalArgumentException e) {
782
+ Log.e(
783
+ "InAppBrowser",
784
+ "Invalid toolbar color: " + _options.getToolbarColor()
785
+ );
533
786
  }
534
- );
787
+ }
535
788
 
536
- ImageButton closeButton = _toolbar.findViewById(R.id.closeButton);
537
- closeButton.setOnClickListener(
789
+ ImageButton closeButtonView = _toolbar.findViewById(R.id.closeButton);
790
+ closeButtonView.setOnClickListener(
538
791
  new View.OnClickListener() {
539
792
  @Override
540
793
  public void onClick(View view) {
@@ -548,7 +801,9 @@ public class WebViewDialog extends Dialog {
548
801
  new OnClickListener() {
549
802
  public void onClick(DialogInterface dialog, int which) {
550
803
  // Close button clicked, do something
551
- String currentUrl = _webView != null ? _webView.getUrl() : "";
804
+ String currentUrl = _webView != null
805
+ ? _webView.getUrl()
806
+ : "";
552
807
  dismiss();
553
808
  if (_options != null && _options.getCallbacks() != null) {
554
809
  _options.getCallbacks().closeEvent(currentUrl);
@@ -570,78 +825,292 @@ public class WebViewDialog extends Dialog {
570
825
  );
571
826
 
572
827
  if (_options.showArrow()) {
573
- closeButton.setImageResource(R.drawable.arrow_back_enabled);
828
+ closeButtonView.setImageResource(R.drawable.arrow_back_enabled);
574
829
  }
575
830
 
576
- if (_options.getShowReloadButton()) {
577
- View reloadButton = _toolbar.findViewById(R.id.reloadButton);
578
- reloadButton.setVisibility(View.VISIBLE);
579
- reloadButton.setOnClickListener(
831
+ // Handle reload button visibility
832
+ if (
833
+ _options.getShowReloadButton() &&
834
+ !TextUtils.equals(_options.getToolbarType(), "activity")
835
+ ) {
836
+ View reloadButtonView = _toolbar.findViewById(R.id.reloadButton);
837
+ reloadButtonView.setVisibility(View.VISIBLE);
838
+ reloadButtonView.setOnClickListener(
580
839
  new View.OnClickListener() {
581
840
  @Override
582
841
  public void onClick(View view) {
583
- _webView.reload();
842
+ if (_webView != null) {
843
+ // First stop any ongoing loading
844
+ _webView.stopLoading();
845
+
846
+ // Check if there's a URL to reload
847
+ if (_webView.getUrl() != null) {
848
+ // Reload the current page
849
+ _webView.reload();
850
+ Log.d("InAppBrowser", "Reloading page: " + _webView.getUrl());
851
+ } else if (_options.getUrl() != null) {
852
+ // If webView URL is null but we have an initial URL, load that
853
+ setUrl(_options.getUrl());
854
+ Log.d(
855
+ "InAppBrowser",
856
+ "Loading initial URL: " + _options.getUrl()
857
+ );
858
+ }
859
+ }
584
860
  }
585
861
  }
586
862
  );
863
+ } else {
864
+ View reloadButtonView = _toolbar.findViewById(R.id.reloadButton);
865
+ reloadButtonView.setVisibility(View.GONE);
587
866
  }
588
867
 
589
868
  if (TextUtils.equals(_options.getToolbarType(), "activity")) {
869
+ // Activity mode should ONLY have:
870
+ // 1. Close button
871
+ // 2. Share button (if shareSubject is provided)
872
+
873
+ // Hide all navigation buttons
590
874
  _toolbar.findViewById(R.id.forwardButton).setVisibility(View.GONE);
591
875
  _toolbar.findViewById(R.id.backButton).setVisibility(View.GONE);
876
+
877
+ // Hide buttonNearDone
592
878
  ImageButton buttonNearDoneView = _toolbar.findViewById(
593
879
  R.id.buttonNearDone
594
880
  );
595
881
  buttonNearDoneView.setVisibility(View.GONE);
596
- //TODO: Add share button functionality
882
+
883
+ // In activity mode, always make the share button visible by setting a default shareSubject if not provided
884
+ if (
885
+ _options.getShareSubject() == null ||
886
+ _options.getShareSubject().isEmpty()
887
+ ) {
888
+ _options.setShareSubject("Share");
889
+ Log.d("InAppBrowser", "Activity mode: Setting default shareSubject");
890
+ }
891
+ // Status bar color is already set at the top of this method, no need to set again
892
+
893
+ // Share button visibility is handled separately later
597
894
  } else if (TextUtils.equals(_options.getToolbarType(), "navigation")) {
598
895
  ImageButton buttonNearDoneView = _toolbar.findViewById(
599
896
  R.id.buttonNearDone
600
897
  );
601
898
  buttonNearDoneView.setVisibility(View.GONE);
602
- //TODO: Remove share button when implemented
899
+ // Status bar color is already set at the top of this method, no need to set again
603
900
  } else if (TextUtils.equals(_options.getToolbarType(), "blank")) {
604
901
  _toolbar.setVisibility(View.GONE);
902
+
903
+ // Also set window background color to match status bar for blank toolbar
904
+ View statusBarColorView = findViewById(R.id.status_bar_color_view);
905
+ if (
906
+ _options.getToolbarColor() != null &&
907
+ !_options.getToolbarColor().isEmpty()
908
+ ) {
909
+ try {
910
+ int toolbarColor = Color.parseColor(_options.getToolbarColor());
911
+ if (getWindow() != null) {
912
+ getWindow().getDecorView().setBackgroundColor(toolbarColor);
913
+ }
914
+ // Also set status bar color view background if available
915
+ if (statusBarColorView != null) {
916
+ statusBarColorView.setBackgroundColor(toolbarColor);
917
+ }
918
+ } catch (IllegalArgumentException e) {
919
+ // Fallback to system default if color parsing fails
920
+ boolean isDarkTheme = isDarkThemeEnabled();
921
+ int windowBackgroundColor = isDarkTheme ? Color.BLACK : Color.WHITE;
922
+ if (getWindow() != null) {
923
+ getWindow()
924
+ .getDecorView()
925
+ .setBackgroundColor(windowBackgroundColor);
926
+ }
927
+ // Also set status bar color view background if available
928
+ if (statusBarColorView != null) {
929
+ statusBarColorView.setBackgroundColor(windowBackgroundColor);
930
+ }
931
+ }
932
+ } else {
933
+ // Follow system dark mode
934
+ boolean isDarkTheme = isDarkThemeEnabled();
935
+ int windowBackgroundColor = isDarkTheme ? Color.BLACK : Color.WHITE;
936
+ if (getWindow() != null) {
937
+ getWindow().getDecorView().setBackgroundColor(windowBackgroundColor);
938
+ }
939
+ // Also set status bar color view background if available
940
+ if (statusBarColorView != null) {
941
+ statusBarColorView.setBackgroundColor(windowBackgroundColor);
942
+ }
943
+ }
605
944
  } else {
606
945
  _toolbar.findViewById(R.id.forwardButton).setVisibility(View.GONE);
607
946
  _toolbar.findViewById(R.id.backButton).setVisibility(View.GONE);
608
947
 
948
+ // Status bar color is already set at the top of this method, no need to set again
949
+
609
950
  Options.ButtonNearDone buttonNearDone = _options.getButtonNearDone();
610
951
  if (buttonNearDone != null) {
611
- AssetManager assetManager = _context.getAssets();
612
-
613
- // Open the SVG file from assets
614
- InputStream inputStream = null;
615
- try {
616
- ImageButton buttonNearDoneView = _toolbar.findViewById(
617
- R.id.buttonNearDone
618
- );
619
- buttonNearDoneView.setVisibility(View.VISIBLE);
620
-
621
- inputStream = assetManager.open(buttonNearDone.getIcon());
952
+ ImageButton buttonNearDoneView = _toolbar.findViewById(
953
+ R.id.buttonNearDone
954
+ );
955
+ buttonNearDoneView.setVisibility(View.VISIBLE);
622
956
 
623
- SVG svg = SVG.getFromInputStream(inputStream);
624
- Picture picture = svg.renderToPicture(
625
- buttonNearDone.getWidth(),
626
- buttonNearDone.getHeight()
627
- );
628
- PictureDrawable pictureDrawable = new PictureDrawable(picture);
957
+ // Handle different icon types
958
+ String iconType = buttonNearDone.getIconType();
959
+ if ("vector".equals(iconType)) {
960
+ // Use native Android vector drawable
961
+ try {
962
+ String iconName = buttonNearDone.getIcon();
963
+ // Convert name to Android resource ID (remove file extension if present)
964
+ if (iconName.endsWith(".xml")) {
965
+ iconName = iconName.substring(0, iconName.length() - 4);
966
+ }
629
967
 
630
- buttonNearDoneView.setImageDrawable(pictureDrawable);
631
- buttonNearDoneView.setOnClickListener(view ->
632
- _options.getCallbacks().buttonNearDoneClicked()
633
- );
634
- } catch (IOException | SVGParseException e) {
635
- throw new RuntimeException(e);
636
- } finally {
637
- if (inputStream != null) {
968
+ // Get resource ID
969
+ int resourceId = _context
970
+ .getResources()
971
+ .getIdentifier(iconName, "drawable", _context.getPackageName());
972
+
973
+ if (resourceId != 0) {
974
+ // Set the vector drawable
975
+ buttonNearDoneView.setImageResource(resourceId);
976
+ // Apply color filter
977
+ buttonNearDoneView.setColorFilter(iconColor);
978
+ Log.d(
979
+ "InAppBrowser",
980
+ "Successfully loaded vector drawable: " + iconName
981
+ );
982
+ } else {
983
+ Log.e(
984
+ "InAppBrowser",
985
+ "Vector drawable not found: " + iconName + ", using fallback"
986
+ );
987
+ // Fallback to a common system icon
988
+ buttonNearDoneView.setImageResource(
989
+ android.R.drawable.ic_menu_info_details
990
+ );
991
+ buttonNearDoneView.setColorFilter(iconColor);
992
+ }
993
+ } catch (Exception e) {
994
+ Log.e(
995
+ "InAppBrowser",
996
+ "Error loading vector drawable: " + e.getMessage()
997
+ );
998
+ // Fallback to a common system icon
999
+ buttonNearDoneView.setImageResource(
1000
+ android.R.drawable.ic_menu_info_details
1001
+ );
1002
+ buttonNearDoneView.setColorFilter(iconColor);
1003
+ }
1004
+ } else if ("asset".equals(iconType)) {
1005
+ // Handle SVG from assets
1006
+ AssetManager assetManager = _context.getAssets();
1007
+ InputStream inputStream = null;
1008
+ try {
1009
+ // Try to load from public folder first
1010
+ String iconPath = "public/" + buttonNearDone.getIcon();
638
1011
  try {
639
- inputStream.close();
1012
+ inputStream = assetManager.open(iconPath);
640
1013
  } catch (IOException e) {
641
- e.printStackTrace();
1014
+ // If not found in public, try root assets
1015
+ try {
1016
+ inputStream = assetManager.open(buttonNearDone.getIcon());
1017
+ } catch (IOException e2) {
1018
+ Log.e(
1019
+ "InAppBrowser",
1020
+ "SVG file not found in assets: " + buttonNearDone.getIcon()
1021
+ );
1022
+ buttonNearDoneView.setVisibility(View.GONE);
1023
+ return;
1024
+ }
1025
+ }
1026
+
1027
+ if (inputStream == null) {
1028
+ Log.e(
1029
+ "InAppBrowser",
1030
+ "Failed to load SVG icon: " + buttonNearDone.getIcon()
1031
+ );
1032
+ buttonNearDoneView.setVisibility(View.GONE);
1033
+ return;
1034
+ }
1035
+
1036
+ // Parse and render SVG
1037
+ SVG svg = SVG.getFromInputStream(inputStream);
1038
+ if (svg == null) {
1039
+ Log.e(
1040
+ "InAppBrowser",
1041
+ "Failed to parse SVG icon: " + buttonNearDone.getIcon()
1042
+ );
1043
+ buttonNearDoneView.setVisibility(View.GONE);
1044
+ return;
1045
+ }
1046
+
1047
+ // Get the dimensions from options or use SVG's size
1048
+ float width = buttonNearDone.getWidth() > 0
1049
+ ? buttonNearDone.getWidth()
1050
+ : 24;
1051
+ float height = buttonNearDone.getHeight() > 0
1052
+ ? buttonNearDone.getHeight()
1053
+ : 24;
1054
+
1055
+ // Get density for proper scaling
1056
+ float density = _context.getResources().getDisplayMetrics().density;
1057
+ int targetWidth = Math.round(width * density);
1058
+ int targetHeight = Math.round(height * density);
1059
+
1060
+ // Set document size
1061
+ svg.setDocumentWidth(targetWidth);
1062
+ svg.setDocumentHeight(targetHeight);
1063
+
1064
+ // Create a bitmap and render SVG to it for better quality
1065
+ Bitmap bitmap = Bitmap.createBitmap(
1066
+ targetWidth,
1067
+ targetHeight,
1068
+ Bitmap.Config.ARGB_8888
1069
+ );
1070
+ Canvas canvas = new Canvas(bitmap);
1071
+ svg.renderToCanvas(canvas);
1072
+
1073
+ // Apply color filter to the bitmap
1074
+ Paint paint = new Paint();
1075
+ paint.setColorFilter(
1076
+ new PorterDuffColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
1077
+ );
1078
+ Canvas colorFilterCanvas = new Canvas(bitmap);
1079
+ colorFilterCanvas.drawBitmap(bitmap, 0, 0, paint);
1080
+
1081
+ // Set the colored bitmap as image
1082
+ buttonNearDoneView.setImageBitmap(bitmap);
1083
+ buttonNearDoneView.setScaleType(ImageView.ScaleType.FIT_CENTER);
1084
+ buttonNearDoneView.setPadding(12, 12, 12, 12); // Standard button padding
1085
+ } catch (SVGParseException e) {
1086
+ Log.e(
1087
+ "InAppBrowser",
1088
+ "Error loading SVG icon: " + e.getMessage(),
1089
+ e
1090
+ );
1091
+ buttonNearDoneView.setVisibility(View.GONE);
1092
+ } finally {
1093
+ if (inputStream != null) {
1094
+ try {
1095
+ inputStream.close();
1096
+ } catch (IOException e) {
1097
+ Log.e(
1098
+ "InAppBrowser",
1099
+ "Error closing input stream: " + e.getMessage()
1100
+ );
1101
+ }
642
1102
  }
643
1103
  }
1104
+ } else {
1105
+ // Default fallback or unsupported type
1106
+ Log.e("InAppBrowser", "Unsupported icon type: " + iconType);
1107
+ buttonNearDoneView.setVisibility(View.GONE);
644
1108
  }
1109
+
1110
+ // Set the click listener
1111
+ buttonNearDoneView.setOnClickListener(view ->
1112
+ _options.getCallbacks().buttonNearDoneClicked()
1113
+ );
645
1114
  } else {
646
1115
  ImageButton buttonNearDoneView = _toolbar.findViewById(
647
1116
  R.id.buttonNearDone
@@ -649,6 +1118,94 @@ public class WebViewDialog extends Dialog {
649
1118
  buttonNearDoneView.setVisibility(View.GONE);
650
1119
  }
651
1120
  }
1121
+
1122
+ // Add share button functionality
1123
+ ImageButton shareButton = _toolbar.findViewById(R.id.shareButton);
1124
+ if (
1125
+ _options.getShareSubject() != null &&
1126
+ !_options.getShareSubject().isEmpty()
1127
+ ) {
1128
+ shareButton.setVisibility(View.VISIBLE);
1129
+ Log.d(
1130
+ "InAppBrowser",
1131
+ "Share button should be visible, shareSubject: " +
1132
+ _options.getShareSubject()
1133
+ );
1134
+
1135
+ // Apply the same color filter as other buttons to ensure visibility
1136
+ shareButton.setColorFilter(iconColor);
1137
+
1138
+ // The color filter is now applied in applyColorToAllButtons
1139
+ shareButton.setOnClickListener(view -> {
1140
+ JSObject shareDisclaimer = _options.getShareDisclaimer();
1141
+ if (shareDisclaimer != null) {
1142
+ new AlertDialog.Builder(_context)
1143
+ .setTitle(shareDisclaimer.getString("title", "Title"))
1144
+ .setMessage(shareDisclaimer.getString("message", "Message"))
1145
+ .setPositiveButton(
1146
+ shareDisclaimer.getString("confirmBtn", "Confirm"),
1147
+ (dialog, which) -> {
1148
+ _options.getCallbacks().confirmBtnClicked();
1149
+ shareUrl();
1150
+ }
1151
+ )
1152
+ .setNegativeButton(
1153
+ shareDisclaimer.getString("cancelBtn", "Cancel"),
1154
+ null
1155
+ )
1156
+ .show();
1157
+ } else {
1158
+ shareUrl();
1159
+ }
1160
+ });
1161
+ } else {
1162
+ shareButton.setVisibility(View.GONE);
1163
+ }
1164
+
1165
+ // Also color the title text
1166
+ TextView titleText = _toolbar.findViewById(R.id.titleText);
1167
+ if (titleText != null) {
1168
+ titleText.setTextColor(iconColor);
1169
+
1170
+ // Set the title text
1171
+ if (!TextUtils.isEmpty(_options.getTitle())) {
1172
+ this.setTitle(_options.getTitle());
1173
+ } else {
1174
+ try {
1175
+ URI uri = new URI(_options.getUrl());
1176
+ this.setTitle(uri.getHost());
1177
+ } catch (URISyntaxException e) {
1178
+ this.setTitle(_options.getTitle());
1179
+ }
1180
+ }
1181
+ }
1182
+ }
1183
+
1184
+ /**
1185
+ * Applies background and tint colors to all buttons in the toolbar
1186
+ */
1187
+ private void applyColorToAllButtons(int backgroundColor, int iconColor) {
1188
+ // Get all buttons
1189
+ ImageButton backButton = _toolbar.findViewById(R.id.backButton);
1190
+ ImageButton forwardButton = _toolbar.findViewById(R.id.forwardButton);
1191
+ ImageButton closeButton = _toolbar.findViewById(R.id.closeButton);
1192
+ ImageButton reloadButton = _toolbar.findViewById(R.id.reloadButton);
1193
+ ImageButton shareButton = _toolbar.findViewById(R.id.shareButton);
1194
+ ImageButton buttonNearDoneView = _toolbar.findViewById(R.id.buttonNearDone);
1195
+
1196
+ // Set button backgrounds
1197
+ backButton.setBackgroundColor(backgroundColor);
1198
+ forwardButton.setBackgroundColor(backgroundColor);
1199
+ closeButton.setBackgroundColor(backgroundColor);
1200
+ reloadButton.setBackgroundColor(backgroundColor);
1201
+
1202
+ // Apply tint colors to buttons
1203
+ backButton.setColorFilter(iconColor);
1204
+ forwardButton.setColorFilter(iconColor);
1205
+ closeButton.setColorFilter(iconColor);
1206
+ reloadButton.setColorFilter(iconColor);
1207
+ shareButton.setColorFilter(iconColor);
1208
+ buttonNearDoneView.setColorFilter(iconColor);
652
1209
  }
653
1210
 
654
1211
  public void handleProxyResultError(String result, String id) {
@@ -838,7 +1395,7 @@ public class WebViewDialog extends Dialog {
838
1395
  );
839
1396
  }
840
1397
  String s = String.format(
841
- "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'})}",
1398
+ "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'})}",
842
1399
  headers,
843
1400
  toBase64(request.getUrl().toString()),
844
1401
  request.getMethod(),
@@ -1021,18 +1578,36 @@ public class WebViewDialog extends Dialog {
1021
1578
  if (_webView.canGoBack()) {
1022
1579
  backButton.setImageResource(R.drawable.arrow_back_enabled);
1023
1580
  backButton.setEnabled(true);
1581
+ backButton.setColorFilter(iconColor);
1024
1582
  } else {
1025
1583
  backButton.setImageResource(R.drawable.arrow_back_disabled);
1026
1584
  backButton.setEnabled(false);
1585
+ backButton.setColorFilter(
1586
+ Color.argb(
1587
+ 128,
1588
+ Color.red(iconColor),
1589
+ Color.green(iconColor),
1590
+ Color.blue(iconColor)
1591
+ )
1592
+ );
1027
1593
  }
1028
1594
 
1029
1595
  ImageButton forwardButton = _toolbar.findViewById(R.id.forwardButton);
1030
1596
  if (_webView.canGoForward()) {
1031
1597
  forwardButton.setImageResource(R.drawable.arrow_forward_enabled);
1032
1598
  forwardButton.setEnabled(true);
1599
+ forwardButton.setColorFilter(iconColor);
1033
1600
  } else {
1034
1601
  forwardButton.setImageResource(R.drawable.arrow_forward_disabled);
1035
1602
  forwardButton.setEnabled(false);
1603
+ forwardButton.setColorFilter(
1604
+ Color.argb(
1605
+ 128,
1606
+ Color.red(iconColor),
1607
+ Color.green(iconColor),
1608
+ Color.blue(iconColor)
1609
+ )
1610
+ );
1036
1611
  }
1037
1612
 
1038
1613
  _options.getCallbacks().pageLoaded();
@@ -1178,4 +1753,82 @@ public class WebViewDialog extends Dialog {
1178
1753
  proxiedRequestsHashmap.remove(key);
1179
1754
  }
1180
1755
  }
1756
+
1757
+ private void shareUrl() {
1758
+ Intent shareIntent = new Intent(Intent.ACTION_SEND);
1759
+ shareIntent.setType("text/plain");
1760
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT, _options.getShareSubject());
1761
+ shareIntent.putExtra(Intent.EXTRA_TEXT, _options.getUrl());
1762
+ _context.startActivity(Intent.createChooser(shareIntent, "Share"));
1763
+ }
1764
+
1765
+ private boolean isDarkColor(int color) {
1766
+ int red = Color.red(color);
1767
+ int green = Color.green(color);
1768
+ int blue = Color.blue(color);
1769
+ double luminance = (0.299 * red + 0.587 * green + 0.114 * blue) / 255.0;
1770
+ return luminance < 0.5;
1771
+ }
1772
+
1773
+ private boolean isDarkThemeEnabled() {
1774
+ // This method checks if dark theme is currently enabled without using Configuration class
1775
+ try {
1776
+ // On Android 10+, check via resources for night mode
1777
+ Resources.Theme theme = _context.getTheme();
1778
+ TypedValue typedValue = new TypedValue();
1779
+
1780
+ if (
1781
+ theme.resolveAttribute(android.R.attr.isLightTheme, typedValue, true)
1782
+ ) {
1783
+ // isLightTheme exists - returns true if light, false if dark
1784
+ return typedValue.data != 1;
1785
+ }
1786
+
1787
+ // Fallback method - check background color of window
1788
+ if (
1789
+ theme.resolveAttribute(
1790
+ android.R.attr.windowBackground,
1791
+ typedValue,
1792
+ true
1793
+ )
1794
+ ) {
1795
+ int backgroundColor = typedValue.data;
1796
+ return isDarkColor(backgroundColor);
1797
+ }
1798
+ } catch (Exception e) {
1799
+ // Ignore and fallback to light theme
1800
+ }
1801
+ return false;
1802
+ }
1803
+
1804
+ private void injectDatePickerFixes() {
1805
+ if (_webView == null || datePickerInjected) {
1806
+ return;
1807
+ }
1808
+
1809
+ datePickerInjected = true;
1810
+
1811
+ // This script adds minimal fixes for date inputs to use Material Design
1812
+ String script =
1813
+ "(function() {\n" +
1814
+ " // Find all date inputs\n" +
1815
+ " const dateInputs = document.querySelectorAll('input[type=\"date\"]');\n" +
1816
+ " dateInputs.forEach(input => {\n" +
1817
+ " // Ensure change events propagate correctly\n" +
1818
+ " let lastValue = input.value;\n" +
1819
+ " input.addEventListener('change', () => {\n" +
1820
+ " if (input.value !== lastValue) {\n" +
1821
+ " lastValue = input.value;\n" +
1822
+ " // Dispatch an input event to ensure frameworks detect the change\n" +
1823
+ " input.dispatchEvent(new Event('input', { bubbles: true }));\n" +
1824
+ " }\n" +
1825
+ " });\n" +
1826
+ " });\n" +
1827
+ "})();";
1828
+
1829
+ // Execute the script in the WebView
1830
+ _webView.post(() -> _webView.evaluateJavascript(script, null));
1831
+
1832
+ Log.d("InAppBrowser", "Applied minimal date picker fixes");
1833
+ }
1181
1834
  }