react-native-my-uploader-android 1.0.27 → 1.0.29

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 (38) hide show
  1. package/android/src/main/java/com/myuploaderandroid/DownloadFileModule.kt +91 -58
  2. package/android/src/main/java/com/myuploaderandroid/MyUploaderModule.kt +71 -87
  3. package/android/src/main/java/com/myuploaderandroid/MyUploaderPackage.kt +1 -1
  4. package/lib/commonjs/NativeModules.js +16 -0
  5. package/lib/commonjs/NativeModules.js.map +1 -0
  6. package/lib/commonjs/components/DownloadFile.js +37 -49
  7. package/lib/commonjs/components/DownloadFile.js.map +1 -1
  8. package/lib/commonjs/components/MyUploader.js +54 -80
  9. package/lib/commonjs/components/MyUploader.js.map +1 -1
  10. package/lib/commonjs/index.d.js.map +1 -1
  11. package/lib/commonjs/index.js +4 -4
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/commonjs/types.js.map +1 -1
  14. package/lib/module/NativeModules.js +10 -0
  15. package/lib/module/NativeModules.js.map +1 -0
  16. package/lib/module/components/DownloadFile.js +38 -50
  17. package/lib/module/components/DownloadFile.js.map +1 -1
  18. package/lib/module/components/MyUploader.js +52 -79
  19. package/lib/module/components/MyUploader.js.map +1 -1
  20. package/lib/module/index.d.js +6 -0
  21. package/lib/module/index.d.js.map +1 -1
  22. package/lib/module/index.js +7 -6
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/types.js.map +1 -1
  25. package/package.json +1 -1
  26. package/src/NativeModules.ts +9 -0
  27. package/src/components/DownloadFile.tsx +38 -62
  28. package/src/components/MyUploader.tsx +59 -90
  29. package/src/index.d.ts +13 -4
  30. package/src/index.ts +11 -6
  31. package/src/types.ts +96 -41
  32. package/android/src/main/java/com/myuploader/MyUploaderModule.kt +0 -173
  33. package/android/src/main/java/com/myuploader/MyUploaderPackage.kt +0 -16
  34. package/lib/commonjs/NativeMyUploader.js +0 -15
  35. package/lib/commonjs/NativeMyUploader.js.map +0 -1
  36. package/lib/module/NativeMyUploader.js +0 -9
  37. package/lib/module/NativeMyUploader.js.map +0 -1
  38. 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 com.myuploaderandroid.BuildConfig
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 = mutableMapOf<Long, Promise>()
24
+ private val downloadPromises = ConcurrentHashMap<Long, Promise>()
24
25
 
25
- override fun getName(): String = "DownloadFile"
26
-
27
- init {
28
- val receiver = object : BroadcastReceiver() {
29
- override fun onReceive(context: Context, intent: Intent) {
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
- reactContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
42
+ reactContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
41
43
  } else {
42
- reactContext.registerReceiver(receiver, intentFilter)
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 (shouldLog) Log.d(TAG, "İndirme isteği alındı. URL: $fileUrl")
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
- val connection = url.openConnection() as HttpURLConnection
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
- if (shouldLog) Log.d(TAG, "Dosya: $fileName, Boyut: $fileSize bytes, Tip: $cleanContentType")
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
- if (maxSizeMB > 0 && fileSize > maxSizeMB * 1024 * 1024) {
78
- promise.reject("E_FILE_TOO_LARGE", "Dosya boyutu (${(fileSize / 1024.0 / 1024.0).toInt()}MB) belirtilen limitten (${maxSizeMB}MB) büyük.")
79
- return@Thread
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(fileName)
84
- .setDescription("İndiriliyor...")
126
+ .setTitle(finalFileName)
127
+ .setDescription("Dosya indiriliyor...")
85
128
  .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
86
- .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
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.moveToFirst()) {
111
- val statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
112
- val status = cursor.getInt(statusIndex)
153
+ if (cursor == null) {
154
+ promise?.reject("E_DB_CURSOR_NULL", "Download cursor null")
155
+ return
156
+ }
113
157
 
114
- if (status == DownloadManager.STATUS_SUCCESSFUL) {
115
- val uriIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
116
- val localUri = cursor.getString(uriIndex)
117
- promise?.resolve(localUri)
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
- val reasonIndex = cursor.getColumnIndex(DownloadManager.COLUMN_REASON)
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
  }
@@ -14,7 +14,7 @@ class MyUploaderModule(private val reactContext: ReactApplicationContext) :
14
14
 
15
15
  private var pickerPromise: Promise? = null
16
16
  private var maxSize: Double = 0.0
17
- private var maxFiles: Int = 0
17
+ private var maxFiles: Int = 1
18
18
  private var excludedUris: List<String> = emptyList()
19
19
 
20
20
  init {
@@ -25,149 +25,133 @@ class MyUploaderModule(private val reactContext: ReactApplicationContext) :
25
25
 
26
26
  companion object {
27
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
28
  }
35
29
 
36
30
  @ReactMethod
37
31
  fun openDocument(options: ReadableMap, promise: Promise) {
38
32
  if (pickerPromise != null) {
39
- promise.reject(E_FAILED_TO_OPEN_DOCUMENT, "Başka bir dosya seçme işlemi zaten devam ediyor.")
33
+ promise.reject("E_BUSY", "Zaten açık bir işlem var.")
40
34
  return
41
35
  }
42
36
 
43
37
  this.pickerPromise = promise
44
38
 
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
39
+ // JS'den gelen opsiyonları al
40
+ val multipleFiles = if(options.hasKey("multipleFiles")) options.getBoolean("multipleFiles") else false
41
+ val jsMaxFiles = if(options.hasKey("maxFiles")) options.getInt("maxFiles") else 0
42
+ this.maxSize = if(options.hasKey("maxSize")) options.getDouble("maxSize") else 0.0
43
+ this.excludedUris = if(options.hasKey("excludedUris"))
44
+ options.getArray("excludedUris")?.toArrayList()?.mapNotNull { it.toString() } ?: emptyList()
45
+ else emptyList()
46
+
47
+ // maxFiles mantığı
48
+ this.maxFiles = when {
49
+ multipleFiles && jsMaxFiles == 0 -> 99 // Limit yoksa varsayılan
50
+ !multipleFiles -> 1
51
+ else -> jsMaxFiles
59
52
  }
60
53
 
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
54
+ val currentActivity = currentActivity
68
55
  if (currentActivity == null) {
69
- pickerPromise?.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity mevcut değil.")
56
+ promise.reject("E_NO_ACTIVITY", "Activity bulunamadı.")
70
57
  pickerPromise = null
71
58
  return
72
59
  }
73
60
 
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()
61
+ val fileTypes = if (options.hasKey("fileTypes"))
62
+ options.getArray("fileTypes")?.toArrayList()?.mapNotNull { it.toString() } ?: listOf("*/*")
63
+ else listOf("*/*")
64
+
65
+ val mimeTypes = fileTypes.toTypedArray()
77
66
 
78
67
  try {
79
- // ANAHTAR: ACTION_GET_CONTENT, güvenilir çoklu seçimi etkinleştirir.
80
68
  val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
81
- type = "*/*"
69
+ type = "*/*" // Filter by MIME types in extra
82
70
  addCategory(Intent.CATEGORY_OPENABLE)
83
71
  putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
84
-
85
- if (multipleFiles) {
86
- putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
87
- }
72
+ if (multipleFiles) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
88
73
  }
89
- currentActivity.startActivityForResult(Intent.createChooser(intent, "Dosya Seçin"), REQUEST_CODE)
74
+ currentActivity.startActivityForResult(Intent.createChooser(intent, "Dosya Seç"), REQUEST_CODE)
90
75
  } catch (e: Exception) {
91
- pickerPromise?.reject(E_FAILED_TO_OPEN_DOCUMENT, e)
76
+ pickerPromise?.reject("E_OPEN_FAILED", e)
92
77
  pickerPromise = null
93
78
  }
94
79
  }
95
80
 
96
81
  override fun onActivityResult(activity: Activity, requestCode: Int, resultCode: Int, data: Intent?) {
97
- if (requestCode != REQUEST_CODE || pickerPromise == null) { return }
82
+ if (requestCode != REQUEST_CODE || pickerPromise == null) return
98
83
 
99
- if (resultCode == Activity.RESULT_OK && data != null) {
100
- val allSelectedUris = mutableListOf<Uri>()
101
- data.clipData?.let { clip ->
84
+ if (resultCode == Activity.RESULT_OK) {
85
+ val uris = mutableListOf<Uri>()
86
+
87
+ if (data?.clipData != null) {
88
+ val clip = data.clipData!!
102
89
  for (i in 0 until clip.itemCount) {
103
- allSelectedUris.add(clip.getItemAt(i).uri)
90
+ uris.add(clip.getItemAt(i).uri)
104
91
  }
105
- } ?: data.data?.let { uri ->
106
- allSelectedUris.add(uri)
92
+ } else if (data?.data != null) {
93
+ uris.add(data.data!!)
107
94
  }
108
-
109
- val newSelectedUris = allSelectedUris.filter { uri -> !excludedUris.contains(uri.toString()) }
110
95
 
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.")
96
+ // Excluded URI Filtreleme
97
+ val filteredUris = uris.filter { !excludedUris.contains(it.toString()) }
98
+
99
+ if (filteredUris.size > maxFiles) {
100
+ pickerPromise?.reject("E_MAX_FILES", "Maksimum $maxFiles dosya seçebilirsiniz.")
113
101
  pickerPromise = null
114
102
  return
115
103
  }
116
104
 
117
105
  try {
118
- val fileInfoArray = processSelectedFiles(newSelectedUris)
119
- pickerPromise?.resolve(fileInfoArray)
106
+ val result = processSelectedFiles(filteredUris)
107
+ pickerPromise?.resolve(result)
120
108
  } catch (e: Exception) {
121
- pickerPromise?.reject(E_FILE_TOO_LARGE, e.message)
109
+ pickerPromise?.reject("E_PROCESS_FAILED", e.message)
122
110
  }
123
111
  } else {
124
- pickerPromise?.reject(E_PICKER_CANCELLED, "Dosya seçimi iptal edildi.")
112
+ pickerPromise?.reject("E_CANCELLED", "İptal edildi.")
125
113
  }
126
114
  pickerPromise = null
127
115
  }
128
116
 
129
- override fun onNewIntent(intent: Intent) {}
117
+ override fun onNewIntent(intent: Intent?) {}
130
118
 
131
119
  private fun processSelectedFiles(uris: List<Uri>): WritableArray {
132
- val fileObjects = WritableNativeArray()
120
+ val resultArr = WritableNativeArray()
133
121
  val maxSizeBytes = (maxSize * 1024 * 1024).toLong()
134
122
 
135
123
  for (uri in uris) {
136
124
  reactContext.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
137
125
  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.")
126
+ val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
127
+ val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
128
+
129
+ val size = if(sizeIndex >= 0) cursor.getLong(sizeIndex) else 0L
130
+ val name = if(nameIndex >= 0) cursor.getString(nameIndex) else "unknown"
131
+
132
+ if (maxSizeBytes > 0 && size > maxSizeBytes) {
133
+ throw Exception("Dosya ($name) boyutu çok büyük. Limit: ${maxSize}MB")
145
134
  }
146
135
 
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)
136
+ // Base64 okuma
137
+ val base64 = reactContext.contentResolver.openInputStream(uri)?.use { stream ->
138
+ val bytes = stream.readBytes()
139
+ Base64.encodeToString(bytes, Base64.NO_WRAP)
140
+ } ?: ""
141
+
142
+ val mimeType = reactContext.contentResolver.getType(uri) ?: "application/octet-stream"
143
+
144
+ val map = WritableNativeMap()
145
+ map.putString("uri", uri.toString())
146
+ map.putString("name", name)
147
+ map.putString("type", mimeType)
148
+ map.putDouble("size", size.toDouble())
149
+ map.putString("base64", base64)
150
+
151
+ resultArr.pushMap(map)
166
152
  }
167
153
  }
168
154
  }
169
- return fileObjects
155
+ return resultArr
170
156
  }
171
-
172
- private fun getMimeType(fileName: String): String? = URLConnection.guessContentTypeFromName(fileName)
173
157
  }
@@ -8,7 +8,7 @@ import com.facebook.react.uimanager.ViewManager
8
8
  class MyUploaderPackage : ReactPackage {
9
9
  override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
10
  return listOf(
11
- MyUploaderModule(reactContext)
11
+ MyUploaderModule(reactContext),
12
12
  DownloadFileModule(reactContext)
13
13
  )
14
14
  }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NativePicker = exports.NativeDownload = void 0;
7
+ var _reactNative = require("react-native");
8
+ const {
9
+ DownloadFile,
10
+ DocumentPicker
11
+ } = _reactNative.NativeModules;
12
+ if (!DownloadFile) console.warn("MyUploader: DownloadFile native module not found.");
13
+ if (!DocumentPicker) console.warn("MyUploader: DocumentPicker native module not found.");
14
+ const NativeDownload = exports.NativeDownload = DownloadFile;
15
+ const NativePicker = exports.NativePicker = DocumentPicker;
16
+ //# sourceMappingURL=NativeModules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","DownloadFile","DocumentPicker","NativeModules","console","warn","NativeDownload","exports","NativePicker"],"sources":["NativeModules.ts"],"sourcesContent":["import { NativeModules } from 'react-native';\r\n\r\nconst { DownloadFile, DocumentPicker } = NativeModules;\r\n\r\nif (!DownloadFile) console.warn(\"MyUploader: DownloadFile native module not found.\");\r\nif (!DocumentPicker) console.warn(\"MyUploader: DocumentPicker native module not found.\");\r\n\r\nexport const NativeDownload = DownloadFile;\r\nexport const NativePicker = DocumentPicker;"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAEA,MAAM;EAAEC,YAAY;EAAEC;AAAe,CAAC,GAAGC,0BAAa;AAEtD,IAAI,CAACF,YAAY,EAAEG,OAAO,CAACC,IAAI,CAAC,mDAAmD,CAAC;AACpF,IAAI,CAACH,cAAc,EAAEE,OAAO,CAACC,IAAI,CAAC,qDAAqD,CAAC;AAEjF,MAAMC,cAAc,GAAAC,OAAA,CAAAD,cAAA,GAAGL,YAAY;AACnC,MAAMO,YAAY,GAAAD,OAAA,CAAAC,YAAA,GAAGN,cAAc","ignoreList":[]}
@@ -6,13 +6,11 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
8
  var _reactNative = require("react-native");
9
+ var _NativeModules = require("../NativeModules");
9
10
  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
- const {
11
- DownloadFile: DownloadFileModule
12
- } = _reactNative.NativeModules;
13
11
  const DownloadFile = ({
14
12
  files,
15
- multipleLoad = false,
13
+ multipleDownload = false,
16
14
  disabled = false,
17
15
  debug = false,
18
16
  maxSize = 0,
@@ -21,82 +19,72 @@ const DownloadFile = ({
21
19
  buttonIcon,
22
20
  ButtonStyle,
23
21
  ButtonTextStyle,
24
- onSuccess = () => {},
25
- onError = error => console.error('DownloadFile Error:', error)
22
+ onSuccess,
23
+ onError
26
24
  }) => {
27
25
  const [isLoading, setIsLoading] = (0, _react.useState)(false);
28
26
  const handlePress = async () => {
29
- if (!DownloadFileModule) {
30
- onError(new Error("DownloadFile native module is not available."));
31
- return;
32
- }
27
+ if (disabled || isLoading || !files.length) return;
33
28
  setIsLoading(true);
34
- const downloadOptions = {
29
+ const options = {
30
+ debug,
35
31
  maxSize,
36
- fileTypes,
37
- debug
32
+ fileTypes
38
33
  };
34
+ const targetFiles = multipleDownload ? files : [files[0]];
39
35
  try {
40
- const filesToProcess = multipleLoad ? files : files.slice(0, 1);
41
- const downloadPromises = filesToProcess.map(url => DownloadFileModule.downloadFile(url, downloadOptions));
42
- const settledResults = await Promise.allSettled(downloadPromises);
43
- const finalResult = {
36
+ const promises = targetFiles.map(url => _NativeModules.NativeDownload.downloadFile(url, options));
37
+ const results = await Promise.allSettled(promises);
38
+ const final = {
44
39
  successful: [],
45
40
  skipped: []
46
41
  };
47
- settledResults.forEach((result, index) => {
48
- // DÜZELTME: `filesToProcess[index]` ifadesinin sonuna `!` ekleyerek
49
- // TypeScript'e bu değerin asla undefined olmayacağını bildiriyoruz.
50
- const originalUrl = filesToProcess[index];
51
- if (result.status === 'fulfilled') {
52
- finalResult.successful.push({
42
+ results.forEach((res, index) => {
43
+ var _targetFiles$index;
44
+ const originalUrl = (_targetFiles$index = targetFiles[index]) !== null && _targetFiles$index !== void 0 ? _targetFiles$index : "";
45
+ if (res.status === 'fulfilled') {
46
+ final.successful.push({
53
47
  originalUrl,
54
- // Artık hata vermeyecek
55
- localUri: result.value
48
+ localUri: res.value
56
49
  });
57
50
  } else {
58
- finalResult.skipped.push({
51
+ var _res$reason;
52
+ final.skipped.push({
59
53
  originalUrl,
60
- // Artık hata vermeyecek
61
- reason: result.reason.message
54
+ reason: ((_res$reason = res.reason) === null || _res$reason === void 0 ? void 0 : _res$reason.message) || "Bilinmeyen Hata"
62
55
  });
63
56
  }
64
57
  });
65
- onSuccess(finalResult);
66
- } catch (error) {
67
- onError(error);
58
+ if (onSuccess) onSuccess(final);
59
+ } catch (e) {
60
+ if (onError) onError(e);else console.error(e);
68
61
  } finally {
69
62
  setIsLoading(false);
70
63
  }
71
64
  };
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
65
  return /*#__PURE__*/_react.default.createElement(_reactNative.TouchableOpacity, {
78
- style: [styles.button, ButtonStyle, (disabled || isLoading) && styles.disabledButton],
66
+ style: [styles.button, ButtonStyle, (disabled || isLoading) && styles.disabled],
79
67
  onPress: handlePress,
80
68
  disabled: disabled || isLoading
81
- }, content);
69
+ }, isLoading ? /*#__PURE__*/_react.default.createElement(_reactNative.ActivityIndicator, {
70
+ color: "#FFF"
71
+ }) : buttonIcon ? buttonIcon : /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
72
+ style: [styles.text, ButtonTextStyle]
73
+ }, buttonPlaceHolder));
82
74
  };
83
75
  const styles = _reactNative.StyleSheet.create({
84
76
  button: {
85
- backgroundColor: '#008CBA',
86
- paddingHorizontal: 20,
87
- paddingVertical: 10,
77
+ backgroundColor: '#03DAC6',
78
+ padding: 12,
88
79
  borderRadius: 8,
89
- alignItems: 'center',
90
- justifyContent: 'center',
91
- flexDirection: 'row'
80
+ alignItems: 'center'
92
81
  },
93
- buttonText: {
94
- color: '#FFFFFF',
95
- fontSize: 16,
82
+ text: {
83
+ color: '#000',
96
84
  fontWeight: 'bold'
97
85
  },
98
- disabledButton: {
99
- backgroundColor: '#A9A9A9'
86
+ disabled: {
87
+ backgroundColor: '#AAA'
100
88
  }
101
89
  });
102
90
  var _default = exports.default = DownloadFile;