diff --git a/android/src/main/java/com/vydia/RNUploader/Connectivity.kt b/android/src/main/java/com/vydia/RNUploader/Connectivity.kt new file mode 100644 index 00000000..3b3fa823 --- /dev/null +++ b/android/src/main/java/com/vydia/RNUploader/Connectivity.kt @@ -0,0 +1,30 @@ +package com.vydia.RNUploader + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED +import android.net.NetworkCapabilities.TRANSPORT_WIFI + +enum class Connectivity { + NoWifi, NoInternet, Ok; + + companion object { + fun fetch(context: Context, wifiOnly: Boolean): Connectivity { + val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val network = manager.activeNetwork + val capabilities = manager.getNetworkCapabilities(network) + + val hasInternet = capabilities?.hasCapability(NET_CAPABILITY_VALIDATED) == true + + // not wifiOnly, return early + if (!wifiOnly) return if (hasInternet) Ok else NoInternet + + // handle wifiOnly + return if (hasInternet && capabilities?.hasTransport(TRANSPORT_WIFI) == true) + Ok + else + NoWifi // don't return NoInternet here, more direct to request to join wifi + } + } +} + diff --git a/android/src/main/java/com/vydia/RNUploader/MissingOptionException.kt b/android/src/main/java/com/vydia/RNUploader/MissingOptionException.kt new file mode 100644 index 00000000..c1ab351d --- /dev/null +++ b/android/src/main/java/com/vydia/RNUploader/MissingOptionException.kt @@ -0,0 +1,4 @@ +package com.vydia.RNUploader + +class MissingOptionException(optionName: String) : + IllegalArgumentException("Missing '$optionName'") \ No newline at end of file diff --git a/android/src/main/java/com/vydia/RNUploader/Notification.kt b/android/src/main/java/com/vydia/RNUploader/Notification.kt new file mode 100644 index 00000000..a1e7b371 --- /dev/null +++ b/android/src/main/java/com/vydia/RNUploader/Notification.kt @@ -0,0 +1,119 @@ +package com.vydia.RNUploader + +import android.app.Notification +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import com.facebook.react.bridge.ReadableMap + + +object UploadNotification { + var id: Int = 0 + private set + var title: String = "Uploading files" + private set + var titleNoInternet: String = "Waiting for internet connection" + private set + var titleNoWifi: String = "Waiting for WiFi connection" + private set + var channel: String = "File Uploads" + private set + var maxRetries: Int = 5 + private set + + private var activeUpload: Upload? = null + + @Synchronized + fun setActiveUpload(upload: Upload?) { + this.activeUpload = upload + } + + @Synchronized + fun getActiveUpload(): Upload? { + return this.activeUpload + } + + @Synchronized + fun releaseActiveUpload(upload: Upload) { + if (this.activeUpload?.id == upload.id) this.activeUpload = null + } + + fun setOptions(opts: ReadableMap) { + id = opts.getString("notificationId")?.hashCode() + ?: throw MissingOptionException("notificationId") + title = opts.getString("notificationTitle") + ?: throw MissingOptionException("notificationTitle") + titleNoInternet = opts.getString("notificationTitleNoInternet") + ?: throw MissingOptionException("notificationTitleNoInternet") + titleNoWifi = opts.getString("notificationTitleNoWifi") + ?: throw MissingOptionException("notificationTitleNoWifi") + channel = opts.getString("notificationChannel") + ?: throw MissingOptionException("notificationChannel") + maxRetries = if (opts.hasKey("maxRetries")) opts.getInt("maxRetries") else 5 + } + + // builds the notification required to enable Foreground mode + fun build(context: Context): Notification { + // Determine wifiOnly preference for connectivity check: + // - If an upload is actively running, use its preference + // - Otherwise, check the queue: if ANY upload can proceed with just mobile data (wifiOnly=false), + // we only need internet to make progress. If ALL uploads need WiFi, we need WiFi. + // This ensures the notification ("Waiting for internet" vs "Waiting for WiFi") reflects + // the minimum connectivity required to make progress. + val wifiOnly = getActiveUpload()?.wifiOnly ?: !UploadProgress.hasNonWifiOnlyUploads() + val channel = channel + val progress = UploadProgress.total() + val progress2Decimals = "%.2f".format(progress) + val title = when (Connectivity.fetch(context, wifiOnly)) { + Connectivity.NoWifi -> titleNoWifi + Connectivity.NoInternet -> titleNoInternet + Connectivity.Ok -> title + } + + // Custom layout for progress notification. + // The default hides the % text. This one shows it on the right, + // like most examples in various docs. + val content = RemoteViews(context.packageName, R.layout.notification) + content.setTextViewText(R.id.notification_title, title) + content.setTextViewText(R.id.notification_progress, "${progress2Decimals}%") + content.setProgressBar(R.id.notification_progress_bar, 100, progress.toInt(), false) + + return NotificationCompat.Builder(context, channel).run { + // Starting Android 12, the notification shows up with a confusing delay of 10s. + // This fixes that delay. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + foregroundServiceBehavior = Notification.FOREGROUND_SERVICE_IMMEDIATE + + // Required by android. Here we use the system's default upload icon + setSmallIcon(android.R.drawable.stat_sys_upload) + // These prevent the notification from being force-dismissed or dismissed when pressed + setOngoing(true) + setAutoCancel(false) + // These help show the same custom content when the notification collapses and expands + setCustomContentView(content) + setCustomBigContentView(content) + // opens the app when the notification is pressed + setContentIntent(openAppIntent(context)) + build() + } + } + + fun update(context: Context) { + val notification = build(context) + val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + manager.notify(id, notification) + } +} + + +private fun openAppIntent(context: Context): PendingIntent? { + val intent = Intent(context, NotificationReceiver::class.java) + val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + return PendingIntent.getBroadcast(context, "RNFileUpload-notification".hashCode(), intent, flags) +} + + diff --git a/android/src/main/java/com/vydia/RNUploader/Upload.kt b/android/src/main/java/com/vydia/RNUploader/Upload.kt index db7b83d5..06906d3d 100644 --- a/android/src/main/java/com/vydia/RNUploader/Upload.kt +++ b/android/src/main/java/com/vydia/RNUploader/Upload.kt @@ -11,25 +11,15 @@ data class Upload( val url: String, val path: String, val method: String, - val maxRetries: Int, val wifiOnly: Boolean, val headers: Map, - val notificationId: Int, - val notificationTitle: String, - val notificationTitleNoInternet: String, - val notificationTitleNoWifi: String, - val notificationChannel: String, ) { - class MissingOptionException(optionName: String) : - IllegalArgumentException("Missing '$optionName'") - companion object { fun fromReadableMap(map: ReadableMap) = Upload( id = map.getString("customUploadId") ?: UUID.randomUUID().toString(), url = map.getString(Upload::url.name) ?: throw MissingOptionException(Upload::url.name), path = map.getString(Upload::path.name) ?: throw MissingOptionException(Upload::path.name), method = map.getString(Upload::method.name) ?: "POST", - maxRetries = if (map.hasKey(Upload::maxRetries.name)) map.getInt(Upload::maxRetries.name) else 5, wifiOnly = if (map.hasKey(Upload::wifiOnly.name)) map.getBoolean(Upload::wifiOnly.name) else false, headers = map.getMap(Upload::headers.name).let { headers -> if (headers == null) return@let mapOf() @@ -39,16 +29,6 @@ data class Upload( } return@let map }, - notificationId = map.getString(Upload::notificationId.name)?.hashCode() - ?: throw MissingOptionException(Upload::notificationId.name), - notificationTitle = map.getString(Upload::notificationTitle.name) - ?: throw MissingOptionException(Upload::notificationTitle.name), - notificationTitleNoInternet = map.getString(Upload::notificationTitleNoInternet.name) - ?: throw MissingOptionException(Upload::notificationTitleNoInternet.name), - notificationTitleNoWifi = map.getString(Upload::notificationTitleNoWifi.name) - ?: throw MissingOptionException(Upload::notificationTitleNoWifi.name), - notificationChannel = map.getString(Upload::notificationChannel.name) - ?: throw MissingOptionException(Upload::notificationChannel.name), ) } } diff --git a/android/src/main/java/com/vydia/RNUploader/UploadProgress.kt b/android/src/main/java/com/vydia/RNUploader/UploadProgress.kt index 03b9455b..226cc8bb 100644 --- a/android/src/main/java/com/vydia/RNUploader/UploadProgress.kt +++ b/android/src/main/java/com/vydia/RNUploader/UploadProgress.kt @@ -8,6 +8,7 @@ object UploadProgress { private data class Progress( var bytesUploaded: Long, val size: Long, + val wifiOnly: Boolean, var complete: Boolean = false ) { fun complete() { @@ -19,8 +20,8 @@ object UploadProgress { private val map = mutableMapOf() @Synchronized - fun add(id: String, size: Long) { - map[id] = Progress(bytesUploaded = 0L, size = size) + fun add(id: String, size: Long, wifiOnly: Boolean) { + map[id] = Progress(bytesUploaded = 0L, size = size, wifiOnly = wifiOnly) } @Synchronized @@ -55,4 +56,16 @@ object UploadProgress { private fun clearIfCompleted() { if (map.values.all { it.complete }) map.clear() } + + /** + * Returns true if any incomplete upload can proceed without WiFi (wifiOnly=false). + * Used to determine notification text when no upload is actively running: + * - If true: at least one upload only needs mobile data, so show "Waiting for internet" + * - If false: all uploads need WiFi, so show "Waiting for WiFi" + * This ensures the notification reflects the minimum connectivity needed to make progress. + */ + @Synchronized + fun hasNonWifiOnlyUploads(): Boolean { + return map.values.any { !it.complete && !it.wifiOnly } + } } \ No newline at end of file diff --git a/android/src/main/java/com/vydia/RNUploader/UploadWorker.kt b/android/src/main/java/com/vydia/RNUploader/UploadWorker.kt index a3bbec7c..628a2192 100644 --- a/android/src/main/java/com/vydia/RNUploader/UploadWorker.kt +++ b/android/src/main/java/com/vydia/RNUploader/UploadWorker.kt @@ -1,17 +1,8 @@ package com.vydia.RNUploader -import android.app.Notification -import android.app.NotificationManager -import android.app.PendingIntent import android.content.Context -import android.content.Intent import android.content.pm.ServiceInfo -import android.net.ConnectivityManager -import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED -import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.os.Build -import android.widget.RemoteViews -import androidx.core.app.NotificationCompat import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo import androidx.work.WorkerParameters @@ -48,8 +39,6 @@ private val client = OkHttpClient.Builder() .callTimeout(REQUEST_TIMEOUT, REQUEST_TIMEOUT_UNIT) .build() -private enum class Connectivity { NoWifi, NoInternet, Ok } - class UploadWorker(private val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) { @@ -57,9 +46,6 @@ class UploadWorker(private val context: Context, params: WorkerParameters) : private lateinit var upload: Upload private var retries = 0 - private var connectivity = Connectivity.Ok - private val notificationManager = - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager override suspend fun doWork(): Result = withContext(Dispatchers.IO) { // Retrieve the upload. If this throws errors, error reporting won't work. @@ -122,15 +108,21 @@ class UploadWorker(private val context: Context, params: WorkerParameters) : // Register progress asap so the total progress is accurate // This needs to happen before the semaphore wait - UploadProgress.add(upload.id, size) + UploadProgress.add(upload.id, size, upload.wifiOnly) // Don't bother to run on an invalid network - if (!validateAndReportConnectivity()) return null + if (Connectivity.fetch(context, upload.wifiOnly) != Connectivity.Ok) { + UploadNotification.update(context) + return null + } // wait for its turn to run semaphore.acquire() - try { + // mark as active upload for notification + UploadNotification.setActiveUpload(upload) + UploadNotification.update(context) + return okhttpUpload(client, upload, file) { progress -> handleProgress(progress, size) } @@ -141,23 +133,28 @@ class UploadWorker(private val context: Context, params: WorkerParameters) : throw error } finally { semaphore.release() + // by this point, another worker might have started and set itself as active upload + // so only clear if it's still this upload + UploadNotification.releaseActiveUpload(upload) } } private fun handleProgress(bytesSentTotal: Long, fileSize: Long) { UploadProgress.set(upload.id, bytesSentTotal) EventReporter.progress(upload.id, bytesSentTotal, fileSize) - notificationManager.notify(upload.notificationId, buildNotification()) + UploadNotification.update(context) } private fun handleSuccess(response: UploadResponse) { UploadProgress.complete(upload.id) EventReporter.success(upload.id, response) + UploadNotification.update(context) } private fun handleError(error: Throwable) { UploadProgress.remove(upload.id) EventReporter.error(upload.id, error) + UploadNotification.update(context) } // Check if cancelled by user or new worker with same ID @@ -167,6 +164,7 @@ class UploadWorker(private val context: Context, params: WorkerParameters) : UploadProgress.remove(upload.id) EventReporter.cancelled(upload.id) + UploadNotification.update(context) return true } @@ -176,7 +174,7 @@ class UploadWorker(private val context: Context, params: WorkerParameters) : // Error was thrown due to unmet network preferences. // Also happens every time you switch from one network to any other - if (!validateAndReportConnectivity()) unlimitedRetry = true + if (Connectivity.fetch(context, upload.wifiOnly) != Connectivity.Ok) unlimitedRetry = true // Due to the flaky nature of networking, sometimes the network is // valid but the URL is still inaccessible, so keep waiting until // the URL is accessible @@ -197,59 +195,13 @@ class UploadWorker(private val context: Context, params: WorkerParameters) : } retries = if (unlimitedRetry) 0 else retries + 1 - return retries <= upload.maxRetries + return retries <= UploadNotification.maxRetries } - // Checks connection and alerts connection issues - private fun validateAndReportConnectivity(): Boolean { - this.connectivity = validateConnectivity(context, upload.wifiOnly) - // alert connectivity mode - notificationManager.notify(upload.notificationId, buildNotification()) - return this.connectivity == Connectivity.Ok - } - - // builds the notification required to enable Foreground mode - fun buildNotification(): Notification { - val channel = upload.notificationChannel - val progress = UploadProgress.total() - val progress2Decimals = "%.2f".format(progress) - val title = when (connectivity) { - Connectivity.NoWifi -> upload.notificationTitleNoWifi - Connectivity.NoInternet -> upload.notificationTitleNoInternet - Connectivity.Ok -> upload.notificationTitle - } - - // Custom layout for progress notification. - // The default hides the % text. This one shows it on the right, - // like most examples in various docs. - val content = RemoteViews(context.packageName, R.layout.notification) - content.setTextViewText(R.id.notification_title, title) - content.setTextViewText(R.id.notification_progress, "${progress2Decimals}%") - content.setProgressBar(R.id.notification_progress_bar, 100, progress.toInt(), false) - - return NotificationCompat.Builder(context, channel).run { - // Starting Android 12, the notification shows up with a confusing delay of 10s. - // This fixes that delay. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) - foregroundServiceBehavior = Notification.FOREGROUND_SERVICE_IMMEDIATE - - // Required by android. Here we use the system's default upload icon - setSmallIcon(android.R.drawable.stat_sys_upload) - // These prevent the notification from being force-dismissed or dismissed when pressed - setOngoing(true) - setAutoCancel(false) - // These help show the same custom content when the notification collapses and expands - setCustomContentView(content) - setCustomBigContentView(content) - // opens the app when the notification is pressed - setContentIntent(openAppIntent(context)) - build() - } - } override suspend fun getForegroundInfo(): ForegroundInfo { - val notification = buildNotification() - val id = upload.notificationId + val notification = UploadNotification.build(context) + val id = UploadNotification.id // Starting Android 14, FOREGROUND_SERVICE_TYPE_DATA_SYNC is mandatory, otherwise app will crash return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) ForegroundInfo(id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) @@ -258,28 +210,4 @@ class UploadWorker(private val context: Context, params: WorkerParameters) : } } -// This is outside and synchronized to ensure consistent status across workers -@Synchronized -private fun validateConnectivity(context: Context, wifiOnly: Boolean): Connectivity { - val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val network = manager.activeNetwork - val capabilities = manager.getNetworkCapabilities(network) - - val hasInternet = capabilities?.hasCapability(NET_CAPABILITY_VALIDATED) == true - - // not wifiOnly, return early - if (!wifiOnly) return if (hasInternet) Connectivity.Ok else Connectivity.NoInternet - - // handle wifiOnly - return if (hasInternet && capabilities?.hasTransport(TRANSPORT_WIFI) == true) - Connectivity.Ok - else - Connectivity.NoWifi // don't return NoInternet here, more direct to request to join wifi -} - -private fun openAppIntent(context: Context): PendingIntent? { - val intent = Intent(context, NotificationReceiver::class.java) - val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - return PendingIntent.getBroadcast(context, "RNFileUpload-notification".hashCode(), intent, flags) -} diff --git a/android/src/main/java/com/vydia/RNUploader/UploaderModule.kt b/android/src/main/java/com/vydia/RNUploader/UploaderModule.kt index 0d833fc3..54b55ff8 100644 --- a/android/src/main/java/com/vydia/RNUploader/UploaderModule.kt +++ b/android/src/main/java/com/vydia/RNUploader/UploaderModule.kt @@ -43,7 +43,7 @@ class UploaderModule(context: ReactApplicationContext) : val id = startUpload(rawOptions) promise.resolve(id) } catch (exc: Throwable) { - if (exc !is Upload.MissingOptionException) { + if (exc !is MissingOptionException) { exc.printStackTrace() Log.e(TAG, exc.message, exc) } @@ -74,6 +74,20 @@ class UploaderModule(context: ReactApplicationContext) : return upload.id } + @ReactMethod + fun initialize(options: ReadableMap, promise: Promise) { + try { + UploadNotification.setOptions(options) + promise.resolve(true) + } catch (exc: Throwable) { + if (exc !is MissingOptionException) { + exc.printStackTrace() + Log.e(TAG, exc.message, exc) + } + promise.reject(exc) + } + } + /* * Cancels file upload diff --git a/example/RNBGUExample/App.tsx b/example/RNBGUExample/App.tsx index e7e44655..306327c1 100644 --- a/example/RNBGUExample/App.tsx +++ b/example/RNBGUExample/App.tsx @@ -28,6 +28,16 @@ const TEST_FILE_URL = 'https://gist.githubusercontent.com/khaykov/a6105154becce4c0530da38e723c2330/raw/41ab415ac41c93a198f7da5b47d604956157c5c3/gistfile1.txt'; const UPLOAD_URL = 'https://httpbin.org/put/404'; +const channelId = 'RNBGUExample'; + +Upload.initialize({ + notificationId: channelId, + notificationTitle: channelId, + notificationTitleNoWifi: 'No wifi', + notificationTitleNoInternet: 'No internet', + notificationChannel: channelId, +}); + const App = () => { const [uploadId, setUploadId] = useState(); const [progress, setProgress] = useState(); @@ -63,41 +73,36 @@ const App = () => { const onPressUpload = async () => { await notifee.requestPermission({alert: true, sound: true}); - const channelId = 'RNBGUExample'; await notifee.createChannel({ id: channelId, name: channelId, importance: AndroidImportance.LOW, }); - const uploadOpts: UploadOptions = { - android: { - notificationId: channelId, - notificationTitle: channelId, - notificationTitleNoWifi: 'No wifi', - notificationTitleNoInternet: 'No internet', - notificationChannel: channelId, - }, - type: 'raw', - url: UPLOAD_URL, - path: TEST_FILE, - method: 'POST', - headers: {}, - }; - - Upload.startUpload(uploadOpts) - .then(uploadId => { - console.log( - `Upload started with options: ${JSON.stringify(uploadOpts)}`, - ); - setUploadId(uploadId); - setProgress(0); - }) - .catch(function (err) { - setUploadId(undefined); - setProgress(undefined); - console.log('Upload error!', err); - }); + // Start multiple uploads to test performance + for (let i = 0; i < 100; i++) { + const uploadOpts: UploadOptions = { + type: 'raw', + url: UPLOAD_URL, + path: TEST_FILE, + method: 'POST', + headers: {}, + }; + + Upload.startUpload(uploadOpts) + .then(uploadId => { + console.log( + `Upload started with options: ${JSON.stringify(uploadOpts)}`, + ); + setUploadId(uploadId); + setProgress(0); + }) + .catch(function (err) { + setUploadId(undefined); + setProgress(undefined); + console.log('Upload error!', err); + }); + } }; return ( diff --git a/lib/index.d.ts b/lib/index.d.ts index 3beeecd0..3338c98a 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,7 +1,8 @@ -import { AddListener, UploadOptions } from './types'; +import { AddListener, AndroidInitializationOptions, UploadOptions } from './types'; export * from './types'; declare const _default: { - startUpload: ({ path, android, ios, ...options }: UploadOptions) => Promise; + initialize: (options: AndroidInitializationOptions) => void; + startUpload: ({ path, ios, ...options }: UploadOptions) => Promise; cancelUpload: (cancelUploadId: string) => Promise; addListener: AddListener; ios: { diff --git a/lib/types.d.ts b/lib/types.d.ts index 085ed05f..2d4fbbb3 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -22,10 +22,9 @@ export type UploadOptions = { [index: string]: string; }; wifiOnly?: boolean; - android: AndroidOnlyUploadOptions; ios?: IOSOnlyUploadOptions; } & RawUploadOptions; -type AndroidOnlyUploadOptions = { +export type AndroidInitializationOptions = { notificationId: string; notificationTitle: string; notificationTitleNoWifi: string; diff --git a/package.json b/package.json index a32a3e60..06d4cf85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-background-upload", - "version": "7.5.3", + "version": "7.6.0", "description": "Cross platform http post file uploader with android and iOS background support", "main": "src/index", "typings": "lib/index.d.ts", diff --git a/src/index.ts b/src/index.ts index 4710e914..8f789d37 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,12 @@ * Handles HTTP background file uploads from an iOS or Android device. */ import { NativeModules, DeviceEventEmitter, Platform } from 'react-native'; -import { AddListener, UploadId, UploadOptions } from './types'; +import { + AddListener, + AndroidInitializationOptions, + UploadId, + UploadOptions, +} from './types'; export * from './types'; @@ -19,6 +24,18 @@ if (NativeModules.VydiaRNFileUploader) { NativeModule.addListener(eventPrefix + 'completed'); } +let initializedPromise: Promise | undefined; + +/** + * Initializes the module with the given options. + * Must be called before starting any uploads. + * The notification channel doesn't have to be created beforehand. + * @param options + */ +const initialize = (options: AndroidInitializationOptions) => { + initializedPromise = Promise.resolve(NativeModule.initialize?.(options)); +}; + /** * Starts uploading a file to an HTTP endpoint. * Options object: @@ -35,12 +52,13 @@ if (NativeModules.VydiaRNFileUploader) { * Returns a promise with the string ID of the upload. Will reject if there is a connection problem, the file doesn't exist, or there is some other problem. * It is recommended to add listeners in the .then of this promise. */ -const startUpload = ({ +const startUpload = async ({ path, - android, ios, ...options }: UploadOptions): Promise => { + await initializedPromise; + if (!path.startsWith(fileURIPrefix)) { path = fileURIPrefix + path; } @@ -49,7 +67,7 @@ const startUpload = ({ path = path.replace(fileURIPrefix, ''); } - return NativeModule.startUpload({ ...options, ...android, ...ios, path }); + return NativeModule.startUpload({ ...options, ...ios, path }); }; /** @@ -109,6 +127,7 @@ const android = { }; export default { + initialize, startUpload, cancelUpload, addListener, diff --git a/src/types.ts b/src/types.ts index 2498be65..ca10c9bd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,11 +29,10 @@ export type UploadOptions = { }; // Whether the upload should wait for wifi before starting wifiOnly?: boolean; - android: AndroidOnlyUploadOptions; ios?: IOSOnlyUploadOptions; } & RawUploadOptions; -type AndroidOnlyUploadOptions = { +export type AndroidInitializationOptions = { notificationId: string; notificationTitle: string; notificationTitleNoWifi: string;