react-native-compressor 1.8.10 → 1.8.12

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.
Files changed (34) hide show
  1. package/README.md +50 -12
  2. package/android/build.gradle +0 -1
  3. package/android/src/main/java/com/reactnativecompressor/CompressorModule.kt +3 -1
  4. package/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt +56 -23
  5. package/android/src/main/java/com/reactnativecompressor/Image/ImageCompressorOptions.kt +2 -0
  6. package/android/src/main/java/com/reactnativecompressor/Utils/MediaCache.kt +15 -0
  7. package/android/src/main/java/com/reactnativecompressor/Utils/Uploader.kt +152 -91
  8. package/android/src/main/java/com/reactnativecompressor/Utils/UploaderHelper.kt +121 -0
  9. package/android/src/main/java/com/reactnativecompressor/Utils/Utils.kt +14 -0
  10. package/ios/Image/ImageCompressor.swift +53 -41
  11. package/ios/Image/ImageCompressorOptions.swift +3 -0
  12. package/ios/Utils/MediaCache.swift +20 -0
  13. package/ios/Utils/Uploader.swift +123 -4
  14. package/ios/Utils/Utils.swift +20 -0
  15. package/lib/commonjs/Image/index.js.map +1 -1
  16. package/lib/commonjs/index.js +15 -1
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/commonjs/utils/Uploader.js +16 -1
  19. package/lib/commonjs/utils/Uploader.js.map +1 -1
  20. package/lib/module/Image/index.js.map +1 -1
  21. package/lib/module/index.js +5 -3
  22. package/lib/module/index.js.map +1 -1
  23. package/lib/module/utils/Uploader.js +13 -0
  24. package/lib/module/utils/Uploader.js.map +1 -1
  25. package/lib/typescript/Image/index.d.ts +4 -0
  26. package/lib/typescript/Image/index.d.ts.map +1 -1
  27. package/lib/typescript/index.d.ts +5 -3
  28. package/lib/typescript/index.d.ts.map +1 -1
  29. package/lib/typescript/utils/Uploader.d.ts +12 -11
  30. package/lib/typescript/utils/Uploader.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. package/src/Image/index.tsx +4 -0
  33. package/src/index.tsx +6 -0
  34. package/src/utils/Uploader.tsx +14 -14
package/README.md CHANGED
@@ -287,6 +287,17 @@ const uploadResult = await backgroundUpload(
287
287
  console.log(written, total);
288
288
  }
289
289
  );
290
+
291
+ //OR
292
+
293
+ const uploadResult = await backgroundUpload(
294
+ url,
295
+ fileUrl,
296
+ { uploadType: UploadType.MULTIPART, httpMethod: 'POST', headers },
297
+ (written, total) => {
298
+ console.log(written, total);
299
+ }
300
+ );
290
301
  ```
291
302
 
292
303
  ### Download File
@@ -351,7 +362,12 @@ await clearCache(); // this will clear cache of thumbnails cache directory
351
362
 
352
363
  - ###### `output: OutputType` (default: jpg)
353
364
 
354
- Can be either `jpg` or `png`, defines the output image format.
365
+ The quality modifier for the `JPEG` file format, can be specified when output is `PNG` but will be ignored. if you wanna apply quality modifier then you can enable `disablePngTransparency:true`,
366
+ **Note:** if you png image have no transparent background then enable `disablePngTransparency:true` modifier is recommended
367
+
368
+ - ###### `disablePngTransparency: boolean` (default: false)
369
+
370
+ when user add `output:'png'` then by default compressed image will have transparent background, and quality will be ignored, if you wanna apply quality then you have to disablePngTransparency like `disablePngTransparency:true`, it will convert transparent background to white
355
371
 
356
372
  - ###### `returnableOutputType: ReturnableOutputType` (default: uri)
357
373
  Can be either `uri` or `base64`, defines the Returnable output image format.
@@ -406,28 +422,50 @@ await clearCache(); // this will clear cache of thumbnails cache directory
406
422
 
407
423
  ## Background Upload
408
424
 
409
- - ###### backgroundUpload: (url: string, fileUrl: string, options: FileSystemUploadOptions, onProgress?: ((writtem: number, total: number) => void) | undefined) => Promise< any >
425
+ - ###### backgroundUpload: (url: string, fileUrl: string, options: UploaderOptions, onProgress?: ((writtem: number, total: number) => void) | undefined) => Promise< any >
410
426
 
411
- - ###### ` FileSystemUploadOptions`
427
+ - ###### ` UploaderOptions`
412
428
 
413
429
  ```js
414
- type FileSystemUploadOptions = (
430
+ export enum UploadType {
431
+ BINARY_CONTENT = 0,
432
+ MULTIPART = 1,
433
+ }
434
+
435
+ export enum UploaderHttpMethod {
436
+ POST = 'POST',
437
+ PUT = 'PUT',
438
+ PATCH = 'PATCH',
439
+ }
440
+
441
+ export declare type HTTPResponse = {
442
+ status: number;
443
+ headers: Record<string, string>;
444
+ body: string;
445
+ };
446
+
447
+ export declare type HttpMethod = 'POST' | 'PUT' | 'PATCH';
448
+
449
+ export declare type UploaderOptions = (
415
450
  | {
416
- uploadType?: FileSystemUploadType.BINARY_CONTENT,
451
+ uploadType?: UploadType.BINARY_CONTENT;
452
+ mimeType?: string;
417
453
  }
418
454
  | {
419
- uploadType: FileSystemUploadType.MULTIPART, // will add soon
420
- fieldName?: string, // will add soon
421
- mimeType?: string,
422
- parameters?: Record<string, string>,
455
+ uploadType: UploadType.MULTIPART;
456
+ fieldName?: string;
457
+ mimeType?: string;
458
+ parameters?: Record<string, string>;
423
459
  }
424
460
  ) & {
425
- headers?: Record<string, string>,
426
- httpMethod?: FileSystemAcceptedUploadHttpMethod,
427
- sessionType?: FileSystemSessionType,
461
+ headers?: Record<string, string>;
462
+ httpMethod?: UploaderHttpMethod;
428
463
  };
429
464
  ```
430
465
 
466
+ **Note:** some of the uploader code is borrowed from [Expo](https://github.com/expo/expo)
467
+ I tested file uploader on this backend [Nodejs-File-Uploader](https://github.com/numandev1/nodejs-file-uploader)
468
+
431
469
  ### Download
432
470
 
433
471
  - ##### download: ( fileUrl: string, downloadProgress?: (progress: number) => void, progressDivider?: number ) => Promise< string >
@@ -115,7 +115,6 @@ dependencies {
115
115
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
116
116
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
117
117
  implementation "com.googlecode.mp4parser:isoparser:1.0.6"
118
- implementation 'io.github.lizhangqu:coreprogress:1.0.2'
119
118
  implementation 'com.github.banketree:AndroidLame-kotlin:v0.0.1'
120
119
  implementation 'javazoom:jlayer:1.0.1'
121
120
  }
@@ -15,12 +15,14 @@ import com.reactnativecompressor.Utils.Uploader
15
15
  import com.reactnativecompressor.Utils.Utils
16
16
  import com.reactnativecompressor.Utils.Utils.generateCacheFilePath
17
17
  import com.reactnativecompressor.Utils.Utils.getRealPath
18
+ import com.reactnativecompressor.Utils.convertReadableMapToUploaderOptions
18
19
  import com.reactnativecompressor.Video.VideoMain
19
20
 
20
21
  class CompressorModule(private val reactContext: ReactApplicationContext) : CompressorSpec(reactContext) {
21
22
  private val imageMain: ImageMain = ImageMain(reactContext)
22
23
  private val videoMain: VideoMain = VideoMain(reactContext)
23
24
  private val audioMain: AudioMain = AudioMain(reactContext)
25
+ private val uploader: Uploader = Uploader(reactContext)
24
26
  private val videoThumbnail: CreateVideoThumbnailClass = CreateVideoThumbnailClass(reactContext)
25
27
 
26
28
  override fun initialize() {
@@ -120,7 +122,7 @@ class CompressorModule(private val reactContext: ReactApplicationContext) : Comp
120
122
  fileUrl: String,
121
123
  options: ReadableMap,
122
124
  promise: Promise) {
123
- Uploader.upload(fileUrl, options, reactContext, promise)
125
+ uploader.upload(fileUrl, options, reactContext, promise)
124
126
  }
125
127
 
126
128
  @ReactMethod
@@ -10,7 +10,9 @@ import android.media.ExifInterface
10
10
  import android.net.Uri
11
11
  import android.util.Base64
12
12
  import com.facebook.react.bridge.ReactApplicationContext
13
+ import com.reactnativecompressor.Utils.MediaCache
13
14
  import com.reactnativecompressor.Utils.Utils.generateCacheFilePath
15
+ import com.reactnativecompressor.Utils.Utils.slashifyFilePath
14
16
  import java.io.ByteArrayOutputStream
15
17
  import java.io.File
16
18
  import java.io.FileOutputStream
@@ -71,20 +73,20 @@ object ImageCompressor {
71
73
  }
72
74
 
73
75
  fun resize(image: Bitmap, maxWidth: Int, maxHeight: Int): Bitmap {
74
- val size = findActualSize(image, maxWidth, maxHeight)
75
- val scaledImage = Bitmap.createBitmap(size.width, size.height, image.config)
76
- val scaleMatrix = Matrix()
77
- val canvas = Canvas(scaledImage)
78
- val paint = Paint(Paint.FILTER_BITMAP_FLAG)
79
- scaleMatrix.setScale(size.scale, size.scale, 0f, 0f)
80
- paint.isDither = true
81
- paint.isAntiAlias = true
82
- paint.isFilterBitmap = true
83
- canvas.drawBitmap(image, scaleMatrix, paint)
84
- return scaledImage
76
+ val size = findActualSize(image, maxWidth, maxHeight)
77
+ val scaledImage = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888)
78
+ val scaleMatrix = Matrix()
79
+ val canvas = Canvas(scaledImage)
80
+ val paint = Paint(Paint.FILTER_BITMAP_FLAG)
81
+ scaleMatrix.setScale(size.scale, size.scale, 0f, 0f)
82
+ paint.isDither = true
83
+ paint.isAntiAlias = true
84
+ paint.isFilterBitmap = true
85
+ canvas.drawBitmap(image, scaleMatrix, paint)
86
+ return scaledImage
85
87
  }
86
88
 
87
- fun compress(image: Bitmap?, output: ImageCompressorOptions.OutputType, quality: Float): ByteArrayOutputStream {
89
+ fun compress(image: Bitmap?, output: ImageCompressorOptions.OutputType, quality: Float,disablePngTransparency:Boolean): ByteArrayOutputStream {
88
90
  var stream = ByteArrayOutputStream()
89
91
  if (output === ImageCompressorOptions.OutputType.jpg)
90
92
  {
@@ -92,12 +94,15 @@ object ImageCompressor {
92
94
  }
93
95
  else
94
96
  {
95
- val pngStream = ByteArrayOutputStream()
96
- image!!.compress(CompressFormat.JPEG, Math.round(100 * quality), stream)
97
- val byteArray: ByteArray = stream.toByteArray()
98
- stream=ByteArrayOutputStream()
99
- val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
100
- bitmap.compress(CompressFormat.PNG, 100, stream)
97
+ var bitmap = image
98
+ if(disablePngTransparency)
99
+ {
100
+ image!!.compress(CompressFormat.JPEG, Math.round(100 * quality), stream)
101
+ val byteArray: ByteArray = stream.toByteArray()
102
+ stream=ByteArrayOutputStream()
103
+ bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
104
+ }
105
+ bitmap!!.compress(CompressFormat.PNG, 100, stream)
101
106
  }
102
107
  return stream
103
108
  }
@@ -105,14 +110,36 @@ object ImageCompressor {
105
110
  fun manualCompressImage(imagePath: String?, options: ImageCompressorOptions, reactContext: ReactApplicationContext?): String? {
106
111
  val image = if (options.input === ImageCompressorOptions.InputType.base64) decodeImage(imagePath) else loadImage(imagePath)
107
112
  val resizedImage = resize(image, options.maxWidth, options.maxHeight)
108
- val imageDataByteArrayOutputStream = compress(resizedImage, options.output, options.quality)
113
+ val imageDataByteArrayOutputStream = compress(resizedImage, options.output, options.quality,options.disablePngTransparency)
109
114
  val isBase64 = options.returnableOutputType === ImageCompressorOptions.ReturnableOutputType.base64
110
115
  return encodeImage(imageDataByteArrayOutputStream, isBase64, options.output.toString(), reactContext)
111
116
  }
112
117
 
118
+ fun isCompressedSizeLessThanActualFile(sourceFileUrl: String,compressedFileUrl: String?): Boolean {
119
+ try {
120
+ val sourceUri = Uri.parse(sourceFileUrl)
121
+ val sourcePath = sourceUri.path
122
+ val sourcefile = File(sourcePath)
123
+ val sizeInBytesForSourceFile = sourcefile.length().toFloat()
124
+
125
+ val compressedUri = Uri.parse(compressedFileUrl)
126
+ val compressedPath = compressedUri.path
127
+ val compressedfile = File(compressedPath)
128
+ val sizeInBytesForcompressedFile = compressedfile.length().toFloat()
129
+
130
+ if(sizeInBytesForcompressedFile<=sizeInBytesForSourceFile)
131
+ {
132
+ return true
133
+ }
134
+ return false
135
+ } catch (exception: OutOfMemoryError) {
136
+ exception.printStackTrace()
137
+ return true
138
+ }
139
+ }
140
+
113
141
  fun autoCompressImage(imagePath: String?, compressorOptions: ImageCompressorOptions, reactContext: ReactApplicationContext?): String? {
114
142
  var imagePath = imagePath
115
- val outputExtension = compressorOptions.output.toString()
116
143
  val autoCompressMaxHeight = compressorOptions.maxHeight.toFloat()
117
144
  val autoCompressMaxWidth = compressorOptions.maxWidth.toFloat()
118
145
  val isBase64 = compressorOptions.returnableOutputType === ImageCompressorOptions.ReturnableOutputType.base64
@@ -152,7 +179,7 @@ object ImageCompressor {
152
179
  exception.printStackTrace()
153
180
  }
154
181
  try {
155
- scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.RGB_565)
182
+ scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.ARGB_8888)
156
183
  } catch (exception: OutOfMemoryError) {
157
184
  exception.printStackTrace()
158
185
  }
@@ -169,8 +196,14 @@ object ImageCompressor {
169
196
  bmp.recycle()
170
197
  }
171
198
  scaledBitmap = correctImageOrientation(scaledBitmap, imagePath)
172
- val imageDataByteArrayOutputStream = compress(scaledBitmap, compressorOptions.output, compressorOptions.quality)
173
- return encodeImage(imageDataByteArrayOutputStream, isBase64, compressorOptions.output.toString(), reactContext)
199
+ val imageDataByteArrayOutputStream = compress(scaledBitmap, compressorOptions.output, compressorOptions.quality,compressorOptions.disablePngTransparency)
200
+ val compressedImagePath=encodeImage(imageDataByteArrayOutputStream, isBase64, compressorOptions.output.toString(), reactContext)
201
+ if(isCompressedSizeLessThanActualFile(imagePath!!,compressedImagePath))
202
+ {
203
+ return compressedImagePath
204
+ }
205
+ MediaCache.deleteFile(compressedImagePath!!)
206
+ return slashifyFilePath(imagePath)
174
207
  }
175
208
 
176
209
  fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
@@ -32,6 +32,7 @@ class ImageCompressorOptions {
32
32
  var output = OutputType.jpg
33
33
  var uuid: String? = ""
34
34
  var returnableOutputType = ReturnableOutputType.uri
35
+ var disablePngTransparency:Boolean = false
35
36
 
36
37
  companion object {
37
38
  fun fromMap(map: ReadableMap): ImageCompressorOptions {
@@ -49,6 +50,7 @@ class ImageCompressorOptions {
49
50
  "output" -> options.output = OutputType.valueOf(map.getString(key)!!)
50
51
  "returnableOutputType" -> options.returnableOutputType = ReturnableOutputType.valueOf(map.getString(key)!!)
51
52
  "uuid" -> options.uuid = map.getString(key)
53
+ "disablePngTransparency" -> options.disablePngTransparency = map.getBoolean(key)
52
54
  }
53
55
  }
54
56
  return options
@@ -58,4 +58,19 @@ object MediaCache {
58
58
  }
59
59
  completedImagePaths.clear()
60
60
  }
61
+
62
+ fun deleteFile(filePath: String) {
63
+ val _filePath = filePath.replace("file://", "")
64
+ val file = File(_filePath)
65
+
66
+ if (file.exists()) {
67
+ if (file.delete()) {
68
+ println("File deleted successfully.")
69
+ } else {
70
+ println("File couldn't be deleted.")
71
+ }
72
+ } else {
73
+ println("File not found.")
74
+ }
75
+ }
61
76
  }
@@ -1,117 +1,178 @@
1
1
  package com.reactnativecompressor.Utils
2
2
 
3
+ import android.annotation.SuppressLint
4
+ import android.content.ContentResolver
5
+ import android.net.Uri
3
6
  import android.util.Log
7
+ import android.webkit.MimeTypeMap
4
8
  import com.facebook.react.bridge.Arguments
5
9
  import com.facebook.react.bridge.Promise
6
10
  import com.facebook.react.bridge.ReactApplicationContext
7
11
  import com.facebook.react.bridge.ReadableMap
8
- import io.github.lizhangqu.coreprogress.ProgressHelper
9
- import io.github.lizhangqu.coreprogress.ProgressUIListener
12
+ import com.reactnativecompressor.Utils.Utils.slashifyFilePath
10
13
  import okhttp3.Call
11
14
  import okhttp3.Callback
12
- import okhttp3.MediaType
15
+ import okhttp3.Headers
13
16
  import okhttp3.MediaType.Companion.toMediaTypeOrNull
17
+ import okhttp3.MultipartBody
14
18
  import okhttp3.OkHttpClient
15
- import okhttp3.Request.Builder
19
+ import okhttp3.Request
16
20
  import okhttp3.RequestBody
21
+ import okhttp3.RequestBody.Companion.asRequestBody
17
22
  import okhttp3.Response
18
23
  import java.io.File
19
24
  import java.io.IOException
20
- import java.util.Locale
21
-
22
- object Uploader {
23
- private const val TAG = "asyncTaskUploader"
24
-
25
- fun upload(fileUrl: String, _options: ReadableMap?, reactContext: ReactApplicationContext, promise: Promise) {
26
- val options = _options?.let { UploaderHelper.fromMap(it) }
27
- val uploadableFile = File(fileUrl)
28
- val url = options?.url
29
- var contentType: String? = "video"
30
- val okHttpClient = OkHttpClient()
31
- val builder = Builder()
32
- if (url != null) {
33
- builder.url(url)
34
- }
35
- val headerIterator = options?.headers?.keySetIterator()
36
- while (headerIterator?.hasNextKey() == true) {
37
- val key = headerIterator.nextKey()
38
- val value = options.headers?.getString(key)
39
- Log.d(TAG, "$key value: $value")
40
- builder.addHeader(key, value.toString())
41
- if (key.lowercase(Locale.getDefault()) == "content-type:") {
42
- contentType = value
25
+ import java.net.URLConnection
26
+ import java.util.concurrent.TimeUnit
27
+
28
+ class Uploader(private val reactContext: ReactApplicationContext) {
29
+ val TAG = "asyncTaskUploader"
30
+ var client: OkHttpClient? = null
31
+ val MIN_EVENT_DT_MS: Long = 100
32
+
33
+ fun upload(fileUriString: String, _options: ReadableMap, reactContext: ReactApplicationContext, promise: Promise) {
34
+ val options:UploaderOptions=convertReadableMapToUploaderOptions(_options)
35
+ val url = options.url
36
+ val uuid = options.uuid
37
+ val progressListener: CountingRequestListener = object : CountingRequestListener {
38
+ private var mLastUpdate: Long = -1
39
+ override fun onProgress(bytesWritten: Long, contentLength: Long) {
40
+ val currentTime = System.currentTimeMillis()
41
+
42
+ // Throttle events. Sending too many events will block the JS event loop.
43
+ // Make sure to send the last event when we're at 100%.
44
+ if (currentTime > mLastUpdate + MIN_EVENT_DT_MS || bytesWritten == contentLength) {
45
+ mLastUpdate = currentTime
46
+ EventEmitterHandler.sendUploadProgressEvent(bytesWritten,contentLength,uuid)
43
47
  }
44
48
  }
45
- val mediaType: MediaType? = contentType?.toMediaTypeOrNull();
46
- val body = RequestBody.create(mediaType, uploadableFile)
47
- val requestBody = ProgressHelper.withProgress(body, object : ProgressUIListener() {
48
- //if you don't need this method, don't override this methd. It isn't an abstract method, just an empty method.
49
- override fun onUIProgressStart(totalBytes: Long) {
50
- super.onUIProgressStart(totalBytes)
51
- Log.d(TAG, "onUIProgressStart:$totalBytes")
52
- }
53
-
54
- override fun onUIProgressChanged(numBytes: Long, totalBytes: Long, percent: Float, speed: Float) {
55
- EventEmitterHandler.sendUploadProgressEvent(numBytes,totalBytes,options?.uuid)
56
- Log.d(TAG, "=============start===============")
57
- Log.d(TAG, "numBytes:$numBytes")
58
- Log.d(TAG, "totalBytes:$totalBytes")
59
- Log.d(TAG, "percent:$percent")
60
- Log.d(TAG, "speed:$speed")
61
- Log.d(TAG, "============= end ===============")
62
- }
63
-
64
- //if you don't need this method, don't override this methd. It isn't an abstract method, just an empty method.
65
- override fun onUIProgressFinish() {
66
- super.onUIProgressFinish()
67
- Log.d(TAG, "onUIProgressFinish:")
68
- }
69
- })
70
- builder.put(requestBody)
71
- val call = okHttpClient.newCall(builder.build())
72
- call.enqueue(object : Callback {
73
- override fun onFailure(call: Call, e: IOException) {
74
- Log.d(TAG, "=============onFailure===============")
75
- promise.reject("")
76
- e.printStackTrace()
77
- }
78
-
79
- @Throws(IOException::class)
80
- override fun onResponse(call: Call, response: Response) {
81
- Log.d(TAG, "=============onResponse===============")
82
- Log.d(TAG, "request headers:" + response.request.headers)
83
- Log.d(TAG, "response code:" + response.code)
84
- Log.d(TAG, "response headers:" + response.headers)
85
- Log.d(TAG, "response body:" + response.body!!.string())
86
- val param = Arguments.createMap()
87
- param.putInt("status", response.code)
88
- promise.resolve(param)
89
- }
49
+ }
50
+ val request = createUploadRequest(
51
+ url, fileUriString, options
52
+ ) { requestBody -> CountingRequestBody(requestBody, progressListener) }
53
+
54
+ okHttpClient?.let {
55
+ it.newCall(request).enqueue(object : Callback {
56
+ override fun onFailure(call: Call, e: IOException) {
57
+ Log.e(TAG, e.message.toString())
58
+ promise.reject(TAG, e.message, e)
59
+ }
60
+
61
+ override fun onResponse(call: Call, response: Response) {
62
+ val param = Arguments.createMap()
63
+ param.putInt("status", response.code)
64
+ param.putString("body", response.body?.string())
65
+ param.putMap("headers", translateHeaders(response.headers))
66
+ response.close()
67
+ promise.resolve(param)
68
+ }
90
69
  })
70
+ } ?: run {
71
+ promise.reject(UploaderOkHttpNullException())
72
+ }
73
+
91
74
  }
92
- }
93
75
 
76
+ @get:Synchronized
77
+ private val okHttpClient: OkHttpClient?
78
+ get() {
79
+ if (client == null) {
80
+ val builder = OkHttpClient.Builder()
81
+ .connectTimeout(60, TimeUnit.SECONDS)
82
+ .readTimeout(60, TimeUnit.SECONDS)
83
+ .writeTimeout(60, TimeUnit.SECONDS)
84
+ client = builder.build()
85
+ }
86
+ return client
87
+ }
88
+
89
+ @Throws(IOException::class)
90
+ private fun createUploadRequest(url: String, fileUriString: String, options: UploaderOptions, decorator: RequestBodyDecorator): Request {
91
+ val fileUri = Uri.parse(slashifyFilePath(fileUriString))
92
+ fileUri.checkIfFileExists()
93
+
94
+ val requestBuilder = Request.Builder().url(url)
95
+ options.headers?.let {
96
+ it.forEach { (key, value) -> requestBuilder.addHeader(key, value) }
97
+ }
98
+
99
+ val body = createRequestBody(options, decorator, fileUri.toFile())
100
+ return options.httpMethod.let { requestBuilder.method(it.value, body).build() }
101
+ }
102
+
103
+ @SuppressLint("NewApi")
104
+ private fun createRequestBody(options: UploaderOptions, decorator: RequestBodyDecorator, file: File): RequestBody {
105
+ return when (options.uploadType) {
106
+ UploadType.BINARY_CONTENT -> {
107
+ val mimeType: String? = if (options.mimeType?.isNotEmpty() == true) {
108
+ options.mimeType
109
+ } else {
110
+ getContentType(reactContext, file) ?: "application/octet-stream"
111
+ }
112
+ val contentType = mimeType?.toMediaTypeOrNull()
113
+ decorator.decorate(file.asRequestBody(contentType))
114
+ }
94
115
 
95
- class UploaderHelper {
96
- var uuid: String? = null
97
- var method: String? = null
98
- var headers: ReadableMap? = null
99
- var url: String? = null
100
-
101
- companion object {
102
- fun fromMap(map: ReadableMap): UploaderHelper {
103
- val options = UploaderHelper()
104
- val iterator = map.keySetIterator()
105
- while (iterator.hasNextKey()) {
106
- val key = iterator.nextKey()
107
- when (key) {
108
- "uuid" -> options.uuid = map.getString(key)
109
- "method" -> options.method = map.getString(key)
110
- "headers" -> options.headers = map.getMap(key)
111
- "url" -> options.url = map.getString(key)
116
+ UploadType.MULTIPART -> {
117
+ val bodyBuilder = MultipartBody.Builder().setType(MultipartBody.FORM)
118
+ options.parameters?.let {
119
+ (it as Map<String, Any>)
120
+ .forEach { (key, value) -> bodyBuilder.addFormDataPart(key, value.toString()) }
112
121
  }
122
+ val mimeType: String = options.mimeType ?: URLConnection.guessContentTypeFromName(file.name)
123
+
124
+ val fieldName = options.fieldName ?: file.name
125
+ bodyBuilder.addFormDataPart(fieldName, file.name, decorator.decorate(file.asRequestBody(mimeType.toMediaTypeOrNull())))
126
+ bodyBuilder.build()
127
+ }
128
+ }
129
+ }
130
+
131
+ fun getContentType(context: ReactApplicationContext, file: File): String? {
132
+ val contentResolver: ContentResolver = context.contentResolver
133
+ val fileUri = Uri.fromFile(file)
134
+
135
+ // Try to get the MIME type from the ContentResolver
136
+ val mimeType = contentResolver.getType(fileUri)
137
+
138
+ // If the ContentResolver couldn't determine the MIME type, try to infer it from the file extension
139
+ if (mimeType == null) {
140
+ val fileExtension = MimeTypeMap.getFileExtensionFromUrl(fileUri.toString())
141
+ if (fileExtension != null) {
142
+ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase())
143
+ }
144
+ }
145
+
146
+ return mimeType
147
+ }
148
+
149
+ @Throws(IOException::class)
150
+ private fun Uri.checkIfFileExists() {
151
+ val file = this.toFile()
152
+ if (!file.exists()) {
153
+ throw IOException("Directory for '${file.path}' doesn't exist.")
154
+ }
155
+ }
156
+
157
+ // extension functions of Uri class
158
+ private fun Uri.toFile() = if (this.path != null) {
159
+ File(this.path!!)
160
+ } else {
161
+ throw IOException("Invalid Uri: $this")
162
+ }
163
+
164
+ private fun translateHeaders(headers: Headers): ReadableMap {
165
+ val responseHeaders = Arguments.createMap()
166
+ for (i in 0 until headers.size) {
167
+ val headerName = headers.name(i)
168
+ // multiple values for the same header
169
+ if (responseHeaders.hasKey(headerName)) {
170
+ val existingValue = responseHeaders.getString(headerName)
171
+ responseHeaders.putString(headerName, "$existingValue, ${headers.value(i)}")
172
+ } else {
173
+ responseHeaders.putString(headerName, headers.value(i))
113
174
  }
114
- return options
115
175
  }
176
+ return responseHeaders
116
177
  }
117
178
  }