react-native-my-uploader-android 1.0.28 → 1.0.30
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/android/src/main/java/com/myuploaderandroid/DownloadFileModule.kt +91 -58
- package/android/src/main/java/com/myuploaderandroid/MyUploaderModule.kt +93 -42
- package/lib/commonjs/components/DownloadFile.js +37 -47
- package/lib/commonjs/components/DownloadFile.js.map +1 -1
- package/lib/commonjs/components/MyUploader.js +57 -80
- package/lib/commonjs/components/MyUploader.js.map +1 -1
- package/lib/commonjs/index.d.js.map +1 -1
- package/lib/commonjs/index.js +9 -7
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/components/DownloadFile.js +38 -48
- package/lib/module/components/DownloadFile.js.map +1 -1
- package/lib/module/components/MyUploader.js +55 -79
- package/lib/module/components/MyUploader.js.map +1 -1
- package/lib/module/index.d.js +10 -0
- package/lib/module/index.d.js.map +1 -1
- package/lib/module/index.js +10 -6
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DownloadFile.tsx +40 -61
- package/src/components/MyUploader.tsx +59 -89
- package/src/index.d.ts +12 -5
- package/src/index.ts +12 -6
- package/src/types.ts +98 -41
- package/android/src/main/java/com/myuploader/MyUploaderModule.kt +0 -173
- package/android/src/main/java/com/myuploader/MyUploaderPackage.kt +0 -16
- package/lib/commonjs/NativeMyUploader.js +0 -15
- package/lib/commonjs/NativeMyUploader.js.map +0 -1
- package/lib/module/NativeMyUploader.js +0 -9
- package/lib/module/NativeMyUploader.js.map +0 -1
- package/src/NativeMyUploader.ts +0 -25
|
@@ -12,54 +12,70 @@ import android.util.Log
|
|
|
12
12
|
import android.webkit.CookieManager
|
|
13
13
|
import android.webkit.URLUtil
|
|
14
14
|
import com.facebook.react.bridge.*
|
|
15
|
-
import
|
|
15
|
+
import java.io.File
|
|
16
16
|
import java.net.HttpURLConnection
|
|
17
17
|
import java.net.URL
|
|
18
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
18
19
|
|
|
19
20
|
class DownloadFileModule(private val reactContext: ReactApplicationContext) :
|
|
20
21
|
ReactContextBaseJavaModule(reactContext) {
|
|
21
22
|
|
|
22
23
|
private val TAG = "DownloadFileModule"
|
|
23
|
-
private val downloadPromises =
|
|
24
|
+
private val downloadPromises = ConcurrentHashMap<Long, Promise>()
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
val downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
|
|
31
|
-
if (downloadPromises.containsKey(downloadId)) {
|
|
32
|
-
val promise = downloadPromises[downloadId]
|
|
26
|
+
private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
|
|
27
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
28
|
+
val downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
|
|
29
|
+
if (downloadId != -1L) {
|
|
30
|
+
downloadPromises.remove(downloadId)?.let { promise ->
|
|
33
31
|
checkDownloadStatus(downloadId, promise)
|
|
34
|
-
downloadPromises.remove(downloadId)
|
|
35
32
|
}
|
|
36
33
|
}
|
|
37
34
|
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
override fun getName(): String = "DownloadFile"
|
|
38
|
+
|
|
39
|
+
init {
|
|
38
40
|
val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
|
|
39
41
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
40
|
-
|
|
42
|
+
reactContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
|
|
41
43
|
} else {
|
|
42
|
-
|
|
44
|
+
reactContext.registerReceiver(receiver, intentFilter)
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
override fun onCatalystInstanceDestroy() {
|
|
49
|
+
try {
|
|
50
|
+
reactContext.unregisterReceiver(receiver)
|
|
51
|
+
} catch (e: Exception) {
|
|
52
|
+
Log.w(TAG, "Receiver unregister warning: ${e.message}")
|
|
53
|
+
}
|
|
54
|
+
super.onCatalystInstanceDestroy()
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
@ReactMethod
|
|
47
58
|
fun downloadFile(fileUrl: String, options: ReadableMap, promise: Promise) {
|
|
48
59
|
val isDebug = if (options.hasKey("debug")) options.getBoolean("debug") else false
|
|
49
|
-
val shouldLog = BuildConfig.DEBUG && isDebug
|
|
50
60
|
val maxSizeMB = if (options.hasKey("maxSize")) options.getDouble("maxSize") else 0.0
|
|
61
|
+
|
|
62
|
+
// Allowed File Types (Mime Types)
|
|
63
|
+
val allowedTypes = if (options.hasKey("fileTypes")) {
|
|
64
|
+
options.getArray("fileTypes")?.toArrayList()?.mapNotNull { it.toString() } ?: emptyList()
|
|
65
|
+
} else emptyList()
|
|
51
66
|
|
|
52
|
-
if (
|
|
67
|
+
if (isDebug) Log.d(TAG, "İndirme başlatılıyor: $fileUrl")
|
|
53
68
|
|
|
54
69
|
Thread {
|
|
70
|
+
var connection: HttpURLConnection? = null
|
|
55
71
|
try {
|
|
56
72
|
val url = URL(fileUrl)
|
|
57
|
-
|
|
73
|
+
connection = url.openConnection() as HttpURLConnection
|
|
58
74
|
val cookie = CookieManager.getInstance().getCookie(fileUrl)
|
|
59
75
|
if (cookie != null) connection.setRequestProperty("Cookie", cookie)
|
|
60
|
-
connection.requestMethod = "HEAD"
|
|
76
|
+
connection.requestMethod = "HEAD" // Sadece başlıkları çek
|
|
61
77
|
connection.connect()
|
|
62
|
-
|
|
78
|
+
|
|
63
79
|
val responseCode = connection.responseCode
|
|
64
80
|
if (responseCode !in 200..299) {
|
|
65
81
|
promise.reject("E_HTTP_ERROR", "Sunucu hatası: $responseCode")
|
|
@@ -68,22 +84,49 @@ class DownloadFileModule(private val reactContext: ReactApplicationContext) :
|
|
|
68
84
|
|
|
69
85
|
val fileSize = connection.contentLengthLong
|
|
70
86
|
val contentType = connection.contentType
|
|
71
|
-
val contentDisposition = connection.getHeaderField("Content-Disposition")
|
|
72
87
|
val cleanContentType = contentType?.split(";")?.get(0)?.trim() ?: "*/*"
|
|
88
|
+
|
|
89
|
+
// 1. Content-Type Kontrolü
|
|
90
|
+
if (allowedTypes.isNotEmpty() && !allowedTypes.contains("*/*")) {
|
|
91
|
+
var isAllowed = false
|
|
92
|
+
for (type in allowedTypes) {
|
|
93
|
+
if (type == "*/*" || cleanContentType.equals(type, ignoreCase = true) || cleanContentType.startsWith(type.replace("*", ""))) {
|
|
94
|
+
isAllowed = true
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!isAllowed) {
|
|
99
|
+
promise.reject("E_INVALID_FILE_TYPE", "Dosya türü ($cleanContentType) izin verilenler listesinde yok.")
|
|
100
|
+
return@Thread
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 2. Boyut Kontrolü
|
|
105
|
+
if (maxSizeMB > 0 && fileSize > 0 && fileSize > maxSizeMB * 1024 * 1024) {
|
|
106
|
+
promise.reject("E_FILE_TOO_LARGE", "Dosya boyutu (${(fileSize / 1024.0 / 1024.0).toInt()}MB) limitin (${maxSizeMB}MB) üzerinde.")
|
|
107
|
+
return@Thread
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
val contentDisposition = connection.getHeaderField("Content-Disposition")
|
|
73
111
|
val fileName = URLUtil.guessFileName(fileUrl, contentDisposition, cleanContentType)
|
|
74
112
|
|
|
75
|
-
|
|
113
|
+
// Dosya Adı Çakışma Önleme
|
|
114
|
+
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
|
115
|
+
val baseName = fileName.substringBeforeLast(".")
|
|
116
|
+
val ext = fileName.substringAfterLast(".", "")
|
|
117
|
+
var counter = 1
|
|
118
|
+
var finalFileName = fileName
|
|
76
119
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
120
|
+
while (File(downloadsDir, finalFileName).exists()) {
|
|
121
|
+
finalFileName = if (ext.isNotEmpty()) "$baseName($counter).$ext" else "$baseName($counter)"
|
|
122
|
+
counter++
|
|
80
123
|
}
|
|
81
124
|
|
|
82
125
|
val request = DownloadManager.Request(Uri.parse(fileUrl))
|
|
83
|
-
.setTitle(
|
|
84
|
-
.setDescription("
|
|
126
|
+
.setTitle(finalFileName)
|
|
127
|
+
.setDescription("Dosya indiriliyor...")
|
|
85
128
|
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
|
86
|
-
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,
|
|
129
|
+
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, finalFileName)
|
|
87
130
|
.setAllowedOverMetered(true)
|
|
88
131
|
|
|
89
132
|
if (cookie != null) request.addRequestHeader("Cookie", cookie)
|
|
@@ -91,13 +134,13 @@ class DownloadFileModule(private val reactContext: ReactApplicationContext) :
|
|
|
91
134
|
|
|
92
135
|
val downloadManager = reactContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
|
93
136
|
val downloadId = downloadManager.enqueue(request)
|
|
94
|
-
|
|
95
|
-
if (BuildConfig.DEBUG) Log.i(TAG, "İndirme sıraya eklendi. ID: #$downloadId")
|
|
137
|
+
|
|
96
138
|
downloadPromises[downloadId] = promise
|
|
97
139
|
|
|
98
140
|
} catch (e: Exception) {
|
|
99
|
-
if (BuildConfig.DEBUG) Log.e(TAG, "İndirme başlatılırken hata!", e)
|
|
100
141
|
promise.reject("E_DOWNLOAD_SETUP_FAILED", e.message)
|
|
142
|
+
} finally {
|
|
143
|
+
connection?.disconnect()
|
|
101
144
|
}
|
|
102
145
|
}.start()
|
|
103
146
|
}
|
|
@@ -107,38 +150,28 @@ class DownloadFileModule(private val reactContext: ReactApplicationContext) :
|
|
|
107
150
|
val query = DownloadManager.Query().setFilterById(id)
|
|
108
151
|
val cursor = downloadManager.query(query)
|
|
109
152
|
|
|
110
|
-
if (cursor
|
|
111
|
-
|
|
112
|
-
|
|
153
|
+
if (cursor == null) {
|
|
154
|
+
promise?.reject("E_DB_CURSOR_NULL", "Download cursor null")
|
|
155
|
+
return
|
|
156
|
+
}
|
|
113
157
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
val
|
|
117
|
-
|
|
158
|
+
cursor.use {
|
|
159
|
+
if (it.moveToFirst()) {
|
|
160
|
+
val statusIndex = it.getColumnIndex(DownloadManager.COLUMN_STATUS)
|
|
161
|
+
val status = if (statusIndex >= 0) it.getInt(statusIndex) else DownloadManager.STATUS_FAILED
|
|
162
|
+
|
|
163
|
+
if (status == DownloadManager.STATUS_SUCCESSFUL) {
|
|
164
|
+
val uriIndex = it.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
|
|
165
|
+
val localUri = if (uriIndex >= 0) it.getString(uriIndex) else null
|
|
166
|
+
promise?.resolve(localUri)
|
|
167
|
+
} else {
|
|
168
|
+
val reasonIndex = it.getColumnIndex(DownloadManager.COLUMN_REASON)
|
|
169
|
+
val reason = if (reasonIndex >= 0) it.getInt(reasonIndex) else -1
|
|
170
|
+
promise?.reject("E_DOWNLOAD_FAILED", "İndirme başarısız kod: $reason")
|
|
171
|
+
}
|
|
118
172
|
} else {
|
|
119
|
-
|
|
120
|
-
val reason = cursor.getInt(reasonIndex)
|
|
121
|
-
val reasonText = getDownloadErrorReason(reason)
|
|
122
|
-
promise?.reject("E_DOWNLOAD_FAILED", "İndirme başarısız. Sebep: $reasonText (Kod: $reason)")
|
|
173
|
+
promise?.reject("E_DOWNLOAD_NOT_FOUND", "İndirme kaydı bulunamadı.")
|
|
123
174
|
}
|
|
124
|
-
} else {
|
|
125
|
-
promise?.reject("E_DOWNLOAD_NOT_FOUND", "İndirme işlemi bulunamadı.")
|
|
126
|
-
}
|
|
127
|
-
cursor.close()
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private fun getDownloadErrorReason(reasonCode: Int): String {
|
|
131
|
-
return when (reasonCode) {
|
|
132
|
-
DownloadManager.ERROR_CANNOT_RESUME -> "İndirme devam ettirilemiyor."
|
|
133
|
-
DownloadManager.ERROR_DEVICE_NOT_FOUND -> "Harici depolama bulunamadı."
|
|
134
|
-
DownloadManager.ERROR_FILE_ALREADY_EXISTS -> "Aynı isimde bir dosya zaten var."
|
|
135
|
-
DownloadManager.ERROR_FILE_ERROR -> "Dosya sistemi hatası."
|
|
136
|
-
DownloadManager.ERROR_HTTP_DATA_ERROR -> "Sunucu ile veri alışverişinde hata."
|
|
137
|
-
DownloadManager.ERROR_INSUFFICIENT_SPACE -> "Cihazda yeterli alan yok."
|
|
138
|
-
DownloadManager.ERROR_TOO_MANY_REDIRECTS -> "Çok fazla yönlendirme yapıldı."
|
|
139
|
-
DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> "Sunucudan beklenmeyen HTTP kodu alındı."
|
|
140
|
-
DownloadManager.ERROR_UNKNOWN -> "Bilinmeyen bir hata oluştu."
|
|
141
|
-
else -> "Bilinmeyen hata."
|
|
142
175
|
}
|
|
143
176
|
}
|
|
144
177
|
}
|
|
@@ -21,7 +21,7 @@ class MyUploaderModule(private val reactContext: ReactApplicationContext) :
|
|
|
21
21
|
reactContext.addActivityEventListener(this)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
override fun getName(): String = "
|
|
24
|
+
override fun getName(): String = "UploadDocumentPicker"
|
|
25
25
|
|
|
26
26
|
companion object {
|
|
27
27
|
private const val REQUEST_CODE = 9900
|
|
@@ -94,37 +94,51 @@ class MyUploaderModule(private val reactContext: ReactApplicationContext) :
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
|
|
97
|
-
|
|
97
|
+
if (requestCode != REQUEST_CODE || pickerPromise == null) { return }
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
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()) }
|
|
99
|
+
val currentPromise = pickerPromise
|
|
100
|
+
pickerPromise = null
|
|
110
101
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
102
|
+
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
103
|
+
val allSelectedUris = mutableListOf<Uri>()
|
|
104
|
+
data.clipData?.let { clip ->
|
|
105
|
+
for (i in 0 until clip.itemCount) {
|
|
106
|
+
allSelectedUris.add(clip.getItemAt(i).uri)
|
|
115
107
|
}
|
|
108
|
+
} ?: data.data?.let { uri ->
|
|
109
|
+
allSelectedUris.add(uri)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
val newSelectedUris = allSelectedUris.filter { uri -> !excludedUris.contains(uri.toString()) }
|
|
113
|
+
|
|
114
|
+
if (this.maxFiles > 0 && newSelectedUris.size > this.maxFiles) {
|
|
115
|
+
currentPromise?.reject(E_MAX_FILES_EXCEEDED, "En fazla ${this.maxFiles} adet yeni dosya seçebilirsiniz.")
|
|
116
|
+
return
|
|
117
|
+
}
|
|
116
118
|
|
|
119
|
+
Thread {
|
|
117
120
|
try {
|
|
118
121
|
val fileInfoArray = processSelectedFiles(newSelectedUris)
|
|
119
|
-
|
|
122
|
+
// Sonucu UI thread'inde Promise'e gönder
|
|
123
|
+
reactContext.runOnUiQueueThread {
|
|
124
|
+
currentPromise?.resolve(fileInfoArray)
|
|
125
|
+
}
|
|
120
126
|
} catch (e: Exception) {
|
|
121
|
-
|
|
127
|
+
// Hataları UI thread'inde Promise'e gönder
|
|
128
|
+
reactContext.runOnUiQueueThread {
|
|
129
|
+
when (e) {
|
|
130
|
+
is FileTooLargeException -> currentPromise?.reject(E_FILE_TOO_LARGE, e.message)
|
|
131
|
+
is FileNotFoundException -> currentPromise?.reject(E_FAILED_TO_OPEN_DOCUMENT, "Dosya bulunamadı veya erişilemedi.")
|
|
132
|
+
else -> currentPromise?.reject(E_FAILED_TO_OPEN_DOCUMENT, e.message)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
122
135
|
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
pickerPromise = null
|
|
136
|
+
}.start()
|
|
137
|
+
} else {
|
|
138
|
+
currentPromise?.reject(E_PICKER_CANCELLED, "Dosya seçimi iptal edildi.")
|
|
127
139
|
}
|
|
140
|
+
}
|
|
141
|
+
|
|
128
142
|
|
|
129
143
|
override fun onNewIntent(intent: Intent) {}
|
|
130
144
|
|
|
@@ -135,39 +149,76 @@ class MyUploaderModule(private val reactContext: ReactApplicationContext) :
|
|
|
135
149
|
for (uri in uris) {
|
|
136
150
|
reactContext.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
|
137
151
|
if (cursor.moveToFirst()) {
|
|
138
|
-
val
|
|
139
|
-
val
|
|
140
|
-
val fileName = cursor.getString(fileNameIndex)
|
|
141
|
-
val fileSize = cursor.getLong(fileSizeIndex)
|
|
152
|
+
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
|
153
|
+
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
|
|
142
154
|
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
}
|
|
155
|
+
val fileName = if (nameIndex != -1) cursor.getString(nameIndex) else guessFileNameFromUri(uri)
|
|
156
|
+
val fileSize = if (sizeIndex != -1) cursor.getLong(sizeIndex) else -1L
|
|
146
157
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
var len: Int
|
|
152
|
-
while (inputStream.read(buffer).also { len = it } != -1) {
|
|
153
|
-
byteBuffer.write(buffer, 0, len)
|
|
158
|
+
var trueSize = -1L
|
|
159
|
+
try {
|
|
160
|
+
reactContext.contentResolver.openAssetFileDescriptor(uri, "r")?.use { afd ->
|
|
161
|
+
trueSize = afd.length
|
|
154
162
|
}
|
|
155
|
-
|
|
163
|
+
} catch (e: FileNotFoundException) {
|
|
164
|
+
// Bazı durumlarda bu metod hata verebilir, görmezden gel ve devam et
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (trueSize <= 0) trueSize = fileSize
|
|
168
|
+
|
|
169
|
+
if (maxSizeBytes > 0 && trueSize > 0 && trueSize > maxSizeBytes) {
|
|
170
|
+
throw FileTooLargeException("Seçilen dosya ($fileName) belirtilen ${maxSize}MB boyutundan büyük.")
|
|
156
171
|
}
|
|
157
172
|
|
|
173
|
+
val mimeType = reactContext.contentResolver.getType(uri) ?: URLConnection.guessContentTypeFromName(fileName) ?: "application/octet-stream"
|
|
174
|
+
val base64 = encodeFileToBase64(uri)
|
|
175
|
+
|
|
158
176
|
val fileMap = WritableNativeMap().apply {
|
|
159
177
|
putString("fileName", fileName)
|
|
160
|
-
putDouble("fileSize", fileSize.toDouble())
|
|
178
|
+
putDouble("fileSize", if (trueSize > 0) trueSize.toDouble() else fileSize.toDouble())
|
|
161
179
|
putString("fileType", mimeType)
|
|
162
180
|
putString("fileUri", uri.toString())
|
|
163
181
|
putString("base64", base64)
|
|
164
182
|
}
|
|
165
183
|
fileObjects.pushMap(fileMap)
|
|
166
184
|
}
|
|
167
|
-
}
|
|
185
|
+
} ?:throw FileNotFoundException("Content ile dosya bilgisi alınamadı:$uri")
|
|
168
186
|
}
|
|
169
187
|
return fileObjects
|
|
170
188
|
}
|
|
171
|
-
|
|
172
|
-
private fun
|
|
189
|
+
|
|
190
|
+
private fun guessFileNameFromUri (uri:Uri):String{
|
|
191
|
+
return uri.lastPathSegment?.substringBefore("?") ?: "unknown_file"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// OOM riskini azaltan, streaming tabanlı Base64 çevrimi
|
|
195
|
+
private fun encodeFileToBase64(uri: Uri): String? {
|
|
196
|
+
return try {
|
|
197
|
+
reactContext.contentResolver.openInputStream(uri)?.use { input ->
|
|
198
|
+
val output = ByteArrayOutputStream()
|
|
199
|
+
val encoder = Base64OutputStream(output, Base64.NO_WRAP)
|
|
200
|
+
|
|
201
|
+
// Dosyayı parça parça oku ve anlık olarak Base64'e çevir
|
|
202
|
+
input.copyTo(encoder)
|
|
203
|
+
// .copyTo() encoder'ı otomatik olarak kapatır (flush eder).
|
|
204
|
+
|
|
205
|
+
// DÜZELTME: Doğrudan `output.toString()` değil,
|
|
206
|
+
// `Base64.encodeToString` kullan. Ancak bu gereksiz bir adımdır,
|
|
207
|
+
// çünkü `output` zaten Base64'tür. En basit yol:
|
|
208
|
+
String(output.toByteArray(), Charsets.US_ASCII)
|
|
209
|
+
}
|
|
210
|
+
} catch (e: Exception) {
|
|
211
|
+
null
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
override fun onCatalystInstanceDestroy() {
|
|
216
|
+
// Bellek sızıntısını önlemek için event listener'ı kaldır.
|
|
217
|
+
reactContext.removeActivityEventListener(this)
|
|
218
|
+
super.onCatalystInstanceDestroy()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Özel Exception sınıfınız doğru
|
|
222
|
+
class FileTooLargeException(message: String) : Exception(message)
|
|
223
|
+
|
|
173
224
|
}
|
|
@@ -8,11 +8,11 @@ var _react = _interopRequireWildcard(require("react"));
|
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
10
10
|
const {
|
|
11
|
-
DownloadFile:
|
|
11
|
+
DownloadFile: NativeDownload
|
|
12
12
|
} = _reactNative.NativeModules;
|
|
13
13
|
const DownloadFile = ({
|
|
14
14
|
files,
|
|
15
|
-
|
|
15
|
+
multipleDownload = false,
|
|
16
16
|
disabled = false,
|
|
17
17
|
debug = false,
|
|
18
18
|
maxSize = 0,
|
|
@@ -21,82 +21,72 @@ const DownloadFile = ({
|
|
|
21
21
|
buttonIcon,
|
|
22
22
|
ButtonStyle,
|
|
23
23
|
ButtonTextStyle,
|
|
24
|
-
onSuccess
|
|
25
|
-
onError
|
|
24
|
+
onSuccess,
|
|
25
|
+
onError
|
|
26
26
|
}) => {
|
|
27
27
|
const [isLoading, setIsLoading] = (0, _react.useState)(false);
|
|
28
28
|
const handlePress = async () => {
|
|
29
|
-
if (!
|
|
30
|
-
onError(new Error("DownloadFile native module is not available."));
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
29
|
+
if (disabled || isLoading || !files.length) return;
|
|
33
30
|
setIsLoading(true);
|
|
34
|
-
const
|
|
31
|
+
const options = {
|
|
32
|
+
debug,
|
|
35
33
|
maxSize,
|
|
36
|
-
fileTypes
|
|
37
|
-
debug
|
|
34
|
+
fileTypes
|
|
38
35
|
};
|
|
36
|
+
const targetFiles = multipleDownload ? files : [files[0]];
|
|
39
37
|
try {
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const finalResult = {
|
|
38
|
+
const promises = targetFiles.map(url => NativeDownload.downloadFile(url, options));
|
|
39
|
+
const results = await Promise.allSettled(promises);
|
|
40
|
+
const final = {
|
|
44
41
|
successful: [],
|
|
45
42
|
skipped: []
|
|
46
43
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
finalResult.successful.push({
|
|
44
|
+
results.forEach((res, index) => {
|
|
45
|
+
var _targetFiles$index;
|
|
46
|
+
const originalUrl = (_targetFiles$index = targetFiles[index]) !== null && _targetFiles$index !== void 0 ? _targetFiles$index : "";
|
|
47
|
+
if (res.status === 'fulfilled') {
|
|
48
|
+
final.successful.push({
|
|
53
49
|
originalUrl,
|
|
54
|
-
|
|
55
|
-
localUri: result.value
|
|
50
|
+
localUri: res.value
|
|
56
51
|
});
|
|
57
52
|
} else {
|
|
58
|
-
|
|
53
|
+
var _res$reason;
|
|
54
|
+
final.skipped.push({
|
|
59
55
|
originalUrl,
|
|
60
|
-
|
|
61
|
-
reason: result.reason.message
|
|
56
|
+
reason: ((_res$reason = res.reason) === null || _res$reason === void 0 ? void 0 : _res$reason.message) || "Bilinmeyen Hata"
|
|
62
57
|
});
|
|
63
58
|
}
|
|
64
59
|
});
|
|
65
|
-
onSuccess(
|
|
66
|
-
} catch (
|
|
67
|
-
onError(error);
|
|
60
|
+
if (onSuccess) onSuccess(final);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
if (onError) onError(e);else console.error(e);
|
|
68
63
|
} finally {
|
|
69
64
|
setIsLoading(false);
|
|
70
65
|
}
|
|
71
66
|
};
|
|
72
|
-
const content = isLoading ? /*#__PURE__*/_react.default.createElement(_reactNative.ActivityIndicator, {
|
|
73
|
-
color: "#FFFFFF"
|
|
74
|
-
}) : buttonIcon || /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
|
|
75
|
-
style: [styles.buttonText, ButtonTextStyle]
|
|
76
|
-
}, buttonPlaceHolder);
|
|
77
67
|
return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableOpacity, {
|
|
78
|
-
style: [styles.button, ButtonStyle, (disabled || isLoading) && styles.
|
|
68
|
+
style: [styles.button, ButtonStyle, (disabled || isLoading) && styles.disabled],
|
|
79
69
|
onPress: handlePress,
|
|
80
70
|
disabled: disabled || isLoading
|
|
81
|
-
},
|
|
71
|
+
}, isLoading ? /*#__PURE__*/_react.default.createElement(_reactNative.ActivityIndicator, {
|
|
72
|
+
color: "#FFF"
|
|
73
|
+
}) : buttonIcon ? buttonIcon : /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
|
|
74
|
+
style: [styles.text, ButtonTextStyle]
|
|
75
|
+
}, buttonPlaceHolder));
|
|
82
76
|
};
|
|
83
77
|
const styles = _reactNative.StyleSheet.create({
|
|
84
78
|
button: {
|
|
85
|
-
backgroundColor: '#
|
|
86
|
-
|
|
87
|
-
paddingVertical: 10,
|
|
79
|
+
backgroundColor: '#03DAC6',
|
|
80
|
+
padding: 12,
|
|
88
81
|
borderRadius: 8,
|
|
89
|
-
alignItems: 'center'
|
|
90
|
-
justifyContent: 'center',
|
|
91
|
-
flexDirection: 'row'
|
|
82
|
+
alignItems: 'center'
|
|
92
83
|
},
|
|
93
|
-
|
|
94
|
-
color: '#
|
|
95
|
-
fontSize: 16,
|
|
84
|
+
text: {
|
|
85
|
+
color: '#000',
|
|
96
86
|
fontWeight: 'bold'
|
|
97
87
|
},
|
|
98
|
-
|
|
99
|
-
backgroundColor: '#
|
|
88
|
+
disabled: {
|
|
89
|
+
backgroundColor: '#AAA'
|
|
100
90
|
}
|
|
101
91
|
});
|
|
102
92
|
var _default = exports.default = DownloadFile;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","DownloadFile","
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","DownloadFile","NativeDownload","NativeModules","files","multipleDownload","disabled","debug","maxSize","fileTypes","buttonPlaceHolder","buttonIcon","ButtonStyle","ButtonTextStyle","onSuccess","onError","isLoading","setIsLoading","useState","handlePress","length","options","targetFiles","promises","map","url","downloadFile","results","Promise","allSettled","final","successful","skipped","forEach","res","index","_targetFiles$index","originalUrl","status","push","localUri","value","_res$reason","reason","message","console","error","createElement","TouchableOpacity","style","styles","button","onPress","ActivityIndicator","color","Text","text","StyleSheet","create","backgroundColor","padding","borderRadius","alignItems","fontWeight","_default","exports"],"sources":["DownloadFile.tsx"],"sourcesContent":["import React, { useState } from 'react';\r\nimport { TouchableOpacity, Text, StyleSheet, ActivityIndicator,NativeModules } from 'react-native';\r\nimport type { DownloadFileProps, DownloadResult } from '../types';\r\n\r\n\r\nconst { DownloadFile: NativeDownload } = NativeModules;\r\n\r\n\r\nconst DownloadFile: React.FC<DownloadFileProps> = ({\r\n files,\r\n multipleDownload = false,\r\n disabled = false,\r\n debug = false,\r\n maxSize = 0,\r\n fileTypes = ['*/*'],\r\n buttonPlaceHolder = 'Dosyaları İndir',\r\n buttonIcon,\r\n ButtonStyle,\r\n ButtonTextStyle,\r\n onSuccess,\r\n onError,\r\n}) => {\r\n const [isLoading, setIsLoading] = useState(false);\r\n\r\n const handlePress = async () => {\r\n if (disabled || isLoading || !files.length) return;\r\n setIsLoading(true);\r\n\r\n const options = { debug, maxSize, fileTypes };\r\n const targetFiles = multipleDownload ? files : [files[0]];\r\n\r\n try {\r\n const promises = targetFiles.map(url => NativeDownload.downloadFile(url, options));\r\n const results = await Promise.allSettled(promises);\r\n \r\n const final: DownloadResult = { successful: [], skipped: [] };\r\n\r\n results.forEach((res, index) => {\r\n const originalUrl = targetFiles[index] ?? \"\";\r\n if (res.status === 'fulfilled') {\r\n final.successful.push({ originalUrl, localUri: res.value });\r\n } else {\r\n final.skipped.push({ \r\n originalUrl, \r\n reason: res.reason?.message || \"Bilinmeyen Hata\" \r\n });\r\n }\r\n });\r\n\r\n if (onSuccess) onSuccess(final);\r\n } catch (e) {\r\n if (onError) onError(e);\r\n else console.error(e);\r\n } finally {\r\n setIsLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <TouchableOpacity\r\n style={[styles.button, ButtonStyle, (disabled || isLoading) && styles.disabled]}\r\n onPress={handlePress}\r\n disabled={disabled || isLoading}\r\n >\r\n {isLoading ? (\r\n <ActivityIndicator color=\"#FFF\" />\r\n ) : (\r\n buttonIcon ? buttonIcon : <Text style={[styles.text, ButtonTextStyle]}>{buttonPlaceHolder}</Text>\r\n )}\r\n </TouchableOpacity>\r\n );\r\n};\r\n\r\nconst styles = StyleSheet.create({\r\n button: {\r\n backgroundColor: '#03DAC6',\r\n padding: 12,\r\n borderRadius: 8,\r\n alignItems: 'center',\r\n },\r\n text: { color: '#000', fontWeight: 'bold' },\r\n disabled: { backgroundColor: '#AAA' }\r\n});\r\n\r\nexport default DownloadFile;"],"mappings":"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","ignoreList":[]}
|