react-native-pdf-jsi 2.2.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +308 -173
- package/android/src/main/java/org/wonday/pdf/FileDownloader.java +292 -0
- package/android/src/main/java/org/wonday/pdf/FileManager.java +123 -0
- package/android/src/main/java/org/wonday/pdf/LicenseVerifier.java +311 -0
- package/android/src/main/java/org/wonday/pdf/PDFExporter.java +769 -0
- package/android/src/main/java/org/wonday/pdf/RNPDFPackage.java +7 -0
- package/index.js +58 -0
- package/ios/RNPDFPdf/PDFExporter.h +16 -0
- package/ios/RNPDFPdf/PDFExporter.m +537 -0
- package/package.json +3 -2
- package/src/components/AnalyticsPanel.jsx +243 -0
- package/src/components/BookmarkIndicator.jsx +66 -0
- package/src/components/BookmarkListModal.jsx +378 -0
- package/src/components/BookmarkModal.jsx +253 -0
- package/src/components/BottomSheet.jsx +121 -0
- package/src/components/ExportMenu.jsx +223 -0
- package/src/components/LoadingOverlay.jsx +52 -0
- package/src/components/OperationsMenu.jsx +231 -0
- package/src/components/SidePanel.jsx +95 -0
- package/src/components/Toast.jsx +140 -0
- package/src/components/Toolbar.jsx +135 -0
- package/src/managers/AnalyticsManager.js +695 -0
- package/src/managers/BookmarkManager.js +538 -0
- package/src/managers/ExportManager.js +687 -0
- package/src/managers/FileManager.js +89 -0
- package/src/utils/ErrorHandler.js +179 -0
- package/src/utils/TestData.js +112 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
package org.wonday.pdf;
|
|
2
|
+
|
|
3
|
+
import android.app.NotificationChannel;
|
|
4
|
+
import android.app.NotificationManager;
|
|
5
|
+
import android.app.PendingIntent;
|
|
6
|
+
import android.content.ContentResolver;
|
|
7
|
+
import android.content.ContentValues;
|
|
8
|
+
import android.content.Context;
|
|
9
|
+
import android.content.Intent;
|
|
10
|
+
import android.net.Uri;
|
|
11
|
+
import android.os.Build;
|
|
12
|
+
import android.os.Environment;
|
|
13
|
+
import android.provider.MediaStore;
|
|
14
|
+
import android.util.Log;
|
|
15
|
+
import androidx.core.app.NotificationCompat;
|
|
16
|
+
import androidx.core.app.NotificationManagerCompat;
|
|
17
|
+
|
|
18
|
+
import com.facebook.react.bridge.Promise;
|
|
19
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
20
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
21
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
22
|
+
|
|
23
|
+
import java.io.File;
|
|
24
|
+
import java.io.FileInputStream;
|
|
25
|
+
import java.io.InputStream;
|
|
26
|
+
import java.io.OutputStream;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* FileDownloader - Native module for downloading files to public storage using MediaStore API
|
|
30
|
+
* Ensures files are immediately visible in file managers
|
|
31
|
+
*/
|
|
32
|
+
public class FileDownloader extends ReactContextBaseJavaModule {
|
|
33
|
+
private static final String TAG = "FileDownloader";
|
|
34
|
+
private static final String FOLDER_NAME = "PDFDemoApp";
|
|
35
|
+
private static final String NOTIFICATION_CHANNEL_ID = "pdf_exports";
|
|
36
|
+
private final ReactApplicationContext reactContext;
|
|
37
|
+
|
|
38
|
+
public FileDownloader(ReactApplicationContext reactContext) {
|
|
39
|
+
super(reactContext);
|
|
40
|
+
this.reactContext = reactContext;
|
|
41
|
+
createNotificationChannel();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create notification channel for export notifications (Android O+)
|
|
46
|
+
*/
|
|
47
|
+
private void createNotificationChannel() {
|
|
48
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
49
|
+
CharSequence name = "PDF Exports";
|
|
50
|
+
String description = "Notifications for PDF export operations";
|
|
51
|
+
int importance = NotificationManager.IMPORTANCE_DEFAULT;
|
|
52
|
+
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance);
|
|
53
|
+
channel.setDescription(description);
|
|
54
|
+
|
|
55
|
+
NotificationManager notificationManager = reactContext.getSystemService(NotificationManager.class);
|
|
56
|
+
if (notificationManager != null) {
|
|
57
|
+
notificationManager.createNotificationChannel(channel);
|
|
58
|
+
Log.i(TAG, "📱 [NOTIFICATION] Channel created");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Override
|
|
64
|
+
public String getName() {
|
|
65
|
+
return "FileDownloader";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Download file to public Downloads folder using MediaStore API
|
|
70
|
+
*
|
|
71
|
+
* @param sourcePath Path to source file in app's cache
|
|
72
|
+
* @param fileName Name for the downloaded file
|
|
73
|
+
* @param mimeType MIME type (application/pdf, image/png, image/jpeg)
|
|
74
|
+
* @param promise Promise to resolve with public file path
|
|
75
|
+
*/
|
|
76
|
+
@ReactMethod
|
|
77
|
+
public void downloadToPublicFolder(String sourcePath, String fileName, String mimeType, Promise promise) {
|
|
78
|
+
try {
|
|
79
|
+
Log.i(TAG, "📥 [DOWNLOAD] START - file: " + fileName + ", type: " + mimeType);
|
|
80
|
+
Log.i(TAG, "📁 [SOURCE] " + sourcePath);
|
|
81
|
+
|
|
82
|
+
// Verify source file exists
|
|
83
|
+
File sourceFile = new File(sourcePath);
|
|
84
|
+
if (!sourceFile.exists()) {
|
|
85
|
+
Log.e(TAG, "❌ [ERROR] Source file not found: " + sourcePath);
|
|
86
|
+
promise.reject("FILE_NOT_FOUND", "Source file not found: " + sourcePath);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Log.i(TAG, "📁 [SOURCE] File exists, size: " + sourceFile.length() + " bytes");
|
|
91
|
+
|
|
92
|
+
String publicPath;
|
|
93
|
+
|
|
94
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
95
|
+
// Android 10+ - Use MediaStore API (Scoped Storage)
|
|
96
|
+
publicPath = downloadUsingMediaStore(sourceFile, fileName, mimeType);
|
|
97
|
+
} else {
|
|
98
|
+
// Android 9 and below - Use legacy public directory
|
|
99
|
+
publicPath = downloadUsingLegacyStorage(sourceFile, fileName);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Log.i(TAG, "✅ [DOWNLOAD] SUCCESS - " + publicPath);
|
|
103
|
+
|
|
104
|
+
// Show notification with "Open Folder" action
|
|
105
|
+
showDownloadNotification(1, fileName);
|
|
106
|
+
|
|
107
|
+
promise.resolve(publicPath);
|
|
108
|
+
|
|
109
|
+
} catch (Exception e) {
|
|
110
|
+
Log.e(TAG, "❌ [DOWNLOAD] ERROR", e);
|
|
111
|
+
promise.reject("DOWNLOAD_ERROR", e.getMessage());
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Download using MediaStore API (Android 10+)
|
|
117
|
+
*/
|
|
118
|
+
private String downloadUsingMediaStore(File sourceFile, String fileName, String mimeType) throws Exception {
|
|
119
|
+
Log.i(TAG, "📱 [MEDIASTORE] Using MediaStore API for Android 10+");
|
|
120
|
+
|
|
121
|
+
ContentResolver resolver = reactContext.getContentResolver();
|
|
122
|
+
|
|
123
|
+
// Set up content values
|
|
124
|
+
ContentValues values = new ContentValues();
|
|
125
|
+
values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
|
|
126
|
+
values.put(MediaStore.Downloads.MIME_TYPE, mimeType);
|
|
127
|
+
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + FOLDER_NAME);
|
|
128
|
+
values.put(MediaStore.Downloads.IS_PENDING, 1); // Mark as pending while writing
|
|
129
|
+
|
|
130
|
+
Log.i(TAG, "📁 [MEDIASTORE] Creating entry in MediaStore...");
|
|
131
|
+
|
|
132
|
+
// Insert into MediaStore
|
|
133
|
+
Uri uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
|
|
134
|
+
|
|
135
|
+
if (uri == null) {
|
|
136
|
+
throw new Exception("Failed to create MediaStore entry");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Log.i(TAG, "📁 [MEDIASTORE] URI created: " + uri.toString());
|
|
140
|
+
Log.i(TAG, "📥 [COPY] Copying file content...");
|
|
141
|
+
|
|
142
|
+
// Copy file content
|
|
143
|
+
try (InputStream in = new FileInputStream(sourceFile);
|
|
144
|
+
OutputStream out = resolver.openOutputStream(uri)) {
|
|
145
|
+
|
|
146
|
+
if (out == null) {
|
|
147
|
+
throw new Exception("Failed to open output stream");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
byte[] buffer = new byte[8192];
|
|
151
|
+
int bytesRead;
|
|
152
|
+
long totalBytes = 0;
|
|
153
|
+
|
|
154
|
+
while ((bytesRead = in.read(buffer)) != -1) {
|
|
155
|
+
out.write(buffer, 0, bytesRead);
|
|
156
|
+
totalBytes += bytesRead;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Log.i(TAG, "✅ [COPY] Copied " + totalBytes + " bytes");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Mark as complete (no longer pending)
|
|
163
|
+
values.clear();
|
|
164
|
+
values.put(MediaStore.Downloads.IS_PENDING, 0);
|
|
165
|
+
resolver.update(uri, values, null, null);
|
|
166
|
+
|
|
167
|
+
Log.i(TAG, "✅ [MEDIASTORE] File published successfully");
|
|
168
|
+
|
|
169
|
+
// Return user-friendly path
|
|
170
|
+
String publicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
|
171
|
+
+ "/" + FOLDER_NAME + "/" + fileName;
|
|
172
|
+
|
|
173
|
+
return publicPath;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Download using legacy storage (Android 9 and below)
|
|
178
|
+
*/
|
|
179
|
+
private String downloadUsingLegacyStorage(File sourceFile, String fileName) throws Exception {
|
|
180
|
+
Log.i(TAG, "📱 [LEGACY] Using legacy storage for Android 9 and below");
|
|
181
|
+
|
|
182
|
+
// Get public Downloads directory
|
|
183
|
+
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
|
184
|
+
File appFolder = new File(downloadsDir, FOLDER_NAME);
|
|
185
|
+
|
|
186
|
+
// Create folder if needed
|
|
187
|
+
if (!appFolder.exists()) {
|
|
188
|
+
boolean created = appFolder.mkdirs();
|
|
189
|
+
Log.i(TAG, "📁 [LEGACY] Folder created: " + created);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
File destFile = new File(appFolder, fileName);
|
|
193
|
+
Log.i(TAG, "📁 [LEGACY] Destination: " + destFile.getAbsolutePath());
|
|
194
|
+
Log.i(TAG, "📥 [COPY] Copying file...");
|
|
195
|
+
|
|
196
|
+
// Copy file
|
|
197
|
+
try (InputStream in = new FileInputStream(sourceFile);
|
|
198
|
+
OutputStream out = new java.io.FileOutputStream(destFile)) {
|
|
199
|
+
|
|
200
|
+
byte[] buffer = new byte[8192];
|
|
201
|
+
int bytesRead;
|
|
202
|
+
long totalBytes = 0;
|
|
203
|
+
|
|
204
|
+
while ((bytesRead = in.read(buffer)) != -1) {
|
|
205
|
+
out.write(buffer, 0, bytesRead);
|
|
206
|
+
totalBytes += bytesRead;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Log.i(TAG, "✅ [COPY] Copied " + totalBytes + " bytes");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Trigger media scanner for legacy devices
|
|
213
|
+
android.media.MediaScannerConnection.scanFile(
|
|
214
|
+
reactContext,
|
|
215
|
+
new String[]{destFile.getAbsolutePath()},
|
|
216
|
+
new String[]{getMimeType(fileName)},
|
|
217
|
+
null
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
Log.i(TAG, "✅ [LEGACY] Media scanner notified");
|
|
221
|
+
|
|
222
|
+
return destFile.getAbsolutePath();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get MIME type from file extension
|
|
227
|
+
*/
|
|
228
|
+
private String getMimeType(String fileName) {
|
|
229
|
+
if (fileName.endsWith(".pdf")) {
|
|
230
|
+
return "application/pdf";
|
|
231
|
+
} else if (fileName.endsWith(".png")) {
|
|
232
|
+
return "image/png";
|
|
233
|
+
} else if (fileName.endsWith(".jpeg") || fileName.endsWith(".jpg")) {
|
|
234
|
+
return "image/jpeg";
|
|
235
|
+
}
|
|
236
|
+
return "application/octet-stream";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Show notification after successful download with "Open Folder" action
|
|
241
|
+
* @param fileCount Number of files downloaded
|
|
242
|
+
* @param fileName Name of the file (used for notification text)
|
|
243
|
+
*/
|
|
244
|
+
private void showDownloadNotification(int fileCount, String fileName) {
|
|
245
|
+
try {
|
|
246
|
+
Log.i(TAG, "📱 [NOTIFICATION] Showing notification for " + fileCount + " file(s)");
|
|
247
|
+
|
|
248
|
+
// Create intent to open Downloads/PDFDemoApp folder
|
|
249
|
+
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
250
|
+
Uri folderUri = Uri.parse("content://com.android.externalstorage.documents/document/primary:Download/" + FOLDER_NAME);
|
|
251
|
+
intent.setDataAndType(folderUri, "resource/folder");
|
|
252
|
+
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
253
|
+
|
|
254
|
+
// Create pending intent with FLAG_IMMUTABLE for Android 12+
|
|
255
|
+
PendingIntent pendingIntent = PendingIntent.getActivity(
|
|
256
|
+
reactContext,
|
|
257
|
+
0,
|
|
258
|
+
intent,
|
|
259
|
+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
|
260
|
+
? PendingIntent.FLAG_IMMUTABLE
|
|
261
|
+
: 0
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Build notification
|
|
265
|
+
String contentText = fileCount + " file(s) saved to Downloads/" + FOLDER_NAME;
|
|
266
|
+
NotificationCompat.Builder builder = new NotificationCompat.Builder(reactContext, NOTIFICATION_CHANNEL_ID)
|
|
267
|
+
.setSmallIcon(android.R.drawable.ic_menu_save)
|
|
268
|
+
.setContentTitle("✅ Export Complete")
|
|
269
|
+
.setContentText(contentText)
|
|
270
|
+
.setStyle(new NotificationCompat.BigTextStyle().bigText(contentText))
|
|
271
|
+
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
|
272
|
+
.setContentIntent(pendingIntent)
|
|
273
|
+
.setAutoCancel(true)
|
|
274
|
+
.addAction(android.R.drawable.ic_menu_view, "Open Folder", pendingIntent);
|
|
275
|
+
|
|
276
|
+
// Show notification
|
|
277
|
+
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(reactContext);
|
|
278
|
+
notificationManager.notify(1001, builder.build());
|
|
279
|
+
|
|
280
|
+
Log.i(TAG, "✅ [NOTIFICATION] Notification shown successfully");
|
|
281
|
+
|
|
282
|
+
} catch (Exception e) {
|
|
283
|
+
Log.e(TAG, "❌ [NOTIFICATION] Failed to show notification", e);
|
|
284
|
+
// Don't throw - notification is non-critical
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
package org.wonday.pdf;
|
|
2
|
+
|
|
3
|
+
import android.app.DownloadManager;
|
|
4
|
+
import android.content.Intent;
|
|
5
|
+
import android.net.Uri;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
|
|
8
|
+
import com.facebook.react.bridge.Promise;
|
|
9
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
10
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
11
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* FileManager - Native module for file operations like opening folders
|
|
15
|
+
*/
|
|
16
|
+
public class FileManager extends ReactContextBaseJavaModule {
|
|
17
|
+
private static final String TAG = "FileManager";
|
|
18
|
+
private static final String FOLDER_NAME = "PDFDemoApp";
|
|
19
|
+
private final ReactApplicationContext reactContext;
|
|
20
|
+
|
|
21
|
+
public FileManager(ReactApplicationContext reactContext) {
|
|
22
|
+
super(reactContext);
|
|
23
|
+
this.reactContext = reactContext;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Override
|
|
27
|
+
public String getName() {
|
|
28
|
+
return "FileManager";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Open the Downloads/PDFDemoApp folder in the file manager
|
|
33
|
+
* Multiple fallback strategies for maximum compatibility
|
|
34
|
+
*/
|
|
35
|
+
@ReactMethod
|
|
36
|
+
public void openDownloadsFolder(Promise promise) {
|
|
37
|
+
try {
|
|
38
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Attempting to open Downloads/" + FOLDER_NAME);
|
|
39
|
+
|
|
40
|
+
// Strategy 1: Try to open specific Downloads/PDFDemoApp folder
|
|
41
|
+
try {
|
|
42
|
+
Intent specificIntent = new Intent(Intent.ACTION_VIEW);
|
|
43
|
+
Uri folderUri = Uri.parse("content://com.android.externalstorage.documents/document/primary:Download/" + FOLDER_NAME);
|
|
44
|
+
specificIntent.setDataAndType(folderUri, "resource/folder");
|
|
45
|
+
specificIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
46
|
+
|
|
47
|
+
if (specificIntent.resolveActivity(reactContext.getPackageManager()) != null) {
|
|
48
|
+
reactContext.startActivity(specificIntent);
|
|
49
|
+
Log.i(TAG, "✅ [OPEN FOLDER] Opened specific folder via DocumentsUI");
|
|
50
|
+
promise.resolve(true);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
} catch (Exception e) {
|
|
54
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Strategy 1 failed, trying fallback...");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Strategy 2: Open Downloads app
|
|
58
|
+
try {
|
|
59
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Trying Downloads app");
|
|
60
|
+
Intent downloadsIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
|
|
61
|
+
downloadsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
62
|
+
|
|
63
|
+
if (downloadsIntent.resolveActivity(reactContext.getPackageManager()) != null) {
|
|
64
|
+
reactContext.startActivity(downloadsIntent);
|
|
65
|
+
Log.i(TAG, "✅ [OPEN FOLDER] Opened Downloads app");
|
|
66
|
+
promise.resolve(true);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
} catch (Exception e) {
|
|
70
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Strategy 2 failed, trying fallback...");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Strategy 3: Open Files app with generic CATEGORY_APP_FILES intent
|
|
74
|
+
try {
|
|
75
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Trying Files app");
|
|
76
|
+
Intent filesIntent = new Intent(Intent.ACTION_VIEW);
|
|
77
|
+
filesIntent.addCategory(Intent.CATEGORY_DEFAULT);
|
|
78
|
+
filesIntent.setType("resource/folder");
|
|
79
|
+
filesIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
80
|
+
|
|
81
|
+
if (filesIntent.resolveActivity(reactContext.getPackageManager()) != null) {
|
|
82
|
+
reactContext.startActivity(filesIntent);
|
|
83
|
+
Log.i(TAG, "✅ [OPEN FOLDER] Opened Files app");
|
|
84
|
+
promise.resolve(true);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
} catch (Exception e) {
|
|
88
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Strategy 3 failed");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Strategy 4: Try to launch any file manager using generic intent
|
|
92
|
+
try {
|
|
93
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Trying generic file manager");
|
|
94
|
+
Intent genericIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
|
95
|
+
genericIntent.setType("*/*");
|
|
96
|
+
genericIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
|
97
|
+
genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
98
|
+
|
|
99
|
+
if (genericIntent.resolveActivity(reactContext.getPackageManager()) != null) {
|
|
100
|
+
reactContext.startActivity(Intent.createChooser(genericIntent, "Open File Manager"));
|
|
101
|
+
Log.i(TAG, "✅ [OPEN FOLDER] Opened file picker");
|
|
102
|
+
promise.resolve(true);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
} catch (Exception e) {
|
|
106
|
+
Log.i(TAG, "📂 [OPEN FOLDER] Strategy 4 failed");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// All strategies failed
|
|
110
|
+
Log.w(TAG, "⚠️ [OPEN FOLDER] All strategies failed - no file manager available");
|
|
111
|
+
promise.reject("NO_FILE_MANAGER", "No file manager app available on this device");
|
|
112
|
+
|
|
113
|
+
} catch (Exception e) {
|
|
114
|
+
Log.e(TAG, "❌ [OPEN FOLDER] ERROR", e);
|
|
115
|
+
promise.reject("OPEN_FOLDER_ERROR", e.getMessage());
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|