cordova-plugin-mserviceinterface-2.0 1.0.93 → 1.0.95

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cordova-plugin-mserviceinterface-2.0",
3
- "version": "1.0.93",
3
+ "version": "1.0.95",
4
4
  "description": "This plugin establishes the connection to the native interface and performs the requested task.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/plugin.xml CHANGED
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
3
- xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-mserviceinterface-2.0" version="1.0.93">
3
+ xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-mserviceinterface-2.0" version="1.0.95">
4
4
  <name>mserviceinterface-2.0</name>
5
5
  <description>This plugin establishes the connection to the native interface and performs the requested task.</description>
6
6
  <author>selfservit@gmail.com</author>
@@ -47,14 +47,11 @@
47
47
  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
48
48
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
49
49
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
50
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
51
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
52
50
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
53
51
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
54
52
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
55
53
  <uses-permission android:name="android.permission.READ_PHONE_STATE" />
56
54
  <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
57
- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
58
55
  <queries>
59
56
  <intent>
60
57
  <action android:name="android.media.action.IMAGE_CAPTURE" />
@@ -486,11 +486,12 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
486
486
  } else if (action.equals("CheckLocationPermission")) {
487
487
  checkLocationPermission(args, callbackContext);
488
488
  return true;
489
- } else if (action.equals("GetMediaImagePermission")) {
490
- getMediaImagePermission(args, callbackContext);
491
- return true;
492
- } else if (action.equals("CheckMediaImagePermission")) {
493
- checkMediaImagePermission(args, callbackContext);
489
+ } else if (action.equals("CheckMediaImagePermission") || action.equals("GetMediaImagePermission")) {
490
+ try {
491
+ callbackContext.success("not_required");
492
+ } catch (Exception e) {
493
+ callbackContext.error(e.getMessage());
494
+ }
494
495
  return true;
495
496
  } else if (action.equals("AppVersion")) {
496
497
  appVersion(callbackContext);
@@ -1606,40 +1607,12 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
1606
1607
  }
1607
1608
  }
1608
1609
 
1609
- private void checkMediaImagePermission(JSONArray args, CallbackContext callbackContext) {
1610
- boolean permissionGranted = true, checkPermission = true;
1611
- List<String> list;
1612
- try {
1613
- callback = callbackContext;
1614
- Context context = cordova.getActivity().getApplicationContext();
1615
- String[] permissions = args.optJSONObject(0).optString("permissions").split(",");
1616
- if (permissions[0].equalsIgnoreCase("android.permission.READ_MEDIA_IMAGES")) {
1617
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
1618
- checkPermission = false;
1619
- }
1620
- }
1621
- if (checkPermission) {
1622
- list = new ArrayList<String>(Arrays.asList(permissions));
1623
- permissions = list.toArray(new String[0]);
1624
-
1625
- for (int i = 0; i < permissions.length; i++) {
1626
- if (ActivityCompat.checkSelfPermission(context, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
1627
- permissionGranted = false;
1628
- }
1629
- }
1630
- if (!permissionGranted) {
1631
- callback.error("failure");
1632
- } else {
1633
- callback.success("success");
1634
- }
1635
- } else {
1636
- callback.success("success");
1637
- }
1638
- } catch (Exception e) {
1639
- e.printStackTrace();
1640
- }
1610
+ private void checkMediaImagePermission(JSONArray args, CallbackContext cb) {
1611
+ try { cb.success("not_required"); } catch (Exception e) { cb.error(e.getMessage()); }
1612
+ }
1613
+ private void getMediaImagePermission(JSONArray args, CallbackContext cb) {
1614
+ try { cb.success("not_required"); } catch (Exception e) { cb.error(e.getMessage()); }
1641
1615
  }
1642
-
1643
1616
 
1644
1617
  private void getLocationPermission(JSONArray args, CallbackContext callbackContext) {
1645
1618
  boolean permissionGranted = true;
@@ -1665,30 +1638,7 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
1665
1638
  e.printStackTrace();
1666
1639
  }
1667
1640
  }
1668
- private void getMediaImagePermission(JSONArray args, CallbackContext callbackContext) {
1669
- boolean permissionGranted = true;
1670
- List<String> list;
1671
- try {
1672
- callback = callbackContext;
1673
- Context context = cordova.getActivity().getApplicationContext();
1674
- String[] permissions = args.optJSONObject(0).optString("permissions").split(",");
1675
- list = new ArrayList<String>(Arrays.asList(permissions));
1676
- permissions = list.toArray(new String[0]);
1677
-
1678
- for (int i = 0; i < permissions.length; i++) {
1679
- if (ActivityCompat.checkSelfPermission(context, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
1680
- permissionGranted = false;
1681
- }
1682
- }
1683
- if (!permissionGranted) {
1684
- PermissionHelper.requestPermissions(this, 111, permissions);
1685
- } else {
1686
- callback.success("success");
1687
- }
1688
- } catch (Exception e) {
1689
- e.printStackTrace();
1690
- }
1691
- }
1641
+
1692
1642
  private void getPermissions(JSONArray args, CallbackContext callbackContext) {
1693
1643
  boolean permissionGranted = true;
1694
1644
  List<String> list;
@@ -1760,29 +1710,33 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
1760
1710
  pluginResult.setKeepCallback(true);
1761
1711
  callbackContext.sendPluginResult(pluginResult);
1762
1712
  }
1763
- public void pickImageFromGallery(CallbackContext callbackContext) {
1713
+
1714
+ void pickImageFromGallery(CallbackContext callbackContext) {
1764
1715
  try {
1765
- Intent openFile,
1766
- selectFile;
1767
- PluginResult pluginResult;
1768
- callback = callbackContext;
1769
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
1770
- // Use the ACTION_OPEN_DOCUMENT intent for Android 13 and higher
1771
- openFile = new Intent(MediaStore.ACTION_PICK_IMAGES);
1772
- openFile.setType("image/*");
1716
+ Intent i;
1717
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
1718
+ i = new Intent(MediaStore.ACTION_PICK_IMAGES);
1719
+ i.setType("image/*");
1773
1720
  } else {
1774
- openFile = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
1775
- openFile.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
1721
+ i = new Intent(Intent.ACTION_OPEN_DOCUMENT);
1722
+ i.addCategory(Intent.CATEGORY_OPENABLE);
1723
+ i.setType("image/*");
1724
+ i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
1776
1725
  }
1777
- selectFile = Intent.createChooser(openFile, "Select File");
1778
- cordova.startActivityForResult(this, selectFile, PICK_IMAGE_FROM_GALLERY);
1779
- pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
1780
- pluginResult.setKeepCallback(true);
1781
- callbackContext.sendPluginResult(pluginResult);
1726
+ i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1727
+ this.callback = callbackContext;
1728
+ PluginResult pr = new PluginResult(PluginResult.Status.NO_RESULT);
1729
+ pr.setKeepCallback(true);
1730
+ callbackContext.sendPluginResult(pr);
1731
+ cordova.startActivityForResult(this, i, PICK_IMAGE_FROM_GALLERY);
1732
+
1733
+
1782
1734
  } catch (Exception e) {
1783
1735
  callbackContext.error("pickImageFromGallery failed, exception is : " + e.getMessage());
1784
1736
  }
1785
1737
  }
1738
+
1739
+
1786
1740
 
1787
1741
  private String getMimeType(String path, Uri uri) {
1788
1742
  String mimeType = null;
@@ -2117,6 +2071,198 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
2117
2071
  return "";
2118
2072
  }
2119
2073
 
2074
+
2075
+ private String safeGetDisplayName(@NonNull ContentResolver cr, @NonNull Uri uri, @NonNull String defaultExt) {
2076
+ String name = null;
2077
+ try (Cursor c = cr.query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) {
2078
+ if (c != null && c.moveToFirst()) {
2079
+ int idx = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
2080
+ if (idx != -1) name = c.getString(idx);
2081
+ }
2082
+ } catch (Throwable t) {
2083
+ android.util.Log.w("mInterface", "safeGetDisplayName query failed: " + t.getMessage());
2084
+ }
2085
+ if (name == null || name.trim().isEmpty()) {
2086
+ String ts = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(new java.util.Date());
2087
+ name = "FILE_" + ts + (defaultExt.startsWith(".") ? defaultExt : "." + defaultExt);
2088
+ }
2089
+ return name;
2090
+ }
2091
+ private void postSuccess(final CallbackContext cb, final org.json.JSONObject out) {
2092
+ if (cb == null) return;
2093
+ final PluginResult pr = new PluginResult(PluginResult.Status.OK, out);
2094
+ pr.setKeepCallback(false);
2095
+ cordova.getActivity().runOnUiThread(new Runnable() {
2096
+ @Override public void run() { cb.sendPluginResult(pr); }
2097
+ });
2098
+ }
2099
+
2100
+ private void postSuccess(final CallbackContext cb, final String s) {
2101
+ if (cb == null) return;
2102
+ final PluginResult pr = new PluginResult(PluginResult.Status.OK, s);
2103
+ pr.setKeepCallback(false);
2104
+ cordova.getActivity().runOnUiThread(new Runnable() {
2105
+ @Override public void run() { cb.sendPluginResult(pr); }
2106
+ });
2107
+ }
2108
+
2109
+ private void postError(final CallbackContext cb, final String msg) {
2110
+ if (cb == null) return;
2111
+ final PluginResult pr = new PluginResult(PluginResult.Status.ERROR, msg);
2112
+ pr.setKeepCallback(false);
2113
+ cordova.getActivity().runOnUiThread(new Runnable() {
2114
+ @Override public void run() { cb.sendPluginResult(pr); }
2115
+ });
2116
+ }
2117
+
2118
+ private int calcInSampleSize(BitmapFactory.Options o, int reqW, int reqH) {
2119
+ int inSample = 1, h = o.outHeight, w = o.outWidth;
2120
+ while ((h / inSample) > reqH || (w / inSample) > reqW) inSample <<= 1;
2121
+ return Math.max(1, inSample);
2122
+ }
2123
+
2124
+ /**
2125
+ * Async version that mirrors the sync behavior:
2126
+ * - "camera"/"gallery": returns base64 string
2127
+ * - "compress": returns JSON { filePath, fileName, fileSize, fileExtension, thumbnail }
2128
+ */
2129
+ private void convertImageTo64BaseStringAsync(final CallbackContext cb,
2130
+ final String imageSourcePath,
2131
+ final String imgSrcType,
2132
+ final String destinationPath) {
2133
+ cordova.getThreadPool().execute(new Runnable() {
2134
+ @Override public void run() {
2135
+ InputStream srcFileReader = null;
2136
+ OutputStream desFileWriter = null;
2137
+ ByteArrayOutputStream baos = null;
2138
+ Bitmap bitmap = null;
2139
+ try {
2140
+ // Common bits
2141
+ final String[] parts = imageSourcePath.split("/");
2142
+ String baseName = parts.length > 0 ? parts[parts.length - 1] : "IMG";
2143
+ if (baseName.indexOf(".") > 0) {
2144
+ baseName = baseName.substring(0, baseName.lastIndexOf("."));
2145
+ }
2146
+ final String extFromSource = "." + imageSourcePath.substring(imageSourcePath.lastIndexOf(".") + 1).toLowerCase();
2147
+ final String thumbnail = getFileThumbnail(imageSourcePath, extFromSource);
2148
+
2149
+ if ("compress".equalsIgnoreCase(imgSrcType)) {
2150
+ // === COMPRESS: write JPEG to destination and return file properties ===
2151
+ File fileToCompress = new File(imageSourcePath);
2152
+ File compressedImageFile = new Compressor(cordova.getActivity())
2153
+ .setQuality(15)
2154
+ .setCompressFormat(Bitmap.CompressFormat.JPEG)
2155
+ .compressToFile(fileToCompress);
2156
+
2157
+ String timeStamp = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(new java.util.Date());
2158
+ final String baseDir = (destinationPath != null && !destinationPath.isEmpty())
2159
+ ? (mInterfaceUtil.rootDirectory + "/" + destinationPath)
2160
+ : (mInterfaceUtil.rootDirectory + "/" + mInterfaceUtil.packageName + "/images");
2161
+ final String outName = baseName + "_" + timeStamp + ".jpg";
2162
+ final String outFullPath = baseDir + "/" + outName;
2163
+
2164
+ File outDir = new File(baseDir);
2165
+ if (!outDir.exists()) outDir.mkdirs();
2166
+
2167
+ // Copy compressed temp → final path
2168
+ if (compressedImageFile.exists()) {
2169
+ srcFileReader = new FileInputStream(compressedImageFile);
2170
+ desFileWriter = new FileOutputStream(new File(outFullPath));
2171
+ byte[] buf = new byte[64 * 1024];
2172
+ int n;
2173
+ while ((n = srcFileReader.read(buf)) > 0) {
2174
+ desFileWriter.write(buf, 0, n);
2175
+ }
2176
+ desFileWriter.flush();
2177
+ }
2178
+
2179
+ // (Optional) load to normalize EXIF rotation like the sync version did
2180
+ try { bitmap = handleImageRotation(outFullPath); } catch (Throwable ignore) {}
2181
+
2182
+ // Build properties
2183
+ File outFile = new File(outFullPath);
2184
+ org.json.JSONObject fileProps = new org.json.JSONObject();
2185
+ fileProps.put("filePath", outFile.getParent());
2186
+ fileProps.put("fileName", outFile.getName());
2187
+ fileProps.put("fileSize", outFile.length());
2188
+ fileProps.put("fileExtension", ".jpg");
2189
+ fileProps.put("thumbnail", thumbnail);
2190
+
2191
+ // Match original behavior: clear attachmentDestPath if you rely on that global
2192
+ try { attachmentDestPath = ""; } catch (Throwable ignore) {}
2193
+
2194
+ postSuccess(cb, fileProps);
2195
+ return;
2196
+ }
2197
+
2198
+ // === CAMERA / GALLERY → return BASE64 string ===
2199
+ // Decode with downsampling and fix rotation
2200
+ BitmapFactory.Options o = new BitmapFactory.Options();
2201
+ o.inJustDecodeBounds = true; BitmapFactory.decodeFile(imageSourcePath, o);
2202
+ o.inSampleSize = calcInSampleSize(o, 1920, 1920);
2203
+ o.inJustDecodeBounds = false;
2204
+
2205
+ // Prefer your rotation-aware helper (handles EXIF)
2206
+ try {
2207
+ bitmap = handleImageRotation(imageSourcePath);
2208
+ if (bitmap == null) bitmap = BitmapFactory.decodeFile(imageSourcePath, o);
2209
+ } catch (Throwable t) {
2210
+ bitmap = BitmapFactory.decodeFile(imageSourcePath, o);
2211
+ }
2212
+ if (bitmap == null) {
2213
+ postError(cb, "decode_failed");
2214
+ return;
2215
+ }
2216
+
2217
+ baos = new ByteArrayOutputStream(1024 * 1024);
2218
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 15, baos);
2219
+ String encodedImage = android.util.Base64.encodeToString(baos.toByteArray(), android.util.Base64.NO_WRAP);
2220
+
2221
+ // Match original behavior: delete camera temp + clear dest flag
2222
+ if ("camera".equalsIgnoreCase(imgSrcType)) {
2223
+ try {
2224
+ File f = new File(imageSourcePath);
2225
+ if (f.exists()) f.delete();
2226
+ } catch (Throwable ignore) {}
2227
+ try { attachmentDestPath = ""; } catch (Throwable ignore) {}
2228
+ } else if ("gallery".equalsIgnoreCase(imgSrcType)) {
2229
+ try { attachmentDestPath = ""; } catch (Throwable ignore) {}
2230
+ }
2231
+
2232
+ postSuccess(cb, encodedImage);
2233
+ } catch (Exception e) {
2234
+ try { attachmentDestPath = ""; } catch (Throwable ignore) {}
2235
+ postError(cb, "convertImageTo64BaseStringAsync failed: " + e.getMessage());
2236
+ } finally {
2237
+ try { if (srcFileReader != null) srcFileReader.close(); } catch (Exception ignore) {}
2238
+ try { if (desFileWriter != null) desFileWriter.close(); } catch (Exception ignore) {}
2239
+ try { if (baos != null) baos.close(); } catch (Exception ignore) {}
2240
+ try { if (bitmap != null) bitmap.recycle(); } catch (Exception ignore) {}
2241
+ }
2242
+ }
2243
+ });
2244
+ }
2245
+
2246
+
2247
+ private String copyFileFromUriFast(Context ctx, Uri uri, String destName, String destDir) {
2248
+ java.io.File dir = (destDir!=null && !destDir.isEmpty())
2249
+ ? new java.io.File(ctx.getFilesDir(), destDir)
2250
+ : ctx.getFilesDir();
2251
+ if (!dir.exists() && !dir.mkdirs()) return null;
2252
+ java.io.File out = new java.io.File(dir, destName);
2253
+ try (java.io.InputStream in = ctx.getContentResolver().openInputStream(uri);
2254
+ java.io.OutputStream os = new java.io.FileOutputStream(out)) {
2255
+ if (in == null) return null;
2256
+ byte[] buf = new byte[64 * 1024];
2257
+ int n; while ((n = in.read(buf)) != -1) os.write(buf, 0, n);
2258
+ os.flush();
2259
+ return out.getAbsolutePath();
2260
+ } catch (Exception e) {
2261
+ android.util.Log.e("mInterface", "copyFileFromUriFast", e);
2262
+ return null;
2263
+ }
2264
+ }
2265
+
2120
2266
  @Override
2121
2267
  public void onActivityResult(int requestCode, int resultCode, Intent data) {
2122
2268
  try {
@@ -2158,136 +2304,89 @@ public class mInterface extends CordovaPlugin implements com.google.android.gms.
2158
2304
  SCANCODEINT = 0;
2159
2305
  callback.error(resultCode);
2160
2306
  }
2161
- } else if (requestCode == PICK_IMAGE_FROM_GALLERY && callback != null) {
2162
- if (resultCode == RESULT_OK) {
2163
- String fileName = null, getfilePath = null, filePath, fileType, storageDefinition, fileExtension;
2164
- final String docId, type, id;
2165
- final String[] split;
2166
- final String tag, path;
2167
- final int splitIndex;
2168
- Cursor cursor;
2169
- Uri uri = data.getData(); //get uri for selected file
2170
- fileType = this.cordova.getActivity().getContentResolver().getType(uri); // get mimeType for selected file
2171
- File getFileName, file;
2172
- try {
2173
- //API LEVEL 19 AND ABOVE (TO GET FILEPATH)
2174
- if (Build.VERSION.SDK_INT >= 19 && !Build.MANUFACTURER.equals("samsung") && DocumentsContract.isDocumentUri(cordova.getActivity(), uri)) {
2175
- if ("com.android.externalstorage.documents".equals(uri.getAuthority())) { // ExternalStorageProvider
2176
- docId = DocumentsContract.getDocumentId(uri);
2177
- split = docId.split(":");
2178
- type = split[0];
2179
- if ("primary".equalsIgnoreCase(type)) {
2180
- getfilePath = mInterfaceUtil.rootDirectory + "/" + split[1];
2181
- } else {
2182
- if (Environment.isExternalStorageRemovable()) {
2183
- storageDefinition = System.getenv("EXTERNAL_STORAGE");
2184
- getfilePath = storageDefinition;
2185
- } else {
2186
- splitIndex = docId.indexOf(':', 1);
2187
- tag = docId.substring(0, splitIndex);
2188
- path = docId.substring(splitIndex + 1);
2189
- if (new mInterfaceUtil().getPathToNonPrimaryVolume(cordova.getActivity(), tag) != null) {
2190
- storageDefinition = new mInterfaceUtil().getPathToNonPrimaryVolume(cordova.getActivity(), tag) + "/" + path;
2191
- file = new File(storageDefinition);
2192
- if (file.exists() && file.canRead()) {
2193
- getfilePath = storageDefinition;
2194
- }
2195
- }
2196
- }
2197
- }
2198
- } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) { // DownloadsProvider
2199
- id = DocumentsContract.getDocumentId(uri);
2200
- final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
2201
- getfilePath = new mInterfaceUtil().getDataColumn(cordova.getActivity(), contentUri, null, null);
2202
- } else if ("com.android.providers.media.documents".equals(uri.getAuthority())) { // MediaProvider
2203
- docId = DocumentsContract.getDocumentId(uri);
2204
- split = docId.split(":");
2205
- type = split[0];
2206
- Uri contentUri = null;
2207
- if ("image".equals(type)) {
2208
- contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
2209
- } else if ("video".equals(type)) {
2210
- contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
2211
- } else if ("audio".equals(type)) {
2212
- contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
2213
- }
2214
- final String selection = "_id=?";
2215
- final String[] selectionArgs = new String[]{
2216
- split[1]
2217
- };
2218
- getfilePath = new mInterfaceUtil().getDataColumn(cordova.getActivity(), contentUri, selection, selectionArgs);
2219
- }
2220
- // *** API Level 19 and Above (To Get FileName,FileExtension and FileSize)**** //
2221
- cursor = cordova.getActivity().getContentResolver().query(uri, null, null, null, null, null);
2307
+ }else if (requestCode == PICK_IMAGE_FROM_GALLERY) {
2308
+ final CallbackContext cb = this.callback;
2309
+ this.callback = null;
2310
+ if (cb == null) return;
2311
+
2312
+ try {
2313
+ if (resultCode != RESULT_OK || data == null) {
2314
+ cb.error("canceled");
2315
+ return;
2316
+ }
2317
+
2318
+ // Photo Picker may return via getData() or ClipData
2319
+ Uri uri = data.getData();
2320
+ if (uri == null && data.getClipData() != null && data.getClipData().getItemCount() > 0) {
2321
+ uri = data.getClipData().getItemAt(0).getUri();
2322
+ }
2323
+ if (uri == null) {
2324
+ cb.error("no_file_selected");
2325
+ return;
2326
+ }
2327
+
2328
+ final Uri selectedUri = uri;
2329
+ final String destPath = attachmentDestPath; // capture current dest
2330
+ final String galleryInd = String.valueOf(galleryAttachmentInd);
2331
+
2332
+ // Offload ALL heavy work (resolver, copy, bitmap/encode) to Cordova thread pool
2333
+ cordova.getThreadPool().execute(new Runnable() {
2334
+ @Override public void run() {
2222
2335
  try {
2223
- if (cursor != null && cursor.moveToFirst()) {
2224
- int displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
2225
- if (displayNameIndex != -1) {
2226
- fileName = cursor.getString(displayNameIndex);
2227
- } else {
2228
- fileName = "";
2229
- }
2336
+ ContentResolver cr = cordova.getActivity().getContentResolver();
2337
+
2338
+ String mime = null;
2339
+ try { mime = cr.getType(selectedUri); } catch (Throwable ignore) {}
2340
+
2341
+ String defExt = (mime != null && mime.contains("/"))
2342
+ ? mime.substring(mime.indexOf('/') + 1).toLowerCase()
2343
+ : "jpg";
2344
+
2345
+ // Get a display name if provider allows; otherwise build a sane fallback
2346
+ String fileName = safeGetDisplayName(cr, selectedUri, defExt);
2347
+ String ext = fileName.contains(".")
2348
+ ? fileName.substring(fileName.lastIndexOf('.')).toLowerCase()
2349
+ : ("." + defExt);
2350
+
2351
+ // Copy the user-selected URI into app-private storage (no READ_MEDIA_* needed)
2352
+ String localPath = copyFileFromUriFast(
2353
+ cordova.getActivity().getApplicationContext(),
2354
+ selectedUri, fileName, destPath);
2355
+
2356
+ if (localPath == null) {
2357
+ cb.error("copy_failed");
2358
+ return;
2230
2359
  }
2231
- } finally {
2232
- cursor.close();
2233
- }
2234
- // *** API Level 19 below and SAMSUNG (To Get FileProperties of MediaFiles)**** //
2235
- } else {
2236
- if (fileType != null) {
2237
- String[] projection = {
2238
- MediaStore.Images.Media.DATA,
2239
- MediaStore.Images.Media.SIZE,
2240
- MediaStore.Images.Media.DISPLAY_NAME
2241
- };
2242
- cursor = cordova.getActivity().getContentResolver().query(uri, projection, null, null, null);
2243
- if (cursor.moveToFirst()) {
2244
- getfilePath = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));
2245
- fileName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME));
2360
+
2361
+ // Accept only image-like picks (by MIME or extension)
2362
+ boolean imageLike =
2363
+ (mime != null && mime.startsWith("image/")) ||
2364
+ ext.equals(".jpg") || ext.equals(".jpeg") ||
2365
+ ext.equals(".png") || ext.equals(".heic");
2366
+
2367
+ if (!imageLike) {
2368
+ cb.error("Only JPEG/PNG/HEIC images are allowed.");
2369
+ return;
2246
2370
  }
2247
- cursor.close();
2248
- // API LEVEL 19 BELOW AND SAMSUNG (TO GET FILE PROPERTIES OF NORMAL FILES(EX:txt,.pdf,etc..,))
2249
- } else {
2250
- getfilePath = data.getData().getPath();
2251
- filePath = getfilePath.substring(0, getfilePath.lastIndexOf(File.separator));
2252
- getFileName = new File(getfilePath);
2253
- fileName = getFileName.getName();
2254
- }
2255
- }
2256
- filePath = getfilePath.substring(0, getfilePath.lastIndexOf(File.separator));
2257
- fileExtension = "." + getfilePath.substring(getfilePath.lastIndexOf(".") + 1).toLowerCase();
2258
- if (filePath != null) {
2259
- try {
2260
- if (fileExtension.equals(".heic")) {
2261
- if (galleryAttachmentInd.equals("true")) {
2262
- convertImageTo64BaseString(callback, filePath + "/" + fileName, "compress", attachmentDestPath);
2263
- } else {
2264
- convertImageTo64BaseString(callback, filePath + "/" + fileName, "gallery", "");
2265
- }
2266
2371
 
2267
- } else if (fileExtension.equals(".jpeg") || fileExtension.equals(".jpg") || fileExtension.equals(".png")) {
2268
- if (galleryAttachmentInd.equals("true")) {
2269
- convertImageTo64BaseString(callback, filePath + "/" + fileName, "compress", attachmentDestPath);
2270
- } else {
2271
- convertImageTo64BaseString(callback, filePath + "/" + fileName, "gallery", "");
2272
- }
2372
+ // Run your existing conversion on a worker too (non-blocking)
2373
+ if ("true".equals(galleryInd)) {
2374
+ convertImageTo64BaseStringAsync(cb, localPath, "compress", destPath);
2273
2375
  } else {
2274
- callback.error("Only JPEG or PNG file extensions are allowed to attach.");
2376
+ convertImageTo64BaseStringAsync(cb, localPath, "gallery", "");
2275
2377
  }
2276
2378
  } catch (Exception e) {
2277
- callback.error("Only JPEG or PNG file extensions are allowed to attach.");
2379
+ cb.error("PICK_IMAGE_FROM_GALLERY: " + e.getMessage());
2278
2380
  }
2279
- } else {
2280
- callback.error("File path was null");
2281
2381
  }
2282
- } catch (Exception e) {
2283
- e.printStackTrace();
2284
- }
2285
- } else if (resultCode == Activity.RESULT_CANCELED) {
2286
- callback.error("failure");
2287
- } else {
2288
- callback.error(resultCode);
2382
+ });
2383
+ return; // IMPORTANT: stop here so no other branch falls through
2384
+ } catch (Exception e) {
2385
+ cb.error("PICK_IMAGE_FROM_GALLERY dispatch failed: " + e.getMessage());
2386
+ return;
2289
2387
  }
2290
- } else if (requestCode == NEW_FILE_PICK_REQUEST && callback != null) {
2388
+ }
2389
+ else if (requestCode == NEW_FILE_PICK_REQUEST && callback != null) {
2291
2390
  try {
2292
2391
  Uri uri = data.getData();
2293
2392
  String fileName, extension, filePath;
@@ -43,6 +43,7 @@
43
43
  UIInterfaceOrientation _lastOrientation;
44
44
  }
45
45
  @property(strong) NSString* callbackID;
46
+ - (void)GetConfigCascade:(CDVInvokedUrlCommand*)command;
46
47
  - (void)ScanBarCode:(CDVInvokedUrlCommand*)command;
47
48
  - (void)ScanQRCode:(CDVInvokedUrlCommand*)command;
48
49
  - (void)viewRoute:(CDVInvokedUrlCommand*)command;
@@ -13,6 +13,7 @@
13
13
  #import "OneSignalPush.h"
14
14
  #import <objc/runtime.h>
15
15
  #import <AVFoundation/AVFoundation.h>
16
+ #import <Cordova/CDV.h>
16
17
  #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
17
18
  @import UserNotifications;
18
19
  #endif
@@ -20,7 +21,14 @@
20
21
  #ifndef NSFoundationVersionNumber_iOS_9_x_Max
21
22
  #define NSFoundationVersionNumber_iOS_9_x_Max 1299
22
23
  #endif
23
-
24
+ /*** TOP-LEVEL helper (NOT inside @implementation mInterface) ***/
25
+ @interface ResolveResult : NSObject
26
+ @property (nonatomic, strong) NSURL *fileURL;
27
+ @property (nonatomic, copy) NSString *ext;
28
+ @end
29
+
30
+ @implementation ResolveResult
31
+ @end
24
32
  @implementation mInterface
25
33
  @synthesize btnGallery;
26
34
  @synthesize indicator = _indicator;
@@ -29,6 +37,333 @@
29
37
  @synthesize callbackID;
30
38
 
31
39
 
40
+
41
+ - (void)GetConfigCascade:(CDVInvokedUrlCommand*)command {
42
+ // Declarations first
43
+
44
+
45
+ @try {
46
+ [self.commandDelegate runInBackground:^{
47
+ @try {
48
+ // --- parse input
49
+ NSDictionary *p = (command.arguments.count > 0 && [command.arguments[0] isKindOfClass:[NSDictionary class]]) ? command.arguments[0] : @{};
50
+ NSString *kind = p[@"configType"] ?: @"controller";
51
+ NSString *identifier = p[@"id"] ?: @"";
52
+ NSString *clientId = p[@"clientId"] ?: @"";
53
+ NSString *countryCode = p[@"countryCode"] ?: @"";
54
+ NSString *clientGroup = p[@"clientGroup"] ?: @"";
55
+ NSString *vertical = p[@"vertical"] ?: @"";
56
+ NSString *solution = p[@"solution"] ?: @"";
57
+ NSString *scope = p[@"scope"] ?: kind;
58
+ NSString *transNo = p[@"transNo"] ?: @"";
59
+ NSString *rootPrefix = p[@"root"] ?: @"";
60
+ NSString *localeId = p[@"localeId"] ?: @"";
61
+
62
+ // --- base dir
63
+ NSFileManager *fm = [NSFileManager defaultManager];
64
+ NSURL *baseDir = [[fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
65
+ if (!baseDir) baseDir = [[fm URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] firstObject];
66
+
67
+ // --- roots
68
+ NSArray<NSURL *> *roots = [self buildRootsWithBase:baseDir
69
+ rootPrefix:rootPrefix
70
+ clientGroup:clientGroup
71
+ clientId:clientId
72
+ countryCode:countryCode];
73
+
74
+ // --- keys (label is locale-aware)
75
+ NSMutableArray<NSString *> *keys = [NSMutableArray array];
76
+ if ([kind isEqualToString:@"label"] && localeId.length > 0) {
77
+ if (clientId.length && countryCode.length) [keys addObject:[NSString stringWithFormat:@"%@_%@_%@_%@", identifier, clientId, countryCode, localeId]];
78
+ if (clientId.length) [keys addObject:[NSString stringWithFormat:@"%@_%@_%@", identifier, clientId, localeId]];
79
+ if (clientGroup.length) [keys addObject:[NSString stringWithFormat:@"%@_%@_%@", identifier, clientGroup, localeId]];
80
+ if (vertical.length) [keys addObject:[NSString stringWithFormat:@"%@_%@_%@", identifier, vertical, localeId]];
81
+ if (solution.length) [keys addObject:[NSString stringWithFormat:@"%@_%@_%@", identifier, solution, localeId]];
82
+ [keys addObject:[NSString stringWithFormat:@"%@_%@", identifier, localeId]];
83
+ } else {
84
+ if (clientId.length && countryCode.length) [keys addObject:[NSString stringWithFormat:@"%@_%@_%@", identifier, clientId, countryCode]];
85
+ if (clientId.length) [keys addObject:[NSString stringWithFormat:@"%@_%@", identifier, clientId]];
86
+ if (clientGroup.length) [keys addObject:[NSString stringWithFormat:@"%@_%@", identifier, clientGroup]];
87
+ if (vertical.length) [keys addObject:[NSString stringWithFormat:@"%@_%@", identifier, vertical]];
88
+ if (solution.length) [keys addObject:[NSString stringWithFormat:@"%@_%@", identifier, solution]];
89
+ [keys addObject:identifier ?: @""];
90
+ }
91
+
92
+ // --- scopes
93
+ NSMutableArray<NSString *> *scopesToTry = [NSMutableArray array];
94
+ if ([scope isEqualToString:@"containR"]) {
95
+ [scopesToTry addObject:@"containR"];
96
+ [scopesToTry addObject:kind];
97
+ } else {
98
+ [scopesToTry addObject:kind];
99
+ [scopesToTry addObject:@"containR"];
100
+ }
101
+
102
+ // --- search
103
+ BOOL found = NO;
104
+ NSString *resolvedKey = nil; NSString *usedExt = nil; NSString *absPath = nil; NSString *content = nil;
105
+
106
+ for (NSString *key in keys) {
107
+ for (NSString *sc in scopesToTry) {
108
+ ResolveResult *rr = [self resolveGenericFileWithRoots:roots
109
+ scope:sc
110
+ kind:kind
111
+ id:identifier
112
+ transNo:transNo
113
+ key:key];
114
+ if (rr && rr.fileURL) {
115
+ NSString *tryPath = rr.fileURL.path;
116
+ NSLog(@"mInterface: Trying: %@", tryPath);
117
+ BOOL isDir = NO;
118
+ if ([fm fileExistsAtPath:tryPath isDirectory:&isDir] && !isDir) {
119
+ NSError *readErr = nil;
120
+ NSString *txt = [NSString stringWithContentsOfURL:rr.fileURL
121
+ encoding:NSUTF8StringEncoding
122
+ error:&readErr];
123
+ if (!readErr && txt != nil) {
124
+ content = txt;
125
+ resolvedKey = key;
126
+ usedExt = rr.ext ?: @"";
127
+ absPath = tryPath;
128
+ found = YES;
129
+ break;
130
+ }
131
+ }
132
+ }
133
+ }
134
+ if (found) break;
135
+ }
136
+
137
+ // --- respond
138
+ if (found) {
139
+ NSDictionary *payload = @{
140
+ @"configType": kind ?: @"",
141
+ @"srcKey": resolvedKey ?: @"",
142
+ @"ext": usedExt ?: @"",
143
+ @"path": absPath ?: @"",
144
+ @"content": content ?: @""
145
+ };
146
+
147
+ NSError *jsonError = nil; // declare inside the block
148
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload
149
+ options:0
150
+ error:&jsonError];
151
+ NSString *jsonStr = [[NSString alloc] initWithData:jsonData
152
+ encoding:NSUTF8StringEncoding];
153
+
154
+ CDVPluginResult *ok = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
155
+ messageAsString:jsonStr ?: @"{}"];
156
+ [self.commandDelegate sendPluginResult:ok callbackId:command.callbackId];
157
+ } else {
158
+ CDVPluginResult *errRes = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
159
+ messageAsString:@"NOT_FOUND"];
160
+ [self.commandDelegate sendPluginResult:errRes callbackId:command.callbackId];
161
+ }
162
+
163
+ } @catch (NSException *ex) {
164
+ CDVPluginResult *errRes = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[NSString stringWithFormat:@"GetConfigCascade failed: %@", ex.reason ?: @""]];
165
+ [self.commandDelegate sendPluginResult:errRes callbackId:command.callbackId];
166
+ }
167
+ }];
168
+ } @catch (NSException *exOuter) {
169
+ CDVPluginResult *errRes = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[NSString stringWithFormat:@"GetConfigCascade failed: %@", exOuter.reason ?: @""]];
170
+ [self.commandDelegate sendPluginResult:errRes callbackId:command.callbackId];
171
+ }
172
+ }
173
+
174
+ #pragma mark - Helpers
175
+
176
+ - (NSArray<NSURL*> *)buildRootsWithBase:(NSURL *)baseDir
177
+ rootPrefix:(NSString *)rootPrefix
178
+ clientGroup:(NSString *)clientGroup
179
+ clientId:(NSString *)clientId
180
+ countryCode:(NSString *)countryCode {
181
+ NSMutableArray<NSURL*> *roots = [NSMutableArray array];
182
+ NSMutableSet<NSString*> *seen = [NSMutableSet set];
183
+
184
+ void (^addIfUnique)(NSURL *) = ^(NSURL *u){
185
+ if (!u) return;
186
+ NSString *p = u.path;
187
+ if (p.length && ![seen containsObject:p]) { [roots addObject:u]; [seen addObject:p]; }
188
+ };
189
+
190
+ @try {
191
+ NSURL *r1 = [[[baseDir URLByAppendingPathComponent:rootPrefix isDirectory:YES]
192
+ URLByAppendingPathComponent:clientId isDirectory:YES]
193
+ URLByAppendingPathComponent:countryCode isDirectory:YES];
194
+ addIfUnique(r1);
195
+
196
+ NSURL *r2 = [[[[baseDir URLByAppendingPathComponent:rootPrefix isDirectory:YES]
197
+ URLByAppendingPathComponent:clientId isDirectory:YES]
198
+ URLByAppendingPathComponent:clientId isDirectory:YES]
199
+ URLByAppendingPathComponent:countryCode isDirectory:YES];
200
+ addIfUnique(r2);
201
+
202
+ if (clientGroup.length) {
203
+ NSURL *r3 = [[[[baseDir URLByAppendingPathComponent:rootPrefix isDirectory:YES]
204
+ URLByAppendingPathComponent:clientGroup isDirectory:YES]
205
+ URLByAppendingPathComponent:clientId isDirectory:YES]
206
+ URLByAppendingPathComponent:countryCode isDirectory:YES];
207
+ addIfUnique(r3);
208
+ }
209
+ } @catch (__unused NSException *e) {}
210
+ return roots;
211
+ }
212
+
213
+ - (NSString *)prefixForKind:(NSString *)kind {
214
+ if ([kind isEqualToString:@"controller"]) return @"controller_";
215
+ if ([kind isEqualToString:@"label"]) return @"label_";
216
+ if ([kind isEqualToString:@"rule"]) return @"rule_";
217
+ if ([kind isEqualToString:@"template"]) return @"template_";
218
+ if ([kind isEqualToString:@"ui"]) return @"ui_";
219
+ return [kind stringByAppendingString:@"_"];
220
+ }
221
+
222
+ - (NSArray<NSString*> *)extensionsForKind:(NSString *)kind {
223
+ if ([kind isEqualToString:@"controller"]) return @[ @"json" ];
224
+ if ([kind isEqualToString:@"label"]) return @[ @"json", @"txt" ];
225
+ if ([kind isEqualToString:@"rule"]) return @[ @"txt", @"json" ];
226
+ if ([kind isEqualToString:@"template"]) return @[ @"html", @"json" ];
227
+ if ([kind isEqualToString:@"ui"]) return @[ @"html", @"json" ];
228
+ return @[ @"json" ];
229
+ }
230
+
231
+ - (ResolveResult *)resolveGenericFileWithRoots:(NSArray<NSURL*> *)roots
232
+ scope:(NSString *)scope
233
+ kind:(NSString *)kind
234
+ id:(NSString *)identifier
235
+ transNo:(NSString *)transNo
236
+ key:(NSString *)key {
237
+ NSArray<NSString*> *exts = [self extensionsForKind:kind];
238
+ NSString *prefix = [self prefixForKind:kind];
239
+
240
+ NSArray<NSString*> *nameVariants = [kind isEqualToString:@"label"]
241
+ ? @[ [prefix stringByAppendingString:key], key ]
242
+ : @[ [prefix stringByAppendingString:key] ];
243
+
244
+ NSFileManager *fm = [NSFileManager defaultManager];
245
+
246
+ @try {
247
+ for (NSURL *root in roots) {
248
+ if (!root) continue;
249
+
250
+ for (NSString *ext in exts) {
251
+
252
+ if ([scope isEqualToString:@"containR"]) {
253
+ NSURL *containRDir = [root URLByAppendingPathComponent:@"containR" isDirectory:YES];
254
+ NSURL *screenDir = [containRDir URLByAppendingPathComponent:identifier isDirectory:YES];
255
+
256
+ if (transNo.length) {
257
+ NSURL *transDir = [screenDir URLByAppendingPathComponent:transNo isDirectory:YES];
258
+
259
+ if ([kind isEqualToString:@"label"]) {
260
+ NSURL *labelDir = [transDir URLByAppendingPathComponent:@"label" isDirectory:YES];
261
+ NSURL *labelsDir = [transDir URLByAppendingPathComponent:@"labels" isDirectory:YES];
262
+
263
+ for (NSString *nv in nameVariants) {
264
+ NSArray<NSURL*> *cands = @[
265
+ [labelDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]],
266
+ [labelsDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]],
267
+ [transDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]]
268
+ ];
269
+ for (NSURL *u in cands) {
270
+ if ([fm fileExistsAtPath:u.path isDirectory:NULL]) {
271
+ ResolveResult *rr = [ResolveResult new]; rr.fileURL = u; rr.ext = ext; return rr;
272
+ }
273
+ }
274
+ }
275
+ } else {
276
+ NSURL *typeDir = [transDir URLByAppendingPathComponent:kind isDirectory:YES];
277
+ NSURL *f1 = [typeDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", [prefix stringByAppendingString:key], ext]];
278
+ NSURL *f2 = [transDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", [prefix stringByAppendingString:key], ext]];
279
+ if ([fm fileExistsAtPath:f1.path isDirectory:NULL]) { ResolveResult *rr=[ResolveResult new]; rr.fileURL=f1; rr.ext=ext; return rr; }
280
+ if ([fm fileExistsAtPath:f2.path isDirectory:NULL]) { ResolveResult *rr=[ResolveResult new]; rr.fileURL=f2; rr.ext=ext; return rr; }
281
+ }
282
+ }
283
+
284
+ if ([kind isEqualToString:@"label"]) {
285
+ NSURL *labelDirNT = [screenDir URLByAppendingPathComponent:@"label" isDirectory:YES];
286
+ NSURL *labelsDirNT = [screenDir URLByAppendingPathComponent:@"labels" isDirectory:YES];
287
+ for (NSString *nv in nameVariants) {
288
+ NSArray<NSURL*> *cands = @[
289
+ [labelDirNT URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]],
290
+ [labelsDirNT URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]],
291
+ [screenDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]]
292
+ ];
293
+ for (NSURL *u in cands) {
294
+ if ([fm fileExistsAtPath:u.path isDirectory:NULL]) {
295
+ ResolveResult *rr = [ResolveResult new]; rr.fileURL = u; rr.ext = ext; return rr;
296
+ }
297
+ }
298
+ }
299
+ } else {
300
+ NSURL *typeDirNT = [screenDir URLByAppendingPathComponent:kind isDirectory:YES];
301
+ NSURL *f1 = [typeDirNT URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", [prefix stringByAppendingString:key], ext]];
302
+ NSURL *f2 = [screenDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", [prefix stringByAppendingString:key], ext]];
303
+ if ([fm fileExistsAtPath:f1.path isDirectory:NULL]) { ResolveResult *rr=[ResolveResult new]; rr.fileURL=f1; rr.ext=ext; return rr; }
304
+ if ([fm fileExistsAtPath:f2.path isDirectory:NULL]) { ResolveResult *rr=[ResolveResult new]; rr.fileURL=f2; rr.ext=ext; return rr; }
305
+ }
306
+
307
+ } else {
308
+ NSURL *cfg = [root URLByAppendingPathComponent:@"configuration_package" isDirectory:YES];
309
+
310
+ if ([kind isEqualToString:@"label"]) {
311
+ NSURL *labelDir = [cfg URLByAppendingPathComponent:@"label" isDirectory:YES];
312
+ NSURL *labelsDir = [cfg URLByAppendingPathComponent:@"labels" isDirectory:YES];
313
+
314
+ for (NSString *nv in nameVariants) {
315
+ NSArray<NSURL*> *cands1 = @[
316
+ [labelDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]],
317
+ [labelsDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]]
318
+ ];
319
+ for (NSURL *u in cands1) {
320
+ if ([fm fileExistsAtPath:u.path isDirectory:NULL]) { ResolveResult *rr=[ResolveResult new]; rr.fileURL=u; rr.ext=ext; return rr; }
321
+ }
322
+ NSArray<NSURL*> *cands2 = @[
323
+ [[labelDir URLByAppendingPathComponent:identifier isDirectory:YES] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]],
324
+ [[labelsDir URLByAppendingPathComponent:identifier isDirectory:YES] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]]
325
+ ];
326
+ for (NSURL *u in cands2) {
327
+ if ([fm fileExistsAtPath:u.path isDirectory:NULL]) { ResolveResult *rr=[ResolveResult new]; rr.fileURL=u; rr.ext=ext; return rr; }
328
+ }
329
+ }
330
+
331
+ NSURL *cfg2 = [root URLByAppendingPathComponent:@"configuration" isDirectory:YES];
332
+ NSURL *label2 = [cfg2 URLByAppendingPathComponent:@"label" isDirectory:YES];
333
+ NSURL *labels2 = [cfg2 URLByAppendingPathComponent:@"labels" isDirectory:YES];
334
+ for (NSString *nv in nameVariants) {
335
+ NSArray<NSURL*> *cands3 = @[
336
+ [label2 URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]],
337
+ [labels2 URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]],
338
+ [[label2 URLByAppendingPathComponent:identifier isDirectory:YES] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]],
339
+ [[labels2 URLByAppendingPathComponent:identifier isDirectory:YES] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", nv, ext]]
340
+ ];
341
+ for (NSURL *u in cands3) {
342
+ if ([fm fileExistsAtPath:u.path isDirectory:NULL]) { ResolveResult *rr=[ResolveResult new]; rr.fileURL=u; rr.ext=ext; return rr; }
343
+ }
344
+ }
345
+
346
+ } else {
347
+ NSURL *typeDir = [cfg URLByAppendingPathComponent:kind isDirectory:YES];
348
+ NSURL *f1 = [typeDir URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", [prefix stringByAppendingString:key], ext]];
349
+ NSURL *f2 = [[typeDir URLByAppendingPathComponent:identifier isDirectory:YES] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", [prefix stringByAppendingString:key], ext]];
350
+ if ([fm fileExistsAtPath:f1.path isDirectory:NULL]) { ResolveResult *rr=[ResolveResult new]; rr.fileURL=f1; rr.ext=ext; return rr; }
351
+ if ([fm fileExistsAtPath:f2.path isDirectory:NULL]) { ResolveResult *rr=[ResolveResult new]; rr.fileURL=f2; rr.ext=ext; return rr; }
352
+
353
+ NSURL *cfg2 = [root URLByAppendingPathComponent:@"configuration" isDirectory:YES];
354
+ NSURL *type2 = [cfg2 URLByAppendingPathComponent:kind isDirectory:YES];
355
+ NSURL *f3 = [type2 URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", [prefix stringByAppendingString:key], ext]];
356
+ NSURL *f4 = [[type2 URLByAppendingPathComponent:identifier isDirectory:YES] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", [prefix stringByAppendingString:key], ext]];
357
+ if ([fm fileExistsAtPath:f3.path isDirectory:NULL]) { ResolveResult *rr=[ResolveResult new]; rr.fileURL=f3; rr.ext=ext; return rr; }
358
+ if ([fm fileExistsAtPath:f4.path isDirectory:NULL]) { ResolveResult *rr=[ResolveResult new]; rr.fileURL=f4; rr.ext=ext; return rr; }
359
+ }
360
+ }
361
+ }
362
+ }
363
+ } @catch (__unused NSException *e) {}
364
+ return nil;
365
+ }
366
+
32
367
 
33
368
  // Inside the @implementation block
34
369
  - (void)ScanBarCode:(CDVInvokedUrlCommand*)command {