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.
- package/README.md +50 -12
- package/android/build.gradle +0 -1
- package/android/src/main/java/com/reactnativecompressor/CompressorModule.kt +3 -1
- package/android/src/main/java/com/reactnativecompressor/Image/ImageCompressor.kt +56 -23
- package/android/src/main/java/com/reactnativecompressor/Image/ImageCompressorOptions.kt +2 -0
- package/android/src/main/java/com/reactnativecompressor/Utils/MediaCache.kt +15 -0
- package/android/src/main/java/com/reactnativecompressor/Utils/Uploader.kt +152 -91
- package/android/src/main/java/com/reactnativecompressor/Utils/UploaderHelper.kt +121 -0
- package/android/src/main/java/com/reactnativecompressor/Utils/Utils.kt +14 -0
- package/ios/Image/ImageCompressor.swift +53 -41
- package/ios/Image/ImageCompressorOptions.swift +3 -0
- package/ios/Utils/MediaCache.swift +20 -0
- package/ios/Utils/Uploader.swift +123 -4
- package/ios/Utils/Utils.swift +20 -0
- package/lib/commonjs/Image/index.js.map +1 -1
- package/lib/commonjs/index.js +15 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/Uploader.js +16 -1
- package/lib/commonjs/utils/Uploader.js.map +1 -1
- package/lib/module/Image/index.js.map +1 -1
- package/lib/module/index.js +5 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/Uploader.js +13 -0
- package/lib/module/utils/Uploader.js.map +1 -1
- package/lib/typescript/Image/index.d.ts +4 -0
- package/lib/typescript/Image/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +5 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/utils/Uploader.d.ts +12 -11
- package/lib/typescript/utils/Uploader.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Image/index.tsx +4 -0
- package/src/index.tsx +6 -0
- 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
|
-
|
|
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:
|
|
425
|
+
- ###### backgroundUpload: (url: string, fileUrl: string, options: UploaderOptions, onProgress?: ((writtem: number, total: number) => void) | undefined) => Promise< any >
|
|
410
426
|
|
|
411
|
-
- ###### `
|
|
427
|
+
- ###### ` UploaderOptions`
|
|
412
428
|
|
|
413
429
|
```js
|
|
414
|
-
|
|
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?:
|
|
451
|
+
uploadType?: UploadType.BINARY_CONTENT;
|
|
452
|
+
mimeType?: string;
|
|
417
453
|
}
|
|
418
454
|
| {
|
|
419
|
-
uploadType:
|
|
420
|
-
fieldName?: string
|
|
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?:
|
|
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 >
|
package/android/build.gradle
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
15
|
+
import okhttp3.Headers
|
|
13
16
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|
17
|
+
import okhttp3.MultipartBody
|
|
14
18
|
import okhttp3.OkHttpClient
|
|
15
|
-
import okhttp3.Request
|
|
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.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
}
|