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.
@@ -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
+