react-native-advanced-share-intent 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 react-native-advanced-share-intent contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,345 @@
1
+ # react-native-advanced-share-intent
2
+
3
+ Lightweight React Native share intent handling for Android and iOS Share Extensions.
4
+
5
+ `react-native-advanced-share-intent` exposes a small typed API for reading content shared into your app from Android share sheets and iOS Share Extensions. It supports cold starts, foreground shares, text, URLs, images, videos, documents, multiple files, metadata, and explicit cleanup.
6
+
7
+ ## Features
8
+
9
+ - Android `ACTION_SEND` and `ACTION_SEND_MULTIPLE`
10
+ - iOS Share Extension support with App Groups
11
+ - Cold-start delivery with `getInitialShare()`
12
+ - Foreground delivery with `addShareListener()`
13
+ - Cleanup with `clearSharedData()`
14
+ - Text, URL, image, video, document, and multi-file shares
15
+ - File name, size, MIME type, original URI, and capture date metadata where available
16
+ - iOS Photos asset preservation with `ph://` local identifiers when available
17
+ - TypeScript definitions
18
+ - No runtime dependencies
19
+ - React Native autolinking support
20
+
21
+ ## Installation
22
+
23
+ Using npm:
24
+
25
+ ```sh
26
+ npm install react-native-advanced-share-intent
27
+ ```
28
+
29
+ Using Yarn:
30
+
31
+ ```sh
32
+ yarn add react-native-advanced-share-intent
33
+ ```
34
+
35
+ For iOS, install pods after adding the package:
36
+
37
+ ```sh
38
+ cd ios && pod install
39
+ ```
40
+
41
+ Rebuild the native app after installation.
42
+
43
+ ## Android Setup
44
+
45
+ Add share intent filters to the activity that hosts React Native. Keep `launchMode="singleTask"` so foreground shares arrive through `onNewIntent`.
46
+
47
+ ```xml
48
+ <activity
49
+ android:name=".MainActivity"
50
+ android:launchMode="singleTask"
51
+ android:exported="true">
52
+
53
+ <intent-filter>
54
+ <action android:name="android.intent.action.SEND" />
55
+ <category android:name="android.intent.category.DEFAULT" />
56
+ <data android:mimeType="text/*" />
57
+ <data android:mimeType="image/*" />
58
+ <data android:mimeType="video/*" />
59
+ <data android:mimeType="application/*" />
60
+ </intent-filter>
61
+
62
+ <intent-filter>
63
+ <action android:name="android.intent.action.SEND_MULTIPLE" />
64
+ <category android:name="android.intent.category.DEFAULT" />
65
+ <data android:mimeType="image/*" />
66
+ <data android:mimeType="video/*" />
67
+ <data android:mimeType="application/*" />
68
+ </intent-filter>
69
+ </activity>
70
+ ```
71
+
72
+ Android returns shared files as provider-backed `content://` URIs. The library does not copy large file bytes into JavaScript memory. Pass the URI to your uploader, media pipeline, or a native file-copy step when your app needs a local copy.
73
+
74
+ ## iOS Setup
75
+
76
+ iOS share delivery requires a Share Extension and an App Group. The library includes `AdvancedShareIntentShareExtension`, a base extension controller that collects shared items, stores a compact payload in the App Group, and opens the containing app.
77
+
78
+ 1. In Xcode, add a Share Extension target.
79
+ 2. Enable the same App Group on the main app target and the Share Extension target.
80
+ 3. Add a URL scheme to the main app, for example `myapp`.
81
+ 4. In your extension target, subclass the included controller:
82
+
83
+ ```swift
84
+ import AdvancedShareIntent
85
+
86
+ final class ShareViewController: AdvancedShareIntentShareExtension {
87
+ override var appGroupIdentifier: String {
88
+ "group.com.example.myapp"
89
+ }
90
+
91
+ override var containingAppScheme: String {
92
+ "myapp"
93
+ }
94
+ }
95
+ ```
96
+
97
+ 5. Configure the App Group from JavaScript before reading initial data:
98
+
99
+ ```ts
100
+ import ShareIntent from 'react-native-advanced-share-intent';
101
+
102
+ await ShareIntent.setAppGroupIdentifier('group.com.example.myapp');
103
+ await ShareIntent.setContainingAppScheme('myapp');
104
+
105
+ const initialShare = await ShareIntent.getInitialShare();
106
+ ```
107
+
108
+ The extension preserves Photos library items as `ph://` URIs with `localIdentifier` when iOS exposes a `PHAsset`. Other files are copied into the App Group container and returned as `file://` URLs. For long-running uploads, copy or consume those files promptly after receiving the payload, then call `clearSharedData()` to remove cached share-extension files.
109
+
110
+ ## Usage
111
+
112
+ Read the share that launched the app:
113
+
114
+ ```ts
115
+ import ShareIntent from 'react-native-advanced-share-intent';
116
+
117
+ const share = await ShareIntent.getInitialShare();
118
+
119
+ if (share) {
120
+ console.log(share.text);
121
+ console.log(share.files);
122
+ }
123
+ ```
124
+
125
+ Listen for new shares while the app is running:
126
+
127
+ ```ts
128
+ import { useEffect, useState } from 'react';
129
+ import ShareIntent, {
130
+ type ShareIntentPayload,
131
+ } from 'react-native-advanced-share-intent';
132
+
133
+ export function useShareIntent() {
134
+ const [share, setShare] = useState<ShareIntentPayload | null>(null);
135
+
136
+ useEffect(() => {
137
+ ShareIntent.getInitialShare().then(setShare);
138
+
139
+ const subscription = ShareIntent.addShareListener(setShare);
140
+ return () => subscription.remove();
141
+ }, []);
142
+
143
+ return share;
144
+ }
145
+ ```
146
+
147
+ Clear processed shared data:
148
+
149
+ ```ts
150
+ await ShareIntent.clearSharedData();
151
+ ```
152
+
153
+ Named function exports are also available:
154
+
155
+ ```ts
156
+ import {
157
+ getInitialShare,
158
+ addShareListener,
159
+ clearSharedData,
160
+ } from 'react-native-advanced-share-intent';
161
+ ```
162
+
163
+ For a compact copyable component, see [`examples/BasicShareIntent.tsx`](examples/BasicShareIntent.tsx).
164
+
165
+ ## API Reference
166
+
167
+ ### `getInitialShare()`
168
+
169
+ ```ts
170
+ getInitialShare(): Promise<ShareIntentPayload | null>
171
+ ```
172
+
173
+ Returns the share payload that launched the app, or `null` when the app was not opened from a share.
174
+
175
+ ### `addShareListener(listener)`
176
+
177
+ ```ts
178
+ addShareListener(listener: ShareIntentListener): EmitterSubscription
179
+ ```
180
+
181
+ Subscribes to share payloads delivered after the app is already running. Call `subscription.remove()` during cleanup.
182
+
183
+ ### `clearSharedData()`
184
+
185
+ ```ts
186
+ clearSharedData(): Promise<void>
187
+ ```
188
+
189
+ Clears the cached share payload and removes iOS App Group files created by the Share Extension.
190
+
191
+ ### `setAppGroupIdentifier(identifier)`
192
+
193
+ ```ts
194
+ setAppGroupIdentifier(identifier: string): Promise<void>
195
+ ```
196
+
197
+ iOS only. Sets the App Group used by the containing app and Share Extension.
198
+
199
+ ### `setContainingAppScheme(scheme)`
200
+
201
+ ```ts
202
+ setContainingAppScheme(scheme: string): Promise<void>
203
+ ```
204
+
205
+ iOS only. Stores the URL scheme used by the Share Extension to reopen the containing app.
206
+
207
+ ### Types
208
+
209
+ ```ts
210
+ type ShareIntentPayload = {
211
+ text?: string;
212
+ subject?: string;
213
+ title?: string;
214
+ mimeType?: string;
215
+ files: SharedFile[];
216
+ webUrl?: string;
217
+ isInitial: boolean;
218
+ receivedAt: number;
219
+ };
220
+
221
+ type SharedFile = {
222
+ uri: string;
223
+ fileName?: string;
224
+ name?: string;
225
+ mimeType?: string;
226
+ size?: number;
227
+ type: 'text' | 'image' | 'video' | 'document' | 'unknown';
228
+ dateTaken?: number;
229
+ localIdentifier?: string;
230
+ originalUri?: string;
231
+ };
232
+ ```
233
+
234
+ ## Example App
235
+
236
+ The example app stays in the GitHub repository so contributors and users can test the native behavior. It is excluded from the npm package through the root `package.json` `files` allowlist.
237
+
238
+ Clone and run the example app with npm:
239
+
240
+ ```sh
241
+ git clone https://github.com/engr-touqeer/react-native-advanced-share-intent
242
+ cd react-native-advanced-share-intent/example
243
+ npm install
244
+ cd ios && pod install
245
+ cd ..
246
+ npm run ios
247
+ npm run android
248
+ ```
249
+
250
+ Or run it with Yarn:
251
+
252
+ ```sh
253
+ git clone https://github.com/engr-touqeer/react-native-advanced-share-intent
254
+ cd react-native-advanced-share-intent/example
255
+ yarn install
256
+ cd ios && pod install
257
+ cd ..
258
+ yarn ios
259
+ yarn android
260
+ ```
261
+
262
+ The Android example is configured to receive share intents and display the parsed payload. For iOS testing, add a Share Extension target to the example app and follow the iOS setup above with your own App Group and URL scheme.
263
+
264
+ ## Publishing
265
+
266
+ Install, build, and inspect the package with npm:
267
+
268
+ ```sh
269
+ npm install
270
+ npm run build
271
+ npm pack --dry-run
272
+ ```
273
+
274
+ Install, build, and inspect the package with Yarn:
275
+
276
+ ```sh
277
+ yarn install
278
+ yarn build
279
+ yarn pack --dry-run
280
+ ```
281
+
282
+ Publish manually only after reviewing the dry-run output:
283
+
284
+ ```sh
285
+ npm login
286
+ npm whoami
287
+ npm publish --access public
288
+ ```
289
+
290
+ npm requires either account 2FA or a granular access token with publish access and bypass 2FA enabled. If publish fails with `E403`, confirm 2FA is enabled for your npm account or create a granular npm access token with the required publishing permissions.
291
+
292
+ The npm package is intentionally limited to:
293
+
294
+ ```txt
295
+ android/
296
+ ios/
297
+ src/
298
+ index.js
299
+ index.mjs
300
+ index.d.ts
301
+ react-native.config.js
302
+ react-native-advanced-share-intent.podspec
303
+ README.md
304
+ LICENSE
305
+ ```
306
+
307
+ This keeps the published package lightweight while preserving the full example app in GitHub.
308
+
309
+ ## Lockfile Policy
310
+
311
+ Use one package manager lockfile style in committed changes. This repository uses npm as the primary lockfile source with `package-lock.json`. Yarn is supported for installs and scripts, but `yarn.lock` should not be committed unless the project intentionally switches to Yarn as the primary package manager.
312
+
313
+ ## Troubleshooting
314
+
315
+ ### The native module is not linked
316
+
317
+ Run `pod install` for iOS, rebuild the native app, and make sure React Native autolinking can see the package.
318
+
319
+ ### Android shares do not arrive while the app is open
320
+
321
+ Confirm the host activity uses `android:launchMode="singleTask"` and has the `SEND` or `SEND_MULTIPLE` intent filters for the MIME types you want to support.
322
+
323
+ ### iOS returns `null`
324
+
325
+ Confirm the main app and Share Extension use the same App Group, the Share Extension subclasses `AdvancedShareIntentShareExtension`, and JavaScript calls `setAppGroupIdentifier()` before `getInitialShare()`.
326
+
327
+ ### Large files are slow or fail to upload
328
+
329
+ The library returns provider or App Group file URIs. Copy, stream, or upload those files from a native-capable file pipeline instead of reading large files into JavaScript memory.
330
+
331
+ ## Contributing
332
+
333
+ Issues and pull requests are welcome. Please keep changes focused, preserve Android and iOS share intent behavior, and test with text, one file, and multiple files where possible.
334
+
335
+ Before opening a pull request:
336
+
337
+ ```sh
338
+ npm install
339
+ npm run build
340
+ npm pack --dry-run
341
+ ```
342
+
343
+ ## License
344
+
345
+ MIT
@@ -0,0 +1,37 @@
1
+ buildscript {
2
+ ext.safeExtGet = { prop, fallback ->
3
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
4
+ }
5
+
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+
11
+ dependencies {
12
+ classpath("com.android.tools.build:gradle:8.7.3")
13
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
14
+ }
15
+ }
16
+
17
+ apply plugin: "com.android.library"
18
+ apply plugin: "org.jetbrains.kotlin.android"
19
+
20
+ android {
21
+ namespace "com.advancedshareintent"
22
+ compileSdkVersion safeExtGet("compileSdkVersion", 35)
23
+
24
+ defaultConfig {
25
+ minSdkVersion safeExtGet("minSdkVersion", 23)
26
+ targetSdkVersion safeExtGet("targetSdkVersion", 35)
27
+ }
28
+ }
29
+
30
+ repositories {
31
+ google()
32
+ mavenCentral()
33
+ }
34
+
35
+ dependencies {
36
+ implementation "com.facebook.react:react-android"
37
+ }
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,266 @@
1
+ package com.advancedshareintent
2
+
3
+ import android.app.Activity
4
+ import android.content.ClipData
5
+ import android.content.ContentResolver
6
+ import android.content.Intent
7
+ import android.database.Cursor
8
+ import android.net.Uri
9
+ import android.os.Bundle
10
+ import android.os.Handler
11
+ import android.os.Looper
12
+ import android.provider.OpenableColumns
13
+ import android.provider.MediaStore
14
+ import com.facebook.react.bridge.ActivityEventListener
15
+ import com.facebook.react.bridge.Arguments
16
+ import com.facebook.react.bridge.LifecycleEventListener
17
+ import com.facebook.react.bridge.Promise
18
+ import com.facebook.react.bridge.ReactApplicationContext
19
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
20
+ import com.facebook.react.bridge.ReactMethod
21
+ import com.facebook.react.bridge.WritableMap
22
+ import com.facebook.react.modules.core.DeviceEventManagerModule
23
+
24
+ class AdvancedShareIntentModule(
25
+ private val reactContext: ReactApplicationContext
26
+ ) : ReactContextBaseJavaModule(reactContext), ActivityEventListener, LifecycleEventListener {
27
+ private val mainHandler = Handler(Looper.getMainLooper())
28
+ private var initialShare: WritableMap? = null
29
+ private var latestShare: WritableMap? = null
30
+ private var hasListeners = false
31
+
32
+ init {
33
+ reactContext.addActivityEventListener(this)
34
+ reactContext.addLifecycleEventListener(this)
35
+ }
36
+
37
+ override fun getName(): String = NAME
38
+
39
+ override fun initialize() {
40
+ super.initialize()
41
+ reactContext.currentActivity?.intent?.let { intent ->
42
+ parseShareIntent(intent, true)?.let { payload ->
43
+ initialShare = payload.copy()
44
+ latestShare = payload.copy()
45
+ }
46
+ }
47
+ }
48
+
49
+ override fun onNewIntent(intent: Intent) {
50
+ parseShareIntent(intent, false)?.let { payload ->
51
+ latestShare = payload.copy()
52
+ sendEventWhenReady(payload.copy())
53
+ }
54
+ }
55
+
56
+ override fun onActivityResult(
57
+ activity: Activity,
58
+ requestCode: Int,
59
+ resultCode: Int,
60
+ data: Intent?
61
+ ) = Unit
62
+
63
+ @ReactMethod
64
+ fun getInitialShare(promise: Promise) {
65
+ try {
66
+ val payload = initialShare ?: reactContext.currentActivity?.intent?.let { parseShareIntent(it, true) }
67
+ initialShare = payload?.copy()
68
+ promise.resolve(payload?.copy())
69
+ } catch (error: Exception) {
70
+ promise.reject("advanced_share_intent_initial_error", error)
71
+ }
72
+ }
73
+
74
+ @ReactMethod
75
+ fun clearSharedData(promise: Promise) {
76
+ initialShare = null
77
+ latestShare = null
78
+ reactContext.currentActivity?.intent?.apply {
79
+ action = null
80
+ type = null
81
+ data = null
82
+ clipData = null
83
+ replaceExtras(Bundle())
84
+ }
85
+ promise.resolve(null)
86
+ }
87
+
88
+ @ReactMethod
89
+ fun addListener(eventName: String) {
90
+ hasListeners = true
91
+ latestShare?.copy()?.let { sendEventWhenReady(it) }
92
+ }
93
+
94
+ @ReactMethod
95
+ fun removeListeners(count: Int) {
96
+ hasListeners = false
97
+ }
98
+
99
+ override fun onHostResume() {
100
+ latestShare?.copy()?.let { sendEventWhenReady(it) }
101
+ }
102
+
103
+ override fun onHostPause() = Unit
104
+
105
+ override fun onHostDestroy() = Unit
106
+
107
+ private fun parseShareIntent(intent: Intent, isInitial: Boolean): WritableMap? {
108
+ val action = intent.action ?: return null
109
+ if (action != Intent.ACTION_SEND && action != Intent.ACTION_SEND_MULTIPLE) {
110
+ return null
111
+ }
112
+
113
+ val mimeType = intent.type ?: "*/*"
114
+ val files = Arguments.createArray()
115
+ val text = intent.getStringExtra(Intent.EXTRA_TEXT)
116
+ val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)
117
+ val title = intent.getStringExtra(Intent.EXTRA_TITLE)
118
+
119
+ collectUris(intent).forEach { uri ->
120
+ grantReadPermission(uri)
121
+ files.pushMap(uriToFileMap(uri, mimeType))
122
+ }
123
+
124
+ if (text.isNullOrBlank() && files.size() == 0) {
125
+ return null
126
+ }
127
+
128
+ return Arguments.createMap().apply {
129
+ if (!text.isNullOrBlank()) putString("text", text)
130
+ if (!subject.isNullOrBlank()) putString("subject", subject)
131
+ if (!title.isNullOrBlank()) putString("title", title)
132
+ putString("mimeType", mimeType)
133
+ putArray("files", files)
134
+ putBoolean("isInitial", isInitial)
135
+ putDouble("receivedAt", System.currentTimeMillis().toDouble())
136
+ extractWebUrl(text)?.let { putString("webUrl", it) }
137
+ }
138
+ }
139
+
140
+ private fun collectUris(intent: Intent): List<Uri> {
141
+ val uris = LinkedHashSet<Uri>()
142
+ val stream = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
143
+ if (stream != null) uris.add(stream)
144
+
145
+ val streams = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
146
+ streams?.forEach { uri -> if (uri != null) uris.add(uri) }
147
+
148
+ collectClipData(intent.clipData, uris)
149
+ intent.data?.let { uris.add(it) }
150
+ return uris.toList()
151
+ }
152
+
153
+ private fun collectClipData(clipData: ClipData?, uris: MutableSet<Uri>) {
154
+ if (clipData == null) return
155
+ for (index in 0 until clipData.itemCount) {
156
+ clipData.getItemAt(index)?.uri?.let { uris.add(it) }
157
+ }
158
+ }
159
+
160
+ private fun uriToFileMap(uri: Uri, fallbackMimeType: String): WritableMap {
161
+ val resolver = reactContext.contentResolver
162
+ val mimeType = resolver.getType(uri) ?: fallbackMimeType
163
+ val metadata = queryMetadata(resolver, uri)
164
+
165
+ return Arguments.createMap().apply {
166
+ putString("uri", uri.toString())
167
+ putString("type", classifyMimeType(mimeType))
168
+ putString("mimeType", mimeType)
169
+ metadata.name?.let { putString("fileName", it) }
170
+ metadata.name?.let { putString("name", it) }
171
+ metadata.size?.let { putDouble("size", it.toDouble()) }
172
+ metadata.dateTaken?.let { putDouble("dateTaken", it.toDouble()) }
173
+ putString("originalUri", uri.toString())
174
+ }
175
+ }
176
+
177
+ private fun queryMetadata(resolver: ContentResolver, uri: Uri): FileMetadata {
178
+ if (uri.scheme == ContentResolver.SCHEME_FILE) {
179
+ val path = uri.path ?: return FileMetadata(null, null, null)
180
+ val file = java.io.File(path)
181
+ return FileMetadata(file.name, file.takeIf { it.exists() }?.length(), file.takeIf { it.exists() }?.lastModified())
182
+ }
183
+
184
+ var cursor: Cursor? = null
185
+ return try {
186
+ cursor = resolver.query(
187
+ uri,
188
+ arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, MediaStore.MediaColumns.DATE_TAKEN),
189
+ null,
190
+ null,
191
+ null
192
+ )
193
+ if (cursor != null && cursor.moveToFirst()) {
194
+ val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
195
+ val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
196
+ val dateTakenIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATE_TAKEN)
197
+ FileMetadata(
198
+ name = if (nameIndex >= 0) cursor.getString(nameIndex) else null,
199
+ size = if (sizeIndex >= 0 && !cursor.isNull(sizeIndex)) cursor.getLong(sizeIndex) else null,
200
+ dateTaken = if (dateTakenIndex >= 0 && !cursor.isNull(dateTakenIndex)) cursor.getLong(dateTakenIndex) else null
201
+ )
202
+ } else {
203
+ FileMetadata(uri.lastPathSegment, null, null)
204
+ }
205
+ } catch (_: Exception) {
206
+ FileMetadata(uri.lastPathSegment, null, null)
207
+ } finally {
208
+ cursor?.close()
209
+ }
210
+ }
211
+
212
+ private fun grantReadPermission(uri: Uri) {
213
+ try {
214
+ val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
215
+ reactContext.grantUriPermission(reactContext.packageName, uri, flags)
216
+ reactContext.contentResolver.takePersistableUriPermission(uri, flags)
217
+ } catch (_: Exception) {
218
+ // Many providers do not support persisted grants. The temporary share grant is still valid.
219
+ }
220
+ }
221
+
222
+ private fun sendEventWhenReady(payload: WritableMap, attempt: Int = 0) {
223
+ if (!hasListeners && attempt < MAX_DELIVERY_ATTEMPTS) {
224
+ mainHandler.postDelayed({ sendEventWhenReady(payload.copy(), attempt + 1) }, DELIVERY_RETRY_MS)
225
+ return
226
+ }
227
+
228
+ if (!reactContext.hasActiveCatalystInstance()) {
229
+ if (attempt < MAX_DELIVERY_ATTEMPTS) {
230
+ mainHandler.postDelayed({ sendEventWhenReady(payload.copy(), attempt + 1) }, DELIVERY_RETRY_MS)
231
+ }
232
+ return
233
+ }
234
+
235
+ reactContext
236
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
237
+ .emit(EVENT_NAME, payload)
238
+ }
239
+
240
+ private fun WritableMap.copy(): WritableMap = Arguments.makeNativeMap(this.toHashMap())
241
+
242
+ private data class FileMetadata(val name: String?, val size: Long?, val dateTaken: Long?)
243
+
244
+ companion object {
245
+ const val NAME = "AdvancedShareIntent"
246
+ private const val EVENT_NAME = "AdvancedShareIntentReceived"
247
+ private const val MAX_DELIVERY_ATTEMPTS = 10
248
+ private const val DELIVERY_RETRY_MS = 500L
249
+
250
+ fun classifyMimeType(mimeType: String?): String {
251
+ val value = mimeType?.lowercase() ?: return "unknown"
252
+ return when {
253
+ value.startsWith("image/") -> "image"
254
+ value.startsWith("video/") -> "video"
255
+ value.startsWith("text/") -> "text"
256
+ value == "text/plain" -> "text"
257
+ else -> "document"
258
+ }
259
+ }
260
+
261
+ private fun extractWebUrl(text: String?): String? {
262
+ if (text.isNullOrBlank()) return null
263
+ return Regex("""https?://\S+""").find(text)?.value
264
+ }
265
+ }
266
+ }
@@ -0,0 +1,16 @@
1
+ package com.advancedshareintent
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class AdvancedShareIntentPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
+ return listOf(AdvancedShareIntentModule(reactContext))
11
+ }
12
+
13
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
+ return emptyList()
15
+ }
16
+ }
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { default } from './src';
2
+ export * from './src';