react-native-my-uploader-android 1.0.21 → 1.0.23
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 +20 -20
- package/MyUploader.podspec +21 -21
- package/README.md +205 -205
- package/android/build.gradle +39 -39
- package/android/gradle.properties +5 -5
- package/android/src/main/AndroidManifest.xml +2 -2
- package/android/src/main/java/com/myuploader/MyUploaderModule.kt +172 -172
- package/android/src/main/java/com/myuploader/MyUploaderPackage.kt +15 -15
- package/android/src/main/java/com/myuploaderandroid/MyUploaderModule.kt +173 -0
- package/android/src/main/java/com/myuploaderandroid/MyUploaderPackage.kt +16 -0
- package/ios/MyUploader.h +8 -8
- package/ios/MyUploader.mm +10 -10
- package/ios/myUploader.swift +97 -97
- package/lib/commonjs/NativeMyUploader.js +0 -2
- package/lib/commonjs/NativeMyUploader.js.map +1 -1
- package/lib/commonjs/index.d.js.map +1 -1
- package/lib/commonjs/index.js +80 -16
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/types.js +4 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/NativeMyUploader.js +0 -3
- package/lib/module/NativeMyUploader.js.map +1 -1
- package/lib/module/index.d.js.map +1 -1
- package/lib/module/index.js +81 -19
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +1 -1
- package/lib/module/types.js.map +1 -1
- package/package.json +161 -161
- package/src/NativeMyUploader.ts +24 -26
- package/src/index.d.ts +7 -59
- package/src/index.tsx +127 -58
- package/src/types.ts +25 -47
|
@@ -1,173 +1,173 @@
|
|
|
1
|
-
package com.myuploader
|
|
2
|
-
|
|
3
|
-
import android.app.Activity
|
|
4
|
-
import android.content.Intent
|
|
5
|
-
import android.net.Uri
|
|
6
|
-
import android.provider.OpenableColumns
|
|
7
|
-
import android.util.Base64
|
|
8
|
-
import com.facebook.react.bridge.*
|
|
9
|
-
import java.io.ByteArrayOutputStream
|
|
10
|
-
import java.net.URLConnection
|
|
11
|
-
|
|
12
|
-
class MyUploaderModule(private val reactContext: ReactApplicationContext) :
|
|
13
|
-
ReactContextBaseJavaModule(reactContext), ActivityEventListener {
|
|
14
|
-
|
|
15
|
-
private var pickerPromise: Promise? = null
|
|
16
|
-
private var maxSize: Double = 0.0
|
|
17
|
-
private var maxFiles: Int = 0
|
|
18
|
-
private var excludedUris: List<String> = emptyList()
|
|
19
|
-
|
|
20
|
-
init {
|
|
21
|
-
reactContext.addActivityEventListener(this)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
override fun getName(): String = "DocumentPicker"
|
|
25
|
-
|
|
26
|
-
companion object {
|
|
27
|
-
private const val REQUEST_CODE = 9900
|
|
28
|
-
private const val E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST"
|
|
29
|
-
private const val E_PICKER_CANCELLED = "E_PICKER_CANCELLED"
|
|
30
|
-
private const val E_FAILED_TO_OPEN_DOCUMENT = "E_FAILED_TO_OPEN_DOCUMENT"
|
|
31
|
-
private const val E_FILE_TOO_LARGE = "E_FILE_TOO_LARGE"
|
|
32
|
-
private const val E_MAX_FILES_EXCEEDED = "E_MAX_FILES_EXCEEDED"
|
|
33
|
-
private const val E_INVALID_OPTIONS = "E_INVALID_OPTIONS"
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
@ReactMethod
|
|
37
|
-
fun openDocument(options: ReadableMap, promise: Promise) {
|
|
38
|
-
if (pickerPromise != null) {
|
|
39
|
-
promise.reject(E_FAILED_TO_OPEN_DOCUMENT, "Başka bir dosya seçme işlemi zaten devam ediyor.")
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
this.pickerPromise = promise
|
|
44
|
-
|
|
45
|
-
val multipleFiles = options.takeIf { it.hasKey("multipleFiles") }?.getBoolean("multipleFiles") ?: false
|
|
46
|
-
val jsMaxFiles = options.takeIf { it.hasKey("maxFiles") }?.getInt("maxFiles") ?: 0
|
|
47
|
-
|
|
48
|
-
// KURAL KONTROLÜ (NATIVE): JS tarafındaki kontrolü burada da yaparak güvenliği artır.
|
|
49
|
-
if (!multipleFiles && jsMaxFiles > 1) {
|
|
50
|
-
promise.reject(E_INVALID_OPTIONS, "`maxFiles` değeri, `multipleFiles` false iken 1'den büyük olamaz.")
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// KURAL UYGULAMA (NATIVE): `maxFiles` için nihai değeri hesapla.
|
|
55
|
-
val effectiveMaxFiles = when {
|
|
56
|
-
multipleFiles && jsMaxFiles == 0 -> 3 // Varsayılan: 3
|
|
57
|
-
!multipleFiles -> 1 // Tekli seçimde her zaman 1
|
|
58
|
-
else -> jsMaxFiles // Belirtilen değeri kullan
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Değerleri sınıf değişkenlerine ata
|
|
62
|
-
this.maxFiles = effectiveMaxFiles
|
|
63
|
-
this.maxSize = options.takeIf { it.hasKey("maxSize") }?.getDouble("maxSize") ?: 0.0
|
|
64
|
-
this.excludedUris = options.takeIf { it.hasKey("excludedUris") }
|
|
65
|
-
?.getArray("excludedUris")?.toArrayList()?.mapNotNull { it.toString() } ?: emptyList()
|
|
66
|
-
|
|
67
|
-
val currentActivity = reactContext.currentActivity
|
|
68
|
-
if (currentActivity == null) {
|
|
69
|
-
pickerPromise?.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity mevcut değil.")
|
|
70
|
-
pickerPromise = null
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
val fileTypes = options.takeIf { it.hasKey("fileTypes") }
|
|
75
|
-
?.getArray("fileTypes")?.toArrayList()?.mapNotNull { it.toString() } ?: listOf("*/*")
|
|
76
|
-
val mimeTypes = if (fileTypes.isEmpty()) arrayOf("*/*") else fileTypes.toTypedArray()
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
// ANAHTAR: ACTION_GET_CONTENT, güvenilir çoklu seçimi etkinleştirir.
|
|
80
|
-
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
|
81
|
-
type = "*/*"
|
|
82
|
-
addCategory(Intent.CATEGORY_OPENABLE)
|
|
83
|
-
putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
|
|
84
|
-
|
|
85
|
-
if (multipleFiles) {
|
|
86
|
-
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
currentActivity.startActivityForResult(Intent.createChooser(intent, "Dosya Seçin"), REQUEST_CODE)
|
|
90
|
-
} catch (e: Exception) {
|
|
91
|
-
pickerPromise?.reject(E_FAILED_TO_OPEN_DOCUMENT, e)
|
|
92
|
-
pickerPromise = null
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
|
|
97
|
-
if (requestCode != REQUEST_CODE || pickerPromise == null) { return }
|
|
98
|
-
|
|
99
|
-
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
100
|
-
val allSelectedUris = mutableListOf<Uri>()
|
|
101
|
-
data.clipData?.let { clip ->
|
|
102
|
-
for (i in 0 until clip.itemCount) {
|
|
103
|
-
allSelectedUris.add(clip.getItemAt(i).uri)
|
|
104
|
-
}
|
|
105
|
-
} ?: data.data?.let { uri ->
|
|
106
|
-
allSelectedUris.add(uri)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
val newSelectedUris = allSelectedUris.filter { uri -> !excludedUris.contains(uri.toString()) }
|
|
110
|
-
|
|
111
|
-
if (this.maxFiles > 0 && newSelectedUris.size > this.maxFiles) {
|
|
112
|
-
pickerPromise?.reject(E_MAX_FILES_EXCEEDED, "En fazla ${this.maxFiles} adet yeni dosya seçebilirsiniz.")
|
|
113
|
-
pickerPromise = null
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
val fileInfoArray = processSelectedFiles(newSelectedUris)
|
|
119
|
-
pickerPromise?.resolve(fileInfoArray)
|
|
120
|
-
} catch (e: Exception) {
|
|
121
|
-
pickerPromise?.reject(E_FILE_TOO_LARGE, e.message)
|
|
122
|
-
}
|
|
123
|
-
} else {
|
|
124
|
-
pickerPromise?.reject(E_PICKER_CANCELLED, "Dosya seçimi iptal edildi.")
|
|
125
|
-
}
|
|
126
|
-
pickerPromise = null
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
override fun onNewIntent(intent: Intent) {}
|
|
130
|
-
|
|
131
|
-
private fun processSelectedFiles(uris: List<Uri>): WritableArray {
|
|
132
|
-
val fileObjects = WritableNativeArray()
|
|
133
|
-
val maxSizeBytes = (maxSize * 1024 * 1024).toLong()
|
|
134
|
-
|
|
135
|
-
for (uri in uris) {
|
|
136
|
-
reactContext.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
|
137
|
-
if (cursor.moveToFirst()) {
|
|
138
|
-
val fileNameIndex = cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
|
|
139
|
-
val fileSizeIndex = cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)
|
|
140
|
-
val fileName = cursor.getString(fileNameIndex)
|
|
141
|
-
val fileSize = cursor.getLong(fileSizeIndex)
|
|
142
|
-
|
|
143
|
-
if (maxSizeBytes > 0 && fileSize > maxSizeBytes) {
|
|
144
|
-
throw Exception("Seçilen dosya ($fileName) belirtilen ${maxSize}MB boyutundan büyük.")
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
val mimeType = getMimeType(fileName)
|
|
148
|
-
val base64 = reactContext.contentResolver.openInputStream(uri)?.use { inputStream ->
|
|
149
|
-
val byteBuffer = ByteArrayOutputStream()
|
|
150
|
-
val buffer = ByteArray(1024)
|
|
151
|
-
var len: Int
|
|
152
|
-
while (inputStream.read(buffer).also { len = it } != -1) {
|
|
153
|
-
byteBuffer.write(buffer, 0, len)
|
|
154
|
-
}
|
|
155
|
-
Base64.encodeToString(byteBuffer.toByteArray(), Base64.NO_WRAP)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
val fileMap = WritableNativeMap().apply {
|
|
159
|
-
putString("fileName", fileName)
|
|
160
|
-
putDouble("fileSize", fileSize.toDouble())
|
|
161
|
-
putString("fileType", mimeType)
|
|
162
|
-
putString("fileUri", uri.toString())
|
|
163
|
-
putString("base64", base64)
|
|
164
|
-
}
|
|
165
|
-
fileObjects.pushMap(fileMap)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return fileObjects
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private fun getMimeType(fileName: String): String? = URLConnection.guessContentTypeFromName(fileName)
|
|
1
|
+
package com.myuploader
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import android.provider.OpenableColumns
|
|
7
|
+
import android.util.Base64
|
|
8
|
+
import com.facebook.react.bridge.*
|
|
9
|
+
import java.io.ByteArrayOutputStream
|
|
10
|
+
import java.net.URLConnection
|
|
11
|
+
|
|
12
|
+
class MyUploaderModule(private val reactContext: ReactApplicationContext) :
|
|
13
|
+
ReactContextBaseJavaModule(reactContext), ActivityEventListener {
|
|
14
|
+
|
|
15
|
+
private var pickerPromise: Promise? = null
|
|
16
|
+
private var maxSize: Double = 0.0
|
|
17
|
+
private var maxFiles: Int = 0
|
|
18
|
+
private var excludedUris: List<String> = emptyList()
|
|
19
|
+
|
|
20
|
+
init {
|
|
21
|
+
reactContext.addActivityEventListener(this)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun getName(): String = "DocumentPicker"
|
|
25
|
+
|
|
26
|
+
companion object {
|
|
27
|
+
private const val REQUEST_CODE = 9900
|
|
28
|
+
private const val E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST"
|
|
29
|
+
private const val E_PICKER_CANCELLED = "E_PICKER_CANCELLED"
|
|
30
|
+
private const val E_FAILED_TO_OPEN_DOCUMENT = "E_FAILED_TO_OPEN_DOCUMENT"
|
|
31
|
+
private const val E_FILE_TOO_LARGE = "E_FILE_TOO_LARGE"
|
|
32
|
+
private const val E_MAX_FILES_EXCEEDED = "E_MAX_FILES_EXCEEDED"
|
|
33
|
+
private const val E_INVALID_OPTIONS = "E_INVALID_OPTIONS"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@ReactMethod
|
|
37
|
+
fun openDocument(options: ReadableMap, promise: Promise) {
|
|
38
|
+
if (pickerPromise != null) {
|
|
39
|
+
promise.reject(E_FAILED_TO_OPEN_DOCUMENT, "Başka bir dosya seçme işlemi zaten devam ediyor.")
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.pickerPromise = promise
|
|
44
|
+
|
|
45
|
+
val multipleFiles = options.takeIf { it.hasKey("multipleFiles") }?.getBoolean("multipleFiles") ?: false
|
|
46
|
+
val jsMaxFiles = options.takeIf { it.hasKey("maxFiles") }?.getInt("maxFiles") ?: 0
|
|
47
|
+
|
|
48
|
+
// KURAL KONTROLÜ (NATIVE): JS tarafındaki kontrolü burada da yaparak güvenliği artır.
|
|
49
|
+
if (!multipleFiles && jsMaxFiles > 1) {
|
|
50
|
+
promise.reject(E_INVALID_OPTIONS, "`maxFiles` değeri, `multipleFiles` false iken 1'den büyük olamaz.")
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// KURAL UYGULAMA (NATIVE): `maxFiles` için nihai değeri hesapla.
|
|
55
|
+
val effectiveMaxFiles = when {
|
|
56
|
+
multipleFiles && jsMaxFiles == 0 -> 3 // Varsayılan: 3
|
|
57
|
+
!multipleFiles -> 1 // Tekli seçimde her zaman 1
|
|
58
|
+
else -> jsMaxFiles // Belirtilen değeri kullan
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Değerleri sınıf değişkenlerine ata
|
|
62
|
+
this.maxFiles = effectiveMaxFiles
|
|
63
|
+
this.maxSize = options.takeIf { it.hasKey("maxSize") }?.getDouble("maxSize") ?: 0.0
|
|
64
|
+
this.excludedUris = options.takeIf { it.hasKey("excludedUris") }
|
|
65
|
+
?.getArray("excludedUris")?.toArrayList()?.mapNotNull { it.toString() } ?: emptyList()
|
|
66
|
+
|
|
67
|
+
val currentActivity = reactContext.currentActivity
|
|
68
|
+
if (currentActivity == null) {
|
|
69
|
+
pickerPromise?.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity mevcut değil.")
|
|
70
|
+
pickerPromise = null
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
val fileTypes = options.takeIf { it.hasKey("fileTypes") }
|
|
75
|
+
?.getArray("fileTypes")?.toArrayList()?.mapNotNull { it.toString() } ?: listOf("*/*")
|
|
76
|
+
val mimeTypes = if (fileTypes.isEmpty()) arrayOf("*/*") else fileTypes.toTypedArray()
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// ANAHTAR: ACTION_GET_CONTENT, güvenilir çoklu seçimi etkinleştirir.
|
|
80
|
+
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
|
81
|
+
type = "*/*"
|
|
82
|
+
addCategory(Intent.CATEGORY_OPENABLE)
|
|
83
|
+
putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
|
|
84
|
+
|
|
85
|
+
if (multipleFiles) {
|
|
86
|
+
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
currentActivity.startActivityForResult(Intent.createChooser(intent, "Dosya Seçin"), REQUEST_CODE)
|
|
90
|
+
} catch (e: Exception) {
|
|
91
|
+
pickerPromise?.reject(E_FAILED_TO_OPEN_DOCUMENT, e)
|
|
92
|
+
pickerPromise = null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
|
|
97
|
+
if (requestCode != REQUEST_CODE || pickerPromise == null) { return }
|
|
98
|
+
|
|
99
|
+
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
100
|
+
val allSelectedUris = mutableListOf<Uri>()
|
|
101
|
+
data.clipData?.let { clip ->
|
|
102
|
+
for (i in 0 until clip.itemCount) {
|
|
103
|
+
allSelectedUris.add(clip.getItemAt(i).uri)
|
|
104
|
+
}
|
|
105
|
+
} ?: data.data?.let { uri ->
|
|
106
|
+
allSelectedUris.add(uri)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
val newSelectedUris = allSelectedUris.filter { uri -> !excludedUris.contains(uri.toString()) }
|
|
110
|
+
|
|
111
|
+
if (this.maxFiles > 0 && newSelectedUris.size > this.maxFiles) {
|
|
112
|
+
pickerPromise?.reject(E_MAX_FILES_EXCEEDED, "En fazla ${this.maxFiles} adet yeni dosya seçebilirsiniz.")
|
|
113
|
+
pickerPromise = null
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
val fileInfoArray = processSelectedFiles(newSelectedUris)
|
|
119
|
+
pickerPromise?.resolve(fileInfoArray)
|
|
120
|
+
} catch (e: Exception) {
|
|
121
|
+
pickerPromise?.reject(E_FILE_TOO_LARGE, e.message)
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
pickerPromise?.reject(E_PICKER_CANCELLED, "Dosya seçimi iptal edildi.")
|
|
125
|
+
}
|
|
126
|
+
pickerPromise = null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
override fun onNewIntent(intent: Intent) {}
|
|
130
|
+
|
|
131
|
+
private fun processSelectedFiles(uris: List<Uri>): WritableArray {
|
|
132
|
+
val fileObjects = WritableNativeArray()
|
|
133
|
+
val maxSizeBytes = (maxSize * 1024 * 1024).toLong()
|
|
134
|
+
|
|
135
|
+
for (uri in uris) {
|
|
136
|
+
reactContext.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
|
137
|
+
if (cursor.moveToFirst()) {
|
|
138
|
+
val fileNameIndex = cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
|
|
139
|
+
val fileSizeIndex = cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)
|
|
140
|
+
val fileName = cursor.getString(fileNameIndex)
|
|
141
|
+
val fileSize = cursor.getLong(fileSizeIndex)
|
|
142
|
+
|
|
143
|
+
if (maxSizeBytes > 0 && fileSize > maxSizeBytes) {
|
|
144
|
+
throw Exception("Seçilen dosya ($fileName) belirtilen ${maxSize}MB boyutundan büyük.")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
val mimeType = getMimeType(fileName)
|
|
148
|
+
val base64 = reactContext.contentResolver.openInputStream(uri)?.use { inputStream ->
|
|
149
|
+
val byteBuffer = ByteArrayOutputStream()
|
|
150
|
+
val buffer = ByteArray(1024)
|
|
151
|
+
var len: Int
|
|
152
|
+
while (inputStream.read(buffer).also { len = it } != -1) {
|
|
153
|
+
byteBuffer.write(buffer, 0, len)
|
|
154
|
+
}
|
|
155
|
+
Base64.encodeToString(byteBuffer.toByteArray(), Base64.NO_WRAP)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
val fileMap = WritableNativeMap().apply {
|
|
159
|
+
putString("fileName", fileName)
|
|
160
|
+
putDouble("fileSize", fileSize.toDouble())
|
|
161
|
+
putString("fileType", mimeType)
|
|
162
|
+
putString("fileUri", uri.toString())
|
|
163
|
+
putString("base64", base64)
|
|
164
|
+
}
|
|
165
|
+
fileObjects.pushMap(fileMap)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return fileObjects
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private fun getMimeType(fileName: String): String? = URLConnection.guessContentTypeFromName(fileName)
|
|
173
173
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
package com.myuploader
|
|
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 MyUploaderPackage : ReactPackage {
|
|
9
|
-
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
-
return listOf(MyUploaderModule(reactContext))
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
-
return emptyList()
|
|
15
|
-
}
|
|
1
|
+
package com.myuploader
|
|
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 MyUploaderPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(MyUploaderModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return emptyList()
|
|
15
|
+
}
|
|
16
16
|
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
package com.myuploaderandroid
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import android.provider.OpenableColumns
|
|
7
|
+
import android.util.Base64
|
|
8
|
+
import com.facebook.react.bridge.*
|
|
9
|
+
import java.io.ByteArrayOutputStream
|
|
10
|
+
import java.net.URLConnection
|
|
11
|
+
|
|
12
|
+
class MyUploaderModule(private val reactContext: ReactApplicationContext) :
|
|
13
|
+
ReactContextBaseJavaModule(reactContext), ActivityEventListener {
|
|
14
|
+
|
|
15
|
+
private var pickerPromise: Promise? = null
|
|
16
|
+
private var maxSize: Double = 0.0
|
|
17
|
+
private var maxFiles: Int = 0
|
|
18
|
+
private var excludedUris: List<String> = emptyList()
|
|
19
|
+
|
|
20
|
+
init {
|
|
21
|
+
reactContext.addActivityEventListener(this)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun getName(): String = "DocumentPicker"
|
|
25
|
+
|
|
26
|
+
companion object {
|
|
27
|
+
private const val REQUEST_CODE = 9900
|
|
28
|
+
private const val E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST"
|
|
29
|
+
private const val E_PICKER_CANCELLED = "E_PICKER_CANCELLED"
|
|
30
|
+
private const val E_FAILED_TO_OPEN_DOCUMENT = "E_FAILED_TO_OPEN_DOCUMENT"
|
|
31
|
+
private const val E_FILE_TOO_LARGE = "E_FILE_TOO_LARGE"
|
|
32
|
+
private const val E_MAX_FILES_EXCEEDED = "E_MAX_FILES_EXCEEDED"
|
|
33
|
+
private const val E_INVALID_OPTIONS = "E_INVALID_OPTIONS"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@ReactMethod
|
|
37
|
+
fun openDocument(options: ReadableMap, promise: Promise) {
|
|
38
|
+
if (pickerPromise != null) {
|
|
39
|
+
promise.reject(E_FAILED_TO_OPEN_DOCUMENT, "Başka bir dosya seçme işlemi zaten devam ediyor.")
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.pickerPromise = promise
|
|
44
|
+
|
|
45
|
+
val multipleFiles = options.takeIf { it.hasKey("multipleFiles") }?.getBoolean("multipleFiles") ?: false
|
|
46
|
+
val jsMaxFiles = options.takeIf { it.hasKey("maxFiles") }?.getInt("maxFiles") ?: 0
|
|
47
|
+
|
|
48
|
+
// KURAL KONTROLÜ (NATIVE): JS tarafındaki kontrolü burada da yaparak güvenliği artır.
|
|
49
|
+
if (!multipleFiles && jsMaxFiles > 1) {
|
|
50
|
+
promise.reject(E_INVALID_OPTIONS, "`maxFiles` değeri, `multipleFiles` false iken 1'den büyük olamaz.")
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// KURAL UYGULAMA (NATIVE): `maxFiles` için nihai değeri hesapla.
|
|
55
|
+
val effectiveMaxFiles = when {
|
|
56
|
+
multipleFiles && jsMaxFiles == 0 -> 3 // Varsayılan: 3
|
|
57
|
+
!multipleFiles -> 1 // Tekli seçimde her zaman 1
|
|
58
|
+
else -> jsMaxFiles // Belirtilen değeri kullan
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Değerleri sınıf değişkenlerine ata
|
|
62
|
+
this.maxFiles = effectiveMaxFiles
|
|
63
|
+
this.maxSize = options.takeIf { it.hasKey("maxSize") }?.getDouble("maxSize") ?: 0.0
|
|
64
|
+
this.excludedUris = options.takeIf { it.hasKey("excludedUris") }
|
|
65
|
+
?.getArray("excludedUris")?.toArrayList()?.mapNotNull { it.toString() } ?: emptyList()
|
|
66
|
+
|
|
67
|
+
val currentActivity = reactContext.currentActivity
|
|
68
|
+
if (currentActivity == null) {
|
|
69
|
+
pickerPromise?.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity mevcut değil.")
|
|
70
|
+
pickerPromise = null
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
val fileTypes = options.takeIf { it.hasKey("fileTypes") }
|
|
75
|
+
?.getArray("fileTypes")?.toArrayList()?.mapNotNull { it.toString() } ?: listOf("*/*")
|
|
76
|
+
val mimeTypes = if (fileTypes.isEmpty()) arrayOf("*/*") else fileTypes.toTypedArray()
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// ANAHTAR: ACTION_GET_CONTENT, güvenilir çoklu seçimi etkinleştirir.
|
|
80
|
+
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
|
81
|
+
type = "*/*"
|
|
82
|
+
addCategory(Intent.CATEGORY_OPENABLE)
|
|
83
|
+
putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
|
|
84
|
+
|
|
85
|
+
if (multipleFiles) {
|
|
86
|
+
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
currentActivity.startActivityForResult(Intent.createChooser(intent, "Dosya Seçin"), REQUEST_CODE)
|
|
90
|
+
} catch (e: Exception) {
|
|
91
|
+
pickerPromise?.reject(E_FAILED_TO_OPEN_DOCUMENT, e)
|
|
92
|
+
pickerPromise = null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
|
|
97
|
+
if (requestCode != REQUEST_CODE || pickerPromise == null) { return }
|
|
98
|
+
|
|
99
|
+
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
100
|
+
val allSelectedUris = mutableListOf<Uri>()
|
|
101
|
+
data.clipData?.let { clip ->
|
|
102
|
+
for (i in 0 until clip.itemCount) {
|
|
103
|
+
allSelectedUris.add(clip.getItemAt(i).uri)
|
|
104
|
+
}
|
|
105
|
+
} ?: data.data?.let { uri ->
|
|
106
|
+
allSelectedUris.add(uri)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
val newSelectedUris = allSelectedUris.filter { uri -> !excludedUris.contains(uri.toString()) }
|
|
110
|
+
|
|
111
|
+
if (this.maxFiles > 0 && newSelectedUris.size > this.maxFiles) {
|
|
112
|
+
pickerPromise?.reject(E_MAX_FILES_EXCEEDED, "En fazla ${this.maxFiles} adet yeni dosya seçebilirsiniz.")
|
|
113
|
+
pickerPromise = null
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
val fileInfoArray = processSelectedFiles(newSelectedUris)
|
|
119
|
+
pickerPromise?.resolve(fileInfoArray)
|
|
120
|
+
} catch (e: Exception) {
|
|
121
|
+
pickerPromise?.reject(E_FILE_TOO_LARGE, e.message)
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
pickerPromise?.reject(E_PICKER_CANCELLED, "Dosya seçimi iptal edildi.")
|
|
125
|
+
}
|
|
126
|
+
pickerPromise = null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
override fun onNewIntent(intent: Intent) {}
|
|
130
|
+
|
|
131
|
+
private fun processSelectedFiles(uris: List<Uri>): WritableArray {
|
|
132
|
+
val fileObjects = WritableNativeArray()
|
|
133
|
+
val maxSizeBytes = (maxSize * 1024 * 1024).toLong()
|
|
134
|
+
|
|
135
|
+
for (uri in uris) {
|
|
136
|
+
reactContext.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
|
137
|
+
if (cursor.moveToFirst()) {
|
|
138
|
+
val fileNameIndex = cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
|
|
139
|
+
val fileSizeIndex = cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)
|
|
140
|
+
val fileName = cursor.getString(fileNameIndex)
|
|
141
|
+
val fileSize = cursor.getLong(fileSizeIndex)
|
|
142
|
+
|
|
143
|
+
if (maxSizeBytes > 0 && fileSize > maxSizeBytes) {
|
|
144
|
+
throw Exception("Seçilen dosya ($fileName) belirtilen ${maxSize}MB boyutundan büyük.")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
val mimeType = getMimeType(fileName)
|
|
148
|
+
val base64 = reactContext.contentResolver.openInputStream(uri)?.use { inputStream ->
|
|
149
|
+
val byteBuffer = ByteArrayOutputStream()
|
|
150
|
+
val buffer = ByteArray(1024)
|
|
151
|
+
var len: Int
|
|
152
|
+
while (inputStream.read(buffer).also { len = it } != -1) {
|
|
153
|
+
byteBuffer.write(buffer, 0, len)
|
|
154
|
+
}
|
|
155
|
+
Base64.encodeToString(byteBuffer.toByteArray(), Base64.NO_WRAP)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
val fileMap = WritableNativeMap().apply {
|
|
159
|
+
putString("fileName", fileName)
|
|
160
|
+
putDouble("fileSize", fileSize.toDouble())
|
|
161
|
+
putString("fileType", mimeType)
|
|
162
|
+
putString("fileUri", uri.toString())
|
|
163
|
+
putString("base64", base64)
|
|
164
|
+
}
|
|
165
|
+
fileObjects.pushMap(fileMap)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return fileObjects
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private fun getMimeType(fileName: String): String? = URLConnection.guessContentTypeFromName(fileName)
|
|
173
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.myuploaderandroid
|
|
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 MyUploaderPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(MyUploaderModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return emptyList()
|
|
15
|
+
}
|
|
16
|
+
}
|
package/ios/MyUploader.h
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
#ifndef MuhasebeonayMain_Bridging_Header_h
|
|
3
|
-
#define MuhasebeonayMain_Bridging_Header_h
|
|
4
|
-
#import "React/RCTBridgeModule.h"
|
|
5
|
-
#import "React/RCTViewManager.h"
|
|
6
|
-
#import "React/RCTEventEmitter.h"
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
|
|
2
|
+
#ifndef MuhasebeonayMain_Bridging_Header_h
|
|
3
|
+
#define MuhasebeonayMain_Bridging_Header_h
|
|
4
|
+
#import "React/RCTBridgeModule.h"
|
|
5
|
+
#import "React/RCTViewManager.h"
|
|
6
|
+
#import "React/RCTEventEmitter.h"
|
|
7
|
+
|
|
8
|
+
|
package/ios/MyUploader.mm
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
#import <Foundation/Foundation.h>
|
|
3
|
-
#import <React/RCTBridgeModule.h>
|
|
4
|
-
|
|
5
|
-
@interface RCT_EXTERN_MODULE(MyUploader, NSObject)
|
|
6
|
-
|
|
7
|
-
RCT_EXTERN_METHOD(openDocument:(NSDictionary)options
|
|
8
|
-
successCallback:(RCTResponseSenderBlock)successCallback
|
|
9
|
-
failureCallback:(RCTResponseSenderBlock)failureCallback)
|
|
10
|
-
|
|
1
|
+
|
|
2
|
+
#import <Foundation/Foundation.h>
|
|
3
|
+
#import <React/RCTBridgeModule.h>
|
|
4
|
+
|
|
5
|
+
@interface RCT_EXTERN_MODULE(MyUploader, NSObject)
|
|
6
|
+
|
|
7
|
+
RCT_EXTERN_METHOD(openDocument:(NSDictionary)options
|
|
8
|
+
successCallback:(RCTResponseSenderBlock)successCallback
|
|
9
|
+
failureCallback:(RCTResponseSenderBlock)failureCallback)
|
|
10
|
+
|
|
11
11
|
@end
|