react-native-my-uploader-android 1.0.31 → 1.0.37

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 CHANGED
@@ -1,73 +1,270 @@
1
- # react-native-my-uploader
1
+ # React Native My Uploader
2
2
 
3
- file uploader for android
3
+ Android için basit ve esnek bir dosya seçici. Bu paket, hem kullanıma hazır bir buton bileşeni hem de kendi arayüzünüzü oluşturmanız için bir `Promise` tabanlı fonksiyon sunar.
4
4
 
5
- ## Installation
5
+ ## Özellikler
6
6
 
7
+ - **Tekli ve Çoklu Dosya Seçimi:** `multipleFiles` prop'u ile kolayca geçiş yapın.
8
+ - **Dosya Tipi Filtreleme:** Sadece belirli MIME tiplerindeki (`image/*`, `application/pdf` vb.) dosyaların seçilmesine izin verin.
9
+ - **Limit Kontrolleri:** Seçilebilecek maksimum dosya sayısı (`maxFiles`) ve her bir dosya için maksimum boyut (`maxSize`) belirleyin.
10
+ - **Hariç Tutma:** Daha önce seçilmiş dosyaların tekrar seçilmesini engelleyin (`excludedUris`).
11
+ - **Base64 Çıktısı:** Seçilen dosyaların `base64` formatında verisini döndürür.
12
+ - **İki Farklı Kullanım:** Hızlı entegrasyon için `<MyUploader />` bileşeni veya tam kontrol için `pickFile()` fonksiyonu.
13
+
14
+ ## Kurulum
7
15
 
8
16
  ```sh
9
- npm install react-native-my-uploader-android
17
+ npm install react-native-my-uploader-android //"version": "1.0.32"
10
18
  ```
11
19
 
12
- ## Usage
20
+ veya
13
21
 
22
+ ```sh
23
+ yarn add react-native-my-uploader-android //"version": "1.0.32"
24
+ ```
14
25
 
15
- ```js
16
- import MyUploader,{FileInfo} from 'react-native-my-uploader-android';
26
+ ## Kullanım
17
27
 
28
+ Bu paketi kullanmanın iki ana yolu vardır:
18
29
 
19
- const [selectedFiles, setSelectedFiles] = useState<FileInfo[]>([]); // response= base64,fileName,fileSize,fileType,FileUri
30
+ ### Yöntem 1: Hazır Buton `<MyUploader />` (Önerilen ve Kolay Yol)
20
31
 
21
- //for "react-native-my-uploader-android": "^1.0.25" version
22
- <MyUploader
23
- multipleFiles={false}
24
- // maxFiles={3}
25
- maxSize={5} // 5MB
26
- fileTypes={["*/*"]}
27
- buttonPlaceHolder="Dosya Seç (En Fazla 3)"
28
- ButtonStyle={styles.customButton}
29
- ButtonTextStyle={styles.customButtonText}
30
- onSelect={(files) => {
31
- console.log('Dosyalar seçildi:', files);
32
- // Yeni seçilenleri mevcut listeye ekle
33
- setSelectedFiles(prevFiles => [...prevFiles, ...files]);
34
- }}
35
- onError={(error) => {
36
- Alert.alert(`Bir hata oluştu: ${error.message}`);
37
- }}
38
- />
32
+ Hızlıca bir "Dosya Seç" butonu eklemek için idealdir. Tüm mantık bileşenin kendi içindedir.
39
33
 
40
- <MyUploader
41
- multipleFiles={true}
42
- maxFiles={3}
43
- maxSize={5} // 5MB
44
- fileTypes={['application/pdf', 'image/jpeg']}
45
- buttonPlaceHolder="Dosya Seç (En Fazla 3)"
34
+ ```jsx
35
+ import React,{useState} from 'react';
36
+ import { Text,View, Alert, StyleSheet } from 'react-native';
37
+ import MyUploader, { FileInfo } from 'react-native-my-uploader-android';
38
+
39
+ export default function App() {
40
+
41
+ const [files,setFiles]=useState<FileInfo[] |null>(null);
42
+
43
+ const handleFilesSelected = (files: FileInfo[]) => {
44
+ console.log('Seçilen Dosyalar:', files);
45
+ setFiles(prev => prev ? [...prev, ...files] : [...files]);
46
+ };
47
+
48
+ return (
49
+ <View style={styles.container}>
50
+ <MyUploader
51
+ onSelect={handleFilesSelected}
52
+ onError={(error)=>{ Alert.alert('Bir Hata Oluştu', error.message)}}
53
+ buttonPlaceHolder="En Fazla 3 Resim Seç (Max 2MB)"
46
54
  ButtonStyle={styles.customButton}
47
55
  ButtonTextStyle={styles.customButtonText}
48
- onSelect={(files) => {
49
- console.log('Dosyalar seçildi:', files);
50
- // Yeni seçilenleri mevcut listeye ekle
51
- setSelectedFiles(prevFiles => [...prevFiles, ...files]);
52
- }}
53
- onError={(error) => {
54
- Alert.alert(`Bir hata oluştu: ${error.message}`);
55
- }}
56
+ multipleFiles={true}
57
+ fileTypes={['image/*']}
58
+ maxSize={2}
59
+ maxFiles={3}
56
60
  />
57
61
 
62
+ {files ? (
63
+ <View style={styles.fileListContainer}>
64
+ <Text style={styles.fileListTitle}>Seçilen Dosyalar:</Text>
65
+
66
+ {files.map((file, index) => (
67
+ <Text key={index} style={styles.fileName}>
68
+ {file.fileName} ({file.fileSize} KB)
69
+ </Text>
70
+ ))}
71
+ </View>
72
+ ) : (
73
+ <View style={styles.fileListContainer}>
74
+ <Text>Dosya Bulunmamaktadır</Text>
75
+ </View>
76
+ )}
77
+ </View>
78
+ );
79
+ }
80
+
81
+ const styles = StyleSheet.create({
82
+ container: {
83
+ flex: 1,
84
+ justifyContent: 'center',
85
+ alignItems: 'center',
86
+ padding: 20,
87
+ },
88
+ customButton: {
89
+ backgroundColor: '#00796B',
90
+ paddingVertical: 15,
91
+ paddingHorizontal: 30,
92
+ borderRadius: 30,
93
+ },
94
+ customButtonText: {
95
+ color: '#FFFFFF',
96
+ fontSize: 14,
97
+ },
98
+ fileListContainer: {
99
+ marginTop: 20,
100
+ padding: 15,
101
+ backgroundColor: '#f0f0f0',
102
+ borderRadius: 8,
103
+ width: '100%',
104
+ },
105
+ fileListTitle: {
106
+ fontSize: 16,
107
+ fontWeight: 'bold',
108
+ marginBottom: 10,
109
+ },
110
+ fileName: {
111
+ fontSize: 14,
112
+ color: '#333',
113
+ marginBottom: 5,
114
+ },
115
+ });
116
+
117
+ ```
118
+
119
+ ### Yöntem 2: Fonksiyon `pickFile()` (Tam Kontrol İçin)
120
+
121
+ Kendi özel butonunuzu veya dokunma alanınızı tasarlamak istediğinizde bu yöntemi kullanın. `async/await` ile çalışır ve size tam kontrol sağlar.
122
+
123
+ ```jsx
124
+ import React,{useState} from 'react';
125
+ import { Text,View, Alert, StyleSheet ,TouchableOpacity} from 'react-native';
126
+ import { pickFile ,FileInfo} from 'react-native-my-uploader-android';
127
+
128
+ export default function App() {
129
+ const [pickFiles,setPickFiles]=useState<FileInfo[] |null>(null)
130
+
131
+ const handlePress = async () => {
132
+ try {
133
+ // Dosya seçiciyi aç ve sonucu bekle
134
+ const files = await pickFile({
135
+ multipleFiles: true,
136
+ fileTypes: ['application/pdf', 'application/vnd.ms-excel'], // Sadece PDF ve Excel
137
+ maxFiles: 5,
138
+ maxSize: 10, // 10MB
139
+ });
140
+ setPickFiles(prev => prev ? [...prev, ...files] : [...files]);
141
+ console.log(files);
142
+
143
+ } catch (error: any) {
144
+ Alert.alert('Hata', error.message);
145
+ }
146
+ };
147
+
148
+ return (
149
+ <View style={styles.container}>
150
+ <TouchableOpacity style={styles.fancyButton} onPress={handlePress}>
151
+ <Text style={styles.fancyButtonText}>PDF veya Excel Yükle</Text>
152
+ </TouchableOpacity>
153
+ {pickFiles ?(
154
+ <View style={styles.fileListContainer}>
155
+ <Text style={styles.fileListTitle}>Seçilen Dosyalar:</Text>
156
+ {pickFiles.map((file, index) => (
157
+ <View key={index}>
158
+ <Text style={styles.fileName}>{file.fileName} ({file.fileSize} KB)</Text>
159
+ </View>
160
+ ))}
161
+ </View>
162
+ ):(
163
+ <View>
164
+ <Text>Dosya bulunmamaktadır.</Text>
165
+ </View>
166
+ )};
167
+ </View>
168
+ )
169
+ }
170
+
171
+ const styles = StyleSheet.create({
172
+ container: {
173
+ flex: 1,
174
+ justifyContent: 'center',
175
+ alignItems: 'center',
176
+ padding: 20,
177
+ },
178
+ customButton: {
179
+ backgroundColor: '#00796B',
180
+ paddingVertical: 15,
181
+ paddingHorizontal: 30,
182
+ borderRadius: 30,
183
+ },
184
+ customButtonText: {
185
+ color: '#FFFFFF',
186
+ fontSize: 14,
187
+ },
188
+ fileListContainer: {
189
+ marginTop: 20,
190
+ padding: 15,
191
+ backgroundColor: '#f0f0f0',
192
+ borderRadius: 8,
193
+ width: '100%',
194
+ },
195
+ fileListTitle: {
196
+ fontSize: 16,
197
+ fontWeight: 'bold',
198
+ marginBottom: 10,
199
+ },
200
+ fileName: {
201
+ fontSize: 14,
202
+ color: '#333',
203
+ marginBottom: 15,
204
+ borderBottomWidth:1,
205
+ paddingBottom:10
206
+ },
207
+ fancyButton: {
208
+ borderWidth: 2,
209
+ borderColor: '#6200EE',
210
+ padding: 15,
211
+ borderRadius: 10,
212
+ },
213
+ fancyButtonText: {
214
+ color: '#6200EE',
215
+ fontWeight: 'bold',
216
+ fontSize: 16,
217
+ },
218
+ });
219
+
58
220
  ```
59
221
 
222
+ ## Prop'lar
223
+
224
+ ### `<MyUploader />` & `pickFile()` Ortak Prop'ları (`DocumentPickerOptions`)
225
+
226
+ | Prop | Tip | Varsayılan | Açıklama |
227
+ |----------------|-------------|------------|-------------------------------------------------------------------------|
228
+ | `multipleFiles`| `boolean` | `false` | `true` ise birden fazla dosya seçimine izin verir. |
229
+ | `maxFiles` | `number` | `3` | `multipleFiles` `true` iken seçilebilecek maksimum dosya sayısı. `0` ise limitsiz. |
230
+ | `maxSize` | `number` | `0` | Her bir dosya için MB cinsinden maksimum boyut. `0` ise limitsiz. |
231
+ | `fileTypes` | `string[]` | `['*/*']` | Seçilebilecek dosya tipleri (MIME). Örn: `['image/jpeg', 'image/png']`. |
232
+ | `excludedUris` | `string[]` | `[]` | Seçim sonuçlarından hariç tutulacak dosyaların `fileUri` listesi. |
233
+
234
+
235
+ ### `<MyUploader />` Özel Prop'ları
236
+
237
+ | Prop | Tip | Varsayılan | Açıklama |
238
+ |---------------------|-----------------------|-------------|----------------------------------------|
239
+ | `onSelect` | `(files: FileInfo[]) => void` | **Zorunlu** | Dosyalar başarıyla seçildiğinde tetiklenir. |
240
+ | `onError` | `(error: Error) => void` | `undefined` | Bir hata oluştuğunda tetiklenir. |
241
+ | `buttonPlaceHolder` | `string` | `"Dosya Seç"`| Buton üzerinde görünecek metin. |
242
+ | `ButtonStyle` | `ViewStyle` | `undefined` | Butonun `TouchableOpacity` stili. |
243
+ | `ButtonTextStyle` | `TextStyle` | `undefined` | Buton metninin `Text` stili. |
244
+ | `disabled` | `boolean` | `false` | `true` ise buton devre dışı kalır. |
245
+
246
+ ## Dönen Değer (`FileInfo` objesi)
247
+
248
+ Her iki yöntem de `FileInfo` objelerinden oluşan bir dizi döndürür:
249
+
250
+ ```typescript
251
+ interface FileInfo {
252
+ fileName: string; // Dosyanın adı
253
+ fileSize: number; // Dosyanın boyutu (byte cinsinden)
254
+ fileType: string; // Dosyanın MIME tipi
255
+ fileUri: string; // Dosyanın cihazdaki URI'si (Content URI)
256
+ base64: string | null; // Dosyanın Base64 kodlanmış verisi
257
+ }
258
+ ```
60
259
 
61
- ## Contributing
260
+ ## Katkıda Bulunma (Contributing)
62
261
 
63
- - [Development workflow](CONTRIBUTING.md#development-workflow)
64
- - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
65
- - [Code of conduct](CODE_OF_CONDUCT.md)
262
+ Katkılarınız için her zaman açığız! Lütfen [CONTRIBUTING.md](CONTRIBUTING.md) dosyasını inceleyin.
66
263
 
67
- ## License
264
+ ## Lisans
68
265
 
69
266
  MIT
70
267
 
71
268
  ---
72
269
 
73
- Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
270
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -1,177 +1,201 @@
1
1
  package com.myuploaderandroid
2
-
3
- import android.app.DownloadManager
4
- import android.content.BroadcastReceiver
5
- import android.content.Context
6
- import android.content.Intent
7
- import android.content.IntentFilter
8
- import android.net.Uri
2
+ import android.content.ContentValues
9
3
  import android.os.Build
10
4
  import android.os.Environment
11
- import android.util.Log
12
- import android.webkit.CookieManager
13
- import android.webkit.URLUtil
5
+ import android.provider.MediaStore
6
+ import android.webkit.MimeTypeMap
14
7
  import com.facebook.react.bridge.*
8
+ import java.io.BufferedInputStream
15
9
  import java.io.File
10
+ import java.io.FileOutputStream
11
+ import java.io.OutputStream
16
12
  import java.net.HttpURLConnection
17
13
  import java.net.URL
18
- import java.util.concurrent.ConcurrentHashMap
19
-
20
- class DownloadFileModule(private val reactContext: ReactApplicationContext) :
21
- ReactContextBaseJavaModule(reactContext) {
22
-
23
- private val TAG = "DownloadFileModule"
24
- private val downloadPromises = ConcurrentHashMap<Long, Promise>()
25
-
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 ->
31
- checkDownloadStatus(downloadId, promise)
32
- }
14
+ import java.net.URLDecoder
15
+ import java.util.Locale
16
+ class DownloadFileModule ( reactContext: ReactApplicationContext ) : ReactContextBaseJavaModule(reactContext) {
17
+
18
+ override fun getName(): String = "DownloadFileModule"
19
+
20
+ @ReactMethod
21
+ fun downloadFile(urlStr: String, options: ReadableMap, promise: Promise) {
22
+ Thread {
23
+ var connection: HttpURLConnection? = null
24
+ var inputStream: BufferedInputStream? = null
25
+ var outputStream: OutputStream? = null
26
+ var finished = false
27
+
28
+ fun rejectOnce(code: String, message: String) {
29
+ if (!finished) {
30
+ finished = true
31
+ promise.reject(code, message)
33
32
  }
34
33
  }
35
- }
36
-
37
- override fun getName(): String = "DownloadFile"
38
34
 
39
- init {
40
- val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
41
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
42
- reactContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
43
- } else {
44
- reactContext.registerReceiver(receiver, intentFilter)
35
+ fun resolveOnce(value: String) {
36
+ if (!finished) {
37
+ finished = true
38
+ promise.resolve(value)
39
+ }
45
40
  }
46
- }
47
41
 
48
- override fun onCatalystInstanceDestroy() {
49
42
  try {
50
- reactContext.unregisterReceiver(receiver)
51
- } catch (e: Exception) {
52
- Log.w(TAG, "Receiver unregister warning: ${e.message}")
53
- }
54
- super.onCatalystInstanceDestroy()
55
- }
56
-
57
- @ReactMethod
58
- fun downloadFile(fileUrl: String, options: ReadableMap, promise: Promise) {
59
- val isDebug = if (options.hasKey("debug")) options.getBoolean("debug") else false
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()
66
-
67
- if (isDebug) Log.d(TAG, "İndirme başlatılıyor: $fileUrl")
43
+ // --- OPTIONS ---
44
+ val maxSizeMB = if (options.hasKey("maxSize")) options.getInt("maxSize") else 0
45
+ val fileTypes = if (options.hasKey("fileTypes")) options.getArray("fileTypes") else null
46
+
47
+ // --- CONNECTION ---
48
+ val url = URL(urlStr)
49
+ connection = url.openConnection() as HttpURLConnection
50
+ connection.requestMethod = "GET"
51
+ connection.connect()
52
+
53
+ // --- HTTP STATUS CHECK ---
54
+ if (connection.responseCode !in 200..299) {
55
+ connection.disconnect()
56
+ rejectOnce(
57
+ "ERR_HTTP",
58
+ "Sunucu hatası: ${connection.responseCode}"
59
+ )
60
+ return@Thread
61
+ }
68
62
 
69
- Thread {
70
- var connection: HttpURLConnection? = null
71
- try {
72
- val url = URL(fileUrl)
73
- connection = url.openConnection() as HttpURLConnection
74
- val cookie = CookieManager.getInstance().getCookie(fileUrl)
75
- if (cookie != null) connection.setRequestProperty("Cookie", cookie)
76
- connection.requestMethod = "HEAD" // Sadece başlıkları çek
77
- connection.connect()
78
-
79
- val responseCode = connection.responseCode
80
- if (responseCode !in 200..299) {
81
- promise.reject("E_HTTP_ERROR", "Sunucu hatası: $responseCode")
63
+ // --- SIZE CHECK ---
64
+ val contentLength = connection.contentLengthLong
65
+ if (maxSizeMB > 0 && contentLength > 0) {
66
+ val maxBytes = maxSizeMB * 1024 * 1024
67
+ if (contentLength > maxBytes) {
68
+ connection.disconnect()
69
+ rejectOnce(
70
+ "ERR_SIZE_LIMIT",
71
+ "Dosya boyutu ($maxSizeMB MB) sınırını aşıyor."
72
+ )
82
73
  return@Thread
83
74
  }
75
+ }
84
76
 
85
- val fileSize = connection.contentLengthLong
86
- val contentType = connection.contentType
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
- }
77
+ // --- TYPE CHECK ---
78
+ val contentType = connection.contentType ?: ""
79
+
80
+ if (fileTypes != null && fileTypes.size() > 0) {
81
+ val allowedTypes = HashSet<String>()
82
+ for (i in 0 until fileTypes.size()) {
83
+ val type = fileTypes.getString(i)
84
+ if (type != null) {
85
+ // 🔥 DÜZELTME BURADA: typesList -> allowedTypes yapıldı
86
+ allowedTypes.add(type.lowercase(Locale.ROOT))
97
87
  }
98
- if (!isAllowed) {
99
- promise.reject("E_INVALID_FILE_TYPE", "Dosya türü ($cleanContentType) izin verilenler listesinde yok.")
100
- return@Thread
88
+ }
89
+
90
+ val isAllowed = when {
91
+ allowedTypes.contains("*/*") -> true
92
+ contentType.isEmpty() -> false
93
+ else -> allowedTypes.any {
94
+ contentType.lowercase(Locale.ROOT).contains(it)
101
95
  }
102
96
  }
103
97
 
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.")
98
+ if (!isAllowed) {
99
+ connection.disconnect()
100
+ val displayType = if (contentType.isNotEmpty()) contentType else "Bilinmeyen"
101
+ rejectOnce(
102
+ "ERR_TYPE_MISMATCH",
103
+ "Dosya tipi ($displayType) izin verilenler listesinde yok."
104
+ )
107
105
  return@Thread
108
106
  }
107
+ }
109
108
 
110
- val contentDisposition = connection.getHeaderField("Content-Disposition")
111
- val fileName = URLUtil.guessFileName(fileUrl, contentDisposition, cleanContentType)
109
+ // --- FILE NAME ---
110
+ val fileName = getFileNameFromUrl(urlStr, contentType)
112
111
 
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
119
-
120
- while (File(downloadsDir, finalFileName).exists()) {
121
- finalFileName = if (ext.isNotEmpty()) "$baseName($counter).$ext" else "$baseName($counter)"
122
- counter++
112
+ // --- OPEN STREAM ---
113
+ inputStream = BufferedInputStream(connection.inputStream)
114
+ var finalUri = ""
115
+
116
+ // --- SAVE FILE ---
117
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
118
+ val resolver = reactApplicationContext.contentResolver
119
+ val values = ContentValues().apply {
120
+ put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
121
+ put(MediaStore.MediaColumns.MIME_TYPE, contentType)
122
+ put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
123
123
  }
124
124
 
125
- val request = DownloadManager.Request(Uri.parse(fileUrl))
126
- .setTitle(finalFileName)
127
- .setDescription("Dosya indiriliyor...")
128
- .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
129
- .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, finalFileName)
130
- .setAllowedOverMetered(true)
125
+ val uri = resolver.insert(
126
+ MediaStore.Downloads.EXTERNAL_CONTENT_URI,
127
+ values
128
+ ) ?: throw Exception("MediaStore dosya oluşturulamadı")
131
129
 
132
- if (cookie != null) request.addRequestHeader("Cookie", cookie)
133
- if (cleanContentType != "*/*") request.setMimeType(cleanContentType)
130
+ outputStream = resolver.openOutputStream(uri)
131
+ finalUri = uri.toString()
132
+ } else {
133
+ @Suppress("DEPRECATION")
134
+ val dir = Environment.getExternalStoragePublicDirectory(
135
+ Environment.DIRECTORY_DOWNLOADS
136
+ )
137
+ if (!dir.exists()) dir.mkdirs()
138
+
139
+ var file = File(dir, fileName)
140
+ var index = 1
141
+ val base = fileName.substringBeforeLast(".")
142
+ val ext = fileName.substringAfterLast(".", "")
134
143
 
135
- val downloadManager = reactContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
136
- val downloadId = downloadManager.enqueue(request)
144
+ while (file.exists()) {
145
+ val name = if (ext.isNotEmpty()) "$base($index).$ext" else "$base($index)"
146
+ file = File(dir, name)
147
+ index++
148
+ }
137
149
 
138
- downloadPromises[downloadId] = promise
150
+ outputStream = FileOutputStream(file)
151
+ finalUri = file.absolutePath
152
+ }
139
153
 
140
- } catch (e: Exception) {
141
- promise.reject("E_DOWNLOAD_SETUP_FAILED", e.message)
142
- } finally {
143
- connection?.disconnect()
154
+ // --- WRITE ---
155
+ val buffer = ByteArray(4096)
156
+ var count: Int
157
+ while (inputStream.read(buffer).also { count = it } != -1) {
158
+ outputStream?.write(buffer, 0, count)
144
159
  }
145
- }.start()
146
- }
147
160
 
148
- private fun checkDownloadStatus(id: Long, promise: Promise?) {
149
- val downloadManager = reactContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
150
- val query = DownloadManager.Query().setFilterById(id)
151
- val cursor = downloadManager.query(query)
161
+ outputStream?.flush()
162
+ resolveOnce(finalUri)
152
163
 
153
- if (cursor == null) {
154
- promise?.reject("E_DB_CURSOR_NULL", "Download cursor null")
155
- return
164
+ } catch (e: Exception) {
165
+ rejectOnce(
166
+ "ERR_DOWNLOAD",
167
+ e.message ?: "İndirme sırasında hata oluştu"
168
+ )
169
+ } finally {
170
+ try {
171
+ outputStream?.close()
172
+ inputStream?.close()
173
+ connection?.disconnect()
174
+ } catch (_: Exception) {
175
+ }
156
176
  }
177
+ }.start()
178
+ }
179
+
180
+ private fun getFileNameFromUrl(url: String, contentType: String?): String {
181
+ var name = try {
182
+ URLDecoder.decode(
183
+ URL(url).path.substringAfterLast('/'),
184
+ "UTF-8"
185
+ )
186
+ } catch (e: Exception) {
187
+ "downloaded_file"
188
+ }
157
189
 
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
- }
172
- } else {
173
- promise?.reject("E_DOWNLOAD_NOT_FOUND", "İndirme kaydı bulunamadı.")
174
- }
190
+ if (name.isBlank()) name = "downloaded_file"
191
+
192
+ if (!name.contains(".") && !contentType.isNullOrEmpty()) {
193
+ val ext = MimeTypeMap.getSingleton()
194
+ .getExtensionFromMimeType(contentType)
195
+ if (ext != null) {
196
+ name += ".$ext"
175
197
  }
176
198
  }
199
+ return name
200
+ }
177
201
  }