@capgo/inappbrowser 7.6.4 → 7.6.7
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 +22 -19
- package/android/src/main/AndroidManifest.xml +12 -1
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java +113 -78
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +527 -38
- package/android/src/main/res/xml/file_paths.xml +14 -0
- package/dist/docs.json +32 -60
- package/dist/esm/definitions.d.ts +1 -11
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/InAppBrowserPlugin.swift +38 -22
- package/ios/Plugin/WKWebViewController.swift +4 -107
- package/package.json +1 -1
|
@@ -19,6 +19,8 @@ import android.graphics.PorterDuffColorFilter;
|
|
|
19
19
|
import android.net.Uri;
|
|
20
20
|
import android.net.http.SslError;
|
|
21
21
|
import android.os.Build;
|
|
22
|
+
import android.os.Environment;
|
|
23
|
+
import android.provider.MediaStore;
|
|
22
24
|
import android.text.TextUtils;
|
|
23
25
|
import android.util.Base64;
|
|
24
26
|
import android.util.Log;
|
|
@@ -43,6 +45,9 @@ import android.widget.ImageView;
|
|
|
43
45
|
import android.widget.TextView;
|
|
44
46
|
import android.widget.Toast;
|
|
45
47
|
import android.widget.Toolbar;
|
|
48
|
+
import androidx.activity.result.ActivityResult;
|
|
49
|
+
import androidx.activity.result.contract.ActivityResultContracts;
|
|
50
|
+
import androidx.core.content.FileProvider;
|
|
46
51
|
import androidx.core.graphics.Insets;
|
|
47
52
|
import androidx.core.view.ViewCompat;
|
|
48
53
|
import androidx.core.view.WindowInsetsCompat;
|
|
@@ -51,6 +56,7 @@ import com.caverock.androidsvg.SVG;
|
|
|
51
56
|
import com.caverock.androidsvg.SVGParseException;
|
|
52
57
|
import com.getcapacitor.JSObject;
|
|
53
58
|
import java.io.ByteArrayInputStream;
|
|
59
|
+
import java.io.File;
|
|
54
60
|
import java.io.IOException;
|
|
55
61
|
import java.io.InputStream;
|
|
56
62
|
import java.net.URI;
|
|
@@ -110,6 +116,9 @@ public class WebViewDialog extends Dialog {
|
|
|
110
116
|
public ValueCallback<Uri> mUploadMessage;
|
|
111
117
|
public ValueCallback<Uri[]> mFilePathCallback;
|
|
112
118
|
|
|
119
|
+
// Temporary URI for storing camera capture
|
|
120
|
+
public Uri tempCameraUri;
|
|
121
|
+
|
|
113
122
|
public interface PermissionHandler {
|
|
114
123
|
void handleCameraPermissionRequest(PermissionRequest request);
|
|
115
124
|
|
|
@@ -336,21 +345,206 @@ public class WebViewDialog extends Dialog {
|
|
|
336
345
|
FileChooserParams fileChooserParams
|
|
337
346
|
) {
|
|
338
347
|
// Get the accept type safely
|
|
339
|
-
String acceptType
|
|
348
|
+
String acceptType;
|
|
340
349
|
if (
|
|
341
350
|
fileChooserParams.getAcceptTypes() != null &&
|
|
342
351
|
fileChooserParams.getAcceptTypes().length > 0 &&
|
|
343
352
|
!TextUtils.isEmpty(fileChooserParams.getAcceptTypes()[0])
|
|
344
353
|
) {
|
|
345
354
|
acceptType = fileChooserParams.getAcceptTypes()[0];
|
|
355
|
+
} else {
|
|
356
|
+
acceptType = "*/*";
|
|
346
357
|
}
|
|
347
358
|
|
|
359
|
+
// DEBUG: Log details about the file chooser request
|
|
360
|
+
Log.d("InAppBrowser", "onShowFileChooser called");
|
|
361
|
+
Log.d("InAppBrowser", "Accept type: " + acceptType);
|
|
362
|
+
Log.d(
|
|
363
|
+
"InAppBrowser",
|
|
364
|
+
"Current URL: " +
|
|
365
|
+
(webView.getUrl() != null ? webView.getUrl() : "null")
|
|
366
|
+
);
|
|
367
|
+
Log.d(
|
|
368
|
+
"InAppBrowser",
|
|
369
|
+
"Original URL: " +
|
|
370
|
+
(webView.getOriginalUrl() != null
|
|
371
|
+
? webView.getOriginalUrl()
|
|
372
|
+
: "null")
|
|
373
|
+
);
|
|
374
|
+
Log.d(
|
|
375
|
+
"InAppBrowser",
|
|
376
|
+
"Has camera permission: " +
|
|
377
|
+
(activity != null &&
|
|
378
|
+
activity.checkSelfPermission(
|
|
379
|
+
android.Manifest.permission.CAMERA
|
|
380
|
+
) ==
|
|
381
|
+
android.content.pm.PackageManager.PERMISSION_GRANTED)
|
|
382
|
+
);
|
|
383
|
+
|
|
348
384
|
// Check if the file chooser is already open
|
|
349
385
|
if (mFilePathCallback != null) {
|
|
350
386
|
mFilePathCallback.onReceiveValue(null);
|
|
351
387
|
mFilePathCallback = null;
|
|
352
388
|
}
|
|
353
389
|
|
|
390
|
+
mFilePathCallback = filePathCallback;
|
|
391
|
+
|
|
392
|
+
// Direct check for capture attribute in URL (fallback method)
|
|
393
|
+
boolean isCaptureInUrl;
|
|
394
|
+
String captureMode;
|
|
395
|
+
String currentUrl = webView.getUrl();
|
|
396
|
+
|
|
397
|
+
// Look for capture in URL parameters - sometimes the attribute shows up in URL
|
|
398
|
+
if (currentUrl != null && currentUrl.contains("capture=")) {
|
|
399
|
+
isCaptureInUrl = true;
|
|
400
|
+
captureMode = currentUrl.contains("capture=user")
|
|
401
|
+
? "user"
|
|
402
|
+
: "environment";
|
|
403
|
+
Log.d("InAppBrowser", "Found capture in URL: " + captureMode);
|
|
404
|
+
} else {
|
|
405
|
+
captureMode = null;
|
|
406
|
+
isCaptureInUrl = false;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// For image inputs, try to detect capture attribute using JavaScript
|
|
410
|
+
if (acceptType.equals("image/*")) {
|
|
411
|
+
// Check if HTML content contains capture attribute on file inputs (synchronous check)
|
|
412
|
+
webView.evaluateJavascript(
|
|
413
|
+
"document.querySelector('input[type=\"file\"][capture]') !== null",
|
|
414
|
+
hasCaptureValue -> {
|
|
415
|
+
Log.d(
|
|
416
|
+
"InAppBrowser",
|
|
417
|
+
"Quick capture check: " + hasCaptureValue
|
|
418
|
+
);
|
|
419
|
+
if (Boolean.parseBoolean(hasCaptureValue.replace("\"", ""))) {
|
|
420
|
+
Log.d(
|
|
421
|
+
"InAppBrowser",
|
|
422
|
+
"Found capture attribute in quick check"
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
// Fixed JavaScript with proper error handling
|
|
429
|
+
String js =
|
|
430
|
+
"try {" +
|
|
431
|
+
" (function() {" +
|
|
432
|
+
" var captureAttr = null;" +
|
|
433
|
+
" // Check active element first" +
|
|
434
|
+
" if (document.activeElement && " +
|
|
435
|
+
" document.activeElement.tagName === 'INPUT' && " +
|
|
436
|
+
" document.activeElement.type === 'file') {" +
|
|
437
|
+
" if (document.activeElement.hasAttribute('capture')) {" +
|
|
438
|
+
" captureAttr = document.activeElement.getAttribute('capture') || 'environment';" +
|
|
439
|
+
" return captureAttr;" +
|
|
440
|
+
" }" +
|
|
441
|
+
" }" +
|
|
442
|
+
" // Try to find any input with capture attribute" +
|
|
443
|
+
" var inputs = document.querySelectorAll('input[type=\"file\"][capture]');" +
|
|
444
|
+
" if (inputs && inputs.length > 0) {" +
|
|
445
|
+
" captureAttr = inputs[0].getAttribute('capture') || 'environment';" +
|
|
446
|
+
" return captureAttr;" +
|
|
447
|
+
" }" +
|
|
448
|
+
" // Try to extract from HTML attributes" +
|
|
449
|
+
" var allInputs = document.getElementsByTagName('input');" +
|
|
450
|
+
" for (var i = 0; i < allInputs.length; i++) {" +
|
|
451
|
+
" var input = allInputs[i];" +
|
|
452
|
+
" if (input.type === 'file') {" +
|
|
453
|
+
" if (input.hasAttribute('capture')) {" +
|
|
454
|
+
" captureAttr = input.getAttribute('capture') || 'environment';" +
|
|
455
|
+
" return captureAttr;" +
|
|
456
|
+
" }" +
|
|
457
|
+
" // Look for the accept attribute containing image/* as this might be a camera input" +
|
|
458
|
+
" var acceptAttr = input.getAttribute('accept');" +
|
|
459
|
+
" if (acceptAttr && acceptAttr.indexOf('image/*') >= 0) {" +
|
|
460
|
+
" console.log('Found input with image/* accept');" +
|
|
461
|
+
" }" +
|
|
462
|
+
" }" +
|
|
463
|
+
" }" +
|
|
464
|
+
" return '';" +
|
|
465
|
+
" })();" +
|
|
466
|
+
"} catch(e) { console.error('Capture detection error:', e); return ''; }";
|
|
467
|
+
|
|
468
|
+
webView.evaluateJavascript(js, value -> {
|
|
469
|
+
Log.d("InAppBrowser", "Capture attribute JS result: " + value);
|
|
470
|
+
|
|
471
|
+
// If we already found capture in URL, use that directly
|
|
472
|
+
if (isCaptureInUrl) {
|
|
473
|
+
Log.d("InAppBrowser", "Using capture from URL: " + captureMode);
|
|
474
|
+
launchCamera(captureMode.equals("user"));
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Process JavaScript result
|
|
479
|
+
if (value != null && value.length() > 2) {
|
|
480
|
+
// Clean up the value (remove quotes)
|
|
481
|
+
String captureValue = value.replace("\"", "");
|
|
482
|
+
Log.d(
|
|
483
|
+
"InAppBrowser",
|
|
484
|
+
"Found capture attribute: " + captureValue
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
if (!captureValue.isEmpty()) {
|
|
488
|
+
activity.runOnUiThread(() ->
|
|
489
|
+
launchCamera(captureValue.equals("user"))
|
|
490
|
+
);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Look for hints in the web page source
|
|
496
|
+
Log.d("InAppBrowser", "Looking for camera hints in page content");
|
|
497
|
+
webView.evaluateJavascript(
|
|
498
|
+
"(function() { return document.documentElement.innerHTML; })()",
|
|
499
|
+
htmlSource -> {
|
|
500
|
+
if (htmlSource != null && htmlSource.length() > 10) {
|
|
501
|
+
boolean hasCameraOrSelfieKeyword =
|
|
502
|
+
htmlSource.contains("capture=") ||
|
|
503
|
+
htmlSource.contains("camera") ||
|
|
504
|
+
htmlSource.contains("selfie");
|
|
505
|
+
|
|
506
|
+
Log.d(
|
|
507
|
+
"InAppBrowser",
|
|
508
|
+
"Page contains camera keywords: " +
|
|
509
|
+
hasCameraOrSelfieKeyword
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
if (
|
|
513
|
+
hasCameraOrSelfieKeyword &&
|
|
514
|
+
currentUrl != null &&
|
|
515
|
+
(currentUrl.contains("selfie") ||
|
|
516
|
+
currentUrl.contains("camera") ||
|
|
517
|
+
currentUrl.contains("photo"))
|
|
518
|
+
) {
|
|
519
|
+
Log.d(
|
|
520
|
+
"InAppBrowser",
|
|
521
|
+
"URL suggests camera usage, launching camera"
|
|
522
|
+
);
|
|
523
|
+
activity.runOnUiThread(() ->
|
|
524
|
+
launchCamera(currentUrl.contains("selfie"))
|
|
525
|
+
);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// If all detection methods fail, fall back to regular file picker
|
|
531
|
+
Log.d(
|
|
532
|
+
"InAppBrowser",
|
|
533
|
+
"No capture attribute detected, using file picker"
|
|
534
|
+
);
|
|
535
|
+
openFileChooser(
|
|
536
|
+
filePathCallback,
|
|
537
|
+
acceptType,
|
|
538
|
+
fileChooserParams.getMode() ==
|
|
539
|
+
FileChooserParams.MODE_OPEN_MULTIPLE
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
);
|
|
543
|
+
});
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// For non-image types, use regular file picker
|
|
354
548
|
openFileChooser(
|
|
355
549
|
filePathCallback,
|
|
356
550
|
acceptType,
|
|
@@ -359,6 +553,160 @@ public class WebViewDialog extends Dialog {
|
|
|
359
553
|
return true;
|
|
360
554
|
}
|
|
361
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Launch the camera app for capturing images
|
|
558
|
+
* @param useFrontCamera true to use front camera, false for back camera
|
|
559
|
+
*/
|
|
560
|
+
private void launchCamera(boolean useFrontCamera) {
|
|
561
|
+
Log.d(
|
|
562
|
+
"InAppBrowser",
|
|
563
|
+
"Launching camera, front camera: " + useFrontCamera
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
// First check if we have camera permission
|
|
567
|
+
if (activity != null && permissionHandler != null) {
|
|
568
|
+
// Create a temporary permission request to check camera permission
|
|
569
|
+
android.webkit.PermissionRequest tempRequest =
|
|
570
|
+
new android.webkit.PermissionRequest() {
|
|
571
|
+
@Override
|
|
572
|
+
public Uri getOrigin() {
|
|
573
|
+
return Uri.parse("file:///android_asset/");
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
@Override
|
|
577
|
+
public String[] getResources() {
|
|
578
|
+
return new String[] {
|
|
579
|
+
PermissionRequest.RESOURCE_VIDEO_CAPTURE,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
@Override
|
|
584
|
+
public void grant(String[] resources) {
|
|
585
|
+
// Permission granted, now launch the camera
|
|
586
|
+
launchCameraWithPermission(useFrontCamera);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
@Override
|
|
590
|
+
public void deny() {
|
|
591
|
+
// Permission denied, fall back to file picker
|
|
592
|
+
Log.e(
|
|
593
|
+
"InAppBrowser",
|
|
594
|
+
"Camera permission denied, falling back to file picker"
|
|
595
|
+
);
|
|
596
|
+
fallbackToFilePicker();
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// Request camera permission through the plugin
|
|
601
|
+
permissionHandler.handleCameraPermissionRequest(tempRequest);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// If we can't request permission, try launching directly
|
|
606
|
+
launchCameraWithPermission(useFrontCamera);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Launch camera after permission is granted
|
|
611
|
+
*/
|
|
612
|
+
private void launchCameraWithPermission(boolean useFrontCamera) {
|
|
613
|
+
try {
|
|
614
|
+
Intent takePictureIntent = new Intent(
|
|
615
|
+
MediaStore.ACTION_IMAGE_CAPTURE
|
|
616
|
+
);
|
|
617
|
+
if (
|
|
618
|
+
takePictureIntent.resolveActivity(activity.getPackageManager()) !=
|
|
619
|
+
null
|
|
620
|
+
) {
|
|
621
|
+
File photoFile = null;
|
|
622
|
+
try {
|
|
623
|
+
photoFile = createImageFile();
|
|
624
|
+
} catch (IOException ex) {
|
|
625
|
+
Log.e("InAppBrowser", "Error creating image file", ex);
|
|
626
|
+
fallbackToFilePicker();
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (photoFile != null) {
|
|
631
|
+
tempCameraUri = FileProvider.getUriForFile(
|
|
632
|
+
activity,
|
|
633
|
+
activity.getPackageName() + ".fileprovider",
|
|
634
|
+
photoFile
|
|
635
|
+
);
|
|
636
|
+
takePictureIntent.putExtra(
|
|
637
|
+
MediaStore.EXTRA_OUTPUT,
|
|
638
|
+
tempCameraUri
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
if (useFrontCamera) {
|
|
642
|
+
takePictureIntent.putExtra(
|
|
643
|
+
"android.intent.extras.CAMERA_FACING",
|
|
644
|
+
1
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
try {
|
|
649
|
+
if (activity instanceof androidx.activity.ComponentActivity) {
|
|
650
|
+
androidx.activity.ComponentActivity componentActivity =
|
|
651
|
+
(androidx.activity.ComponentActivity) activity;
|
|
652
|
+
componentActivity
|
|
653
|
+
.getActivityResultRegistry()
|
|
654
|
+
.register(
|
|
655
|
+
"camera_capture",
|
|
656
|
+
new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
|
|
657
|
+
result -> {
|
|
658
|
+
if (result.getResultCode() == Activity.RESULT_OK) {
|
|
659
|
+
if (tempCameraUri != null) {
|
|
660
|
+
mFilePathCallback.onReceiveValue(
|
|
661
|
+
new Uri[] { tempCameraUri }
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
} else {
|
|
665
|
+
mFilePathCallback.onReceiveValue(null);
|
|
666
|
+
}
|
|
667
|
+
mFilePathCallback = null;
|
|
668
|
+
tempCameraUri = null;
|
|
669
|
+
}
|
|
670
|
+
)
|
|
671
|
+
.launch(takePictureIntent);
|
|
672
|
+
} else {
|
|
673
|
+
// Fallback for non-ComponentActivity
|
|
674
|
+
activity.startActivityForResult(
|
|
675
|
+
takePictureIntent,
|
|
676
|
+
FILE_CHOOSER_REQUEST_CODE
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
} catch (SecurityException e) {
|
|
680
|
+
Log.e(
|
|
681
|
+
"InAppBrowser",
|
|
682
|
+
"Security exception launching camera: " + e.getMessage(),
|
|
683
|
+
e
|
|
684
|
+
);
|
|
685
|
+
fallbackToFilePicker();
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
Log.e(
|
|
689
|
+
"InAppBrowser",
|
|
690
|
+
"Failed to create photo URI, falling back to file picker"
|
|
691
|
+
);
|
|
692
|
+
fallbackToFilePicker();
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
} catch (Exception e) {
|
|
696
|
+
Log.e("InAppBrowser", "Camera launch failed: " + e.getMessage(), e);
|
|
697
|
+
fallbackToFilePicker();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Fall back to file picker when camera launch fails
|
|
703
|
+
*/
|
|
704
|
+
private void fallbackToFilePicker() {
|
|
705
|
+
if (mFilePathCallback != null) {
|
|
706
|
+
openFileChooser(mFilePathCallback, "image/*", false);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
362
710
|
// Grant permissions for cam
|
|
363
711
|
@Override
|
|
364
712
|
public void onPermissionRequest(final PermissionRequest request) {
|
|
@@ -479,11 +827,8 @@ public class WebViewDialog extends Dialog {
|
|
|
479
827
|
if (
|
|
480
828
|
toolbarView != null &&
|
|
481
829
|
toolbarView.getParent() instanceof
|
|
482
|
-
com.google.android.material.appbar.AppBarLayout
|
|
830
|
+
com.google.android.material.appbar.AppBarLayout appBarLayout
|
|
483
831
|
) {
|
|
484
|
-
com.google.android.material.appbar.AppBarLayout appBarLayout =
|
|
485
|
-
(com.google.android.material.appbar.AppBarLayout) toolbarView.getParent();
|
|
486
|
-
|
|
487
832
|
// Remove elevation to eliminate shadows (only on Android 15+)
|
|
488
833
|
appBarLayout.setElevation(0);
|
|
489
834
|
appBarLayout.setStateListAnimator(null);
|
|
@@ -703,7 +1048,7 @@ public class WebViewDialog extends Dialog {
|
|
|
703
1048
|
_options.getPreShowScript() +
|
|
704
1049
|
'\n' +
|
|
705
1050
|
"};\n" +
|
|
706
|
-
"preShowFunction().then(() => window.PreShowScriptInterface.success()).catch(err => { console.error('
|
|
1051
|
+
"preShowFunction().then(() => window.PreShowScriptInterface.success()).catch(err => { console.error('Pre show error', err); window.PreShowScriptInterface.error(JSON.stringify(err, Object.getOwnPropertyNames(err))) })";
|
|
707
1052
|
|
|
708
1053
|
Log.i(
|
|
709
1054
|
"InjectPreShowScript",
|
|
@@ -804,10 +1149,47 @@ public class WebViewDialog extends Dialog {
|
|
|
804
1149
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultiple);
|
|
805
1150
|
|
|
806
1151
|
try {
|
|
807
|
-
activity.
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1152
|
+
if (activity instanceof androidx.activity.ComponentActivity) {
|
|
1153
|
+
androidx.activity.ComponentActivity componentActivity =
|
|
1154
|
+
(androidx.activity.ComponentActivity) activity;
|
|
1155
|
+
componentActivity
|
|
1156
|
+
.getActivityResultRegistry()
|
|
1157
|
+
.register(
|
|
1158
|
+
"file_chooser",
|
|
1159
|
+
new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
|
|
1160
|
+
result -> {
|
|
1161
|
+
if (result.getResultCode() == Activity.RESULT_OK) {
|
|
1162
|
+
Intent data = result.getData();
|
|
1163
|
+
if (data != null) {
|
|
1164
|
+
if (data.getClipData() != null) {
|
|
1165
|
+
// Handle multiple files
|
|
1166
|
+
int count = data.getClipData().getItemCount();
|
|
1167
|
+
Uri[] results = new Uri[count];
|
|
1168
|
+
for (int i = 0; i < count; i++) {
|
|
1169
|
+
results[i] = data.getClipData().getItemAt(i).getUri();
|
|
1170
|
+
}
|
|
1171
|
+
mFilePathCallback.onReceiveValue(results);
|
|
1172
|
+
} else if (data.getData() != null) {
|
|
1173
|
+
// Handle single file
|
|
1174
|
+
mFilePathCallback.onReceiveValue(
|
|
1175
|
+
new Uri[] { data.getData() }
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
} else {
|
|
1180
|
+
mFilePathCallback.onReceiveValue(null);
|
|
1181
|
+
}
|
|
1182
|
+
mFilePathCallback = null;
|
|
1183
|
+
}
|
|
1184
|
+
)
|
|
1185
|
+
.launch(Intent.createChooser(intent, "Select File"));
|
|
1186
|
+
} else {
|
|
1187
|
+
// Fallback for non-ComponentActivity
|
|
1188
|
+
activity.startActivityForResult(
|
|
1189
|
+
Intent.createChooser(intent, "Select File"),
|
|
1190
|
+
FILE_CHOOSER_REQUEST_CODE
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
811
1193
|
} catch (ActivityNotFoundException e) {
|
|
812
1194
|
// If no app can handle the specific MIME type, try with a more generic one
|
|
813
1195
|
Log.e(
|
|
@@ -816,10 +1198,47 @@ public class WebViewDialog extends Dialog {
|
|
|
816
1198
|
);
|
|
817
1199
|
intent.setType("*/*");
|
|
818
1200
|
try {
|
|
819
|
-
activity.
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1201
|
+
if (activity instanceof androidx.activity.ComponentActivity) {
|
|
1202
|
+
androidx.activity.ComponentActivity componentActivity =
|
|
1203
|
+
(androidx.activity.ComponentActivity) activity;
|
|
1204
|
+
componentActivity
|
|
1205
|
+
.getActivityResultRegistry()
|
|
1206
|
+
.register(
|
|
1207
|
+
"file_chooser",
|
|
1208
|
+
new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
|
|
1209
|
+
result -> {
|
|
1210
|
+
if (result.getResultCode() == Activity.RESULT_OK) {
|
|
1211
|
+
Intent data = result.getData();
|
|
1212
|
+
if (data != null) {
|
|
1213
|
+
if (data.getClipData() != null) {
|
|
1214
|
+
// Handle multiple files
|
|
1215
|
+
int count = data.getClipData().getItemCount();
|
|
1216
|
+
Uri[] results = new Uri[count];
|
|
1217
|
+
for (int i = 0; i < count; i++) {
|
|
1218
|
+
results[i] = data.getClipData().getItemAt(i).getUri();
|
|
1219
|
+
}
|
|
1220
|
+
mFilePathCallback.onReceiveValue(results);
|
|
1221
|
+
} else if (data.getData() != null) {
|
|
1222
|
+
// Handle single file
|
|
1223
|
+
mFilePathCallback.onReceiveValue(
|
|
1224
|
+
new Uri[] { data.getData() }
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
} else {
|
|
1229
|
+
mFilePathCallback.onReceiveValue(null);
|
|
1230
|
+
}
|
|
1231
|
+
mFilePathCallback = null;
|
|
1232
|
+
}
|
|
1233
|
+
)
|
|
1234
|
+
.launch(Intent.createChooser(intent, "Select File"));
|
|
1235
|
+
} else {
|
|
1236
|
+
// Fallback for non-ComponentActivity
|
|
1237
|
+
activity.startActivityForResult(
|
|
1238
|
+
Intent.createChooser(intent, "Select File"),
|
|
1239
|
+
FILE_CHOOSER_REQUEST_CODE
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
823
1242
|
} catch (ActivityNotFoundException ex) {
|
|
824
1243
|
// If still failing, report error
|
|
825
1244
|
Log.e("InAppBrowser", "No app can handle file picker", ex);
|
|
@@ -1191,15 +1610,6 @@ public class WebViewDialog extends Dialog {
|
|
|
1191
1610
|
}
|
|
1192
1611
|
}
|
|
1193
1612
|
|
|
1194
|
-
if (inputStream == null) {
|
|
1195
|
-
Log.e(
|
|
1196
|
-
"InAppBrowser",
|
|
1197
|
-
"Failed to load SVG icon: " + buttonNearDone.getIcon()
|
|
1198
|
-
);
|
|
1199
|
-
buttonNearDoneView.setVisibility(View.GONE);
|
|
1200
|
-
return;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
1613
|
// Parse and render SVG
|
|
1204
1614
|
SVG svg = SVG.getFromInputStream(inputStream);
|
|
1205
1615
|
if (svg == null) {
|
|
@@ -1880,6 +2290,18 @@ public class WebViewDialog extends Dialog {
|
|
|
1880
2290
|
@Override
|
|
1881
2291
|
public void dismiss() {
|
|
1882
2292
|
if (_webView != null) {
|
|
2293
|
+
// Reset file inputs to prevent WebView from caching them
|
|
2294
|
+
_webView.evaluateJavascript(
|
|
2295
|
+
"(function() {" +
|
|
2296
|
+
" var inputs = document.querySelectorAll('input[type=\"file\"]');" +
|
|
2297
|
+
" for (var i = 0; i < inputs.length; i++) {" +
|
|
2298
|
+
" inputs[i].value = '';" +
|
|
2299
|
+
" }" +
|
|
2300
|
+
" return true;" +
|
|
2301
|
+
"})();",
|
|
2302
|
+
null
|
|
2303
|
+
);
|
|
2304
|
+
|
|
1883
2305
|
_webView.loadUrl("about:blank");
|
|
1884
2306
|
_webView.onPause();
|
|
1885
2307
|
_webView.removeAllViews();
|
|
@@ -1977,25 +2399,92 @@ public class WebViewDialog extends Dialog {
|
|
|
1977
2399
|
|
|
1978
2400
|
// This script adds minimal fixes for date inputs to use Material Design
|
|
1979
2401
|
String script =
|
|
1980
|
-
"
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2402
|
+
"""
|
|
2403
|
+
(function() {
|
|
2404
|
+
// Find all date inputs
|
|
2405
|
+
const dateInputs = document.querySelectorAll('input[type="date"]');
|
|
2406
|
+
dateInputs.forEach(input => {
|
|
2407
|
+
// Ensure change events propagate correctly
|
|
2408
|
+
let lastValue = input.value;
|
|
2409
|
+
input.addEventListener('change', () => {
|
|
2410
|
+
if (input.value !== lastValue) {
|
|
2411
|
+
lastValue = input.value;
|
|
2412
|
+
// Dispatch an input event to ensure frameworks detect the change
|
|
2413
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
});
|
|
2417
|
+
})();""";
|
|
1995
2418
|
|
|
1996
2419
|
// Execute the script in the WebView
|
|
1997
2420
|
_webView.post(() -> _webView.evaluateJavascript(script, null));
|
|
1998
2421
|
|
|
1999
2422
|
Log.d("InAppBrowser", "Applied minimal date picker fixes");
|
|
2000
2423
|
}
|
|
2424
|
+
|
|
2425
|
+
/**
|
|
2426
|
+
* Creates a temporary URI for storing camera capture
|
|
2427
|
+
* @return URI for the temporary file or null if creation failed
|
|
2428
|
+
*/
|
|
2429
|
+
private Uri createTempImageUri() {
|
|
2430
|
+
try {
|
|
2431
|
+
String fileName = "capture_" + System.currentTimeMillis() + ".jpg";
|
|
2432
|
+
java.io.File cacheDir = _context.getCacheDir();
|
|
2433
|
+
|
|
2434
|
+
// Make sure cache directory exists
|
|
2435
|
+
if (!cacheDir.exists() && !cacheDir.mkdirs()) {
|
|
2436
|
+
return null;
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
// Create temporary file
|
|
2440
|
+
java.io.File tempFile = new java.io.File(cacheDir, fileName);
|
|
2441
|
+
if (!tempFile.createNewFile()) {
|
|
2442
|
+
return null;
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
// Get content URI through FileProvider
|
|
2446
|
+
try {
|
|
2447
|
+
return androidx.core.content.FileProvider.getUriForFile(
|
|
2448
|
+
_context,
|
|
2449
|
+
_context.getPackageName() + ".fileprovider",
|
|
2450
|
+
tempFile
|
|
2451
|
+
);
|
|
2452
|
+
} catch (IllegalArgumentException e) {
|
|
2453
|
+
// Try using external storage as fallback
|
|
2454
|
+
java.io.File externalCacheDir = _context.getExternalCacheDir();
|
|
2455
|
+
if (externalCacheDir != null) {
|
|
2456
|
+
tempFile = new java.io.File(externalCacheDir, fileName);
|
|
2457
|
+
final boolean newFile = tempFile.createNewFile();
|
|
2458
|
+
if (!newFile) {
|
|
2459
|
+
Log.d("InAppBrowser", "Error creating new file");
|
|
2460
|
+
}
|
|
2461
|
+
return androidx.core.content.FileProvider.getUriForFile(
|
|
2462
|
+
_context,
|
|
2463
|
+
_context.getPackageName() + ".fileprovider",
|
|
2464
|
+
tempFile
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
return null;
|
|
2469
|
+
} catch (Exception e) {
|
|
2470
|
+
return null;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
private File createImageFile() throws IOException {
|
|
2475
|
+
// Create an image file name
|
|
2476
|
+
String timeStamp = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(
|
|
2477
|
+
new java.util.Date()
|
|
2478
|
+
);
|
|
2479
|
+
String imageFileName = "JPEG_" + timeStamp + "_";
|
|
2480
|
+
File storageDir = activity.getExternalFilesDir(
|
|
2481
|
+
Environment.DIRECTORY_PICTURES
|
|
2482
|
+
);
|
|
2483
|
+
File image = File.createTempFile(
|
|
2484
|
+
imageFileName,/* prefix */
|
|
2485
|
+
".jpg",/* suffix */
|
|
2486
|
+
storageDir/* directory */
|
|
2487
|
+
);
|
|
2488
|
+
return image;
|
|
2489
|
+
}
|
|
2001
2490
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<!-- Internal cache for most operations -->
|
|
4
|
+
<cache-path name="camera_captures" path="." />
|
|
5
|
+
|
|
6
|
+
<!-- External cache for fallback -->
|
|
7
|
+
<external-cache-path name="external_cache" path="." />
|
|
8
|
+
|
|
9
|
+
<!-- External files for additional compatibility -->
|
|
10
|
+
<external-files-path name="external_files" path="." />
|
|
11
|
+
|
|
12
|
+
<!-- Allow sharing app-specific files if needed -->
|
|
13
|
+
<files-path name="app_files" path="." />
|
|
14
|
+
</paths>
|