react-native-my-uploader-android 1.0.54 → 1.0.56

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.
@@ -1,4 +1,4 @@
1
- package com.myuploaderandroid
1
+ package com.myuploaderandroid
2
2
 
3
3
  import androidx.exifinterface.media.ExifInterface
4
4
  import java.io.File
@@ -44,6 +44,37 @@ class MyUploaderModule(private val reactContext: ReactApplicationContext) :
44
44
  private const val E_INVALID_OPTIONS = "E_INVALID_OPTIONS"
45
45
  }
46
46
 
47
+ private fun getWorkFile(rawUri: String): File {
48
+ val uri = Uri.parse(rawUri)
49
+ val scheme = uri.scheme?.lowercase() ?: ""
50
+ val tempFile = File.createTempFile("work_process_", ".tmp", reactContext.cacheDir)
51
+
52
+ try {
53
+ if (scheme == "http" || scheme == "https") {
54
+ // Uzak URL'den indir
55
+ val url = java.net.URL(rawUri)
56
+ val connection = url.openConnection() as java.net.HttpURLConnection
57
+ connection.connectTimeout = 15000
58
+ connection.readTimeout = 20000
59
+ connection.inputStream.use { input ->
60
+ tempFile.outputStream().use { output -> input.copyTo(output) }
61
+ }
62
+ connection.disconnect()
63
+ } else {
64
+ // Yerel URI'den (content:// veya file://) kopyala
65
+ reactContext.contentResolver.openInputStream(uri).use { input ->
66
+ if (input == null) throw Exception("Dosya açılamadı: $rawUri")
67
+ tempFile.outputStream().use { output -> input.copyTo(output) }
68
+ }
69
+ }
70
+ } catch (e: Exception) {
71
+ tempFile.delete()
72
+ throw e
73
+ }
74
+ return tempFile
75
+ }
76
+
77
+
47
78
  @ReactMethod
48
79
  fun openDocument(options: ReadableMap, promise: Promise) {
49
80
  if (pickerPromise != null) {
@@ -212,90 +243,51 @@ class MyUploaderModule(private val reactContext: ReactApplicationContext) :
212
243
  }
213
244
 
214
245
  @ReactMethod
215
- fun RotateImageWithUri(fileUri: String, angle: Double, promise: Promise) {
216
- Thread {
217
- try {
218
- val sourceUri = Uri.parse(fileUri)
219
- val contentResolver = reactContext.contentResolver
220
-
221
- // 1. ADIM: OOM Riskini Azaltmak için Bitmap'i verimli bir şekilde oku
222
- // (Bu kısım, cropImageWithUri'deki ile aynı mantığı kullanır)
223
- val boundsOptions = BitmapFactory.Options().apply { inJustDecodeBounds = true }
224
- contentResolver.openInputStream(sourceUri)?.use {
225
- BitmapFactory.decodeStream(it, null, boundsOptions)
226
- }
227
-
228
- val decodeOptions = BitmapFactory.Options()
229
- var inSampleSize = 1
230
- val maxDimension = 2048 // Uygulamanızın ihtiyacına göre ayarlayın
231
- if (boundsOptions.outHeight > maxDimension || boundsOptions.outWidth > maxDimension) {
232
- val halfHeight = boundsOptions.outHeight / 2
233
- val halfWidth = boundsOptions.outWidth / 2
234
- while ((halfHeight / inSampleSize) >= maxDimension && (halfWidth / inSampleSize) >= maxDimension) {
235
- inSampleSize *= 2
236
- }
237
- }
238
- decodeOptions.inSampleSize = inSampleSize
239
-
240
- // Resmi optimize edilmiş boyutta belleğe yükle
241
- val originalBitmap = contentResolver.openInputStream(sourceUri)?.use {
242
- BitmapFactory.decodeStream(it, null, decodeOptions)
243
- } ?: throw Exception("Bitmap oluşturulamadı (decodeStream).")
244
-
245
-
246
- // 2. ADIM: Döndürme Matrisini Oluştur
247
- // NOT: Bu fonksiyonda, JS'den gelen 'angle' değerini doğrudan kullanıyoruz.
248
- // EXIF döndürmesi zaten orijinal resimde mevcuttur ve burada tekrar uygulanmamalıdır,
249
- // çünkü amacımız kullanıcının isteği doğrultusunda ek bir döndürme yapmaktır.
250
- val matrix = Matrix().apply {
251
- postRotate(angle.toFloat())
252
- }
253
-
254
- // Döndürme işlemini uygula
255
- val rotatedBitmap = Bitmap.createBitmap(
256
- originalBitmap, 0, 0, originalBitmap.width, originalBitmap.height, matrix, true
257
- )
258
-
259
-
260
- // 3. ADIM: Döndürülmüş Bitmap'i geçici bir dosyaya kaydet
261
- // (Bu kısım, cropImageWithUri'deki ile tamamen aynı)
262
- val mimeType = contentResolver.getType(sourceUri) ?: "image/jpeg"
263
- val fileExtension = when {
264
- mimeType.contains("png") -> "png"
265
- mimeType.contains("webp") -> "webp"
266
- else -> "jpg"
267
- }
268
-
269
- val cacheDir = reactContext.cacheDir
270
- val outputFile = createTempFile("rotated_", ".$fileExtension", cacheDir)
271
- val fileOutputStream = outputFile.outputStream()
246
+ fun RotateImageWithUri(fileUri: String, angle: Double, promise: Promise) {
247
+ Thread {
248
+ var workFile: File? = null
249
+ try {
250
+ // 1. Dosyayı yerel bir kopyaya al (İster URL olsun ister Local)
251
+ workFile = getWorkFile(fileUri)
272
252
 
273
- val compressFormat = when (fileExtension) {
274
- "png" -> Bitmap.CompressFormat.PNG
275
- "webp" -> if (android.os.Build.VERSION.SDK_INT >= 30) Bitmap.CompressFormat.WEBP_LOSSLESS else Bitmap.CompressFormat.WEBP
276
- else -> Bitmap.CompressFormat.JPEG
277
- }
253
+ // 2. Boyutları oku
254
+ val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
255
+ workFile.inputStream().use { BitmapFactory.decodeStream(it, null, options) }
256
+
257
+ // 3. OOM önlemi
258
+ val decodeOptions = BitmapFactory.Options().apply {
259
+ inSampleSize = calculateInSampleSize(options, 2048, 2048)
260
+ }
278
261
 
279
- fileOutputStream.use { stream ->
280
- rotatedBitmap.compress(compressFormat, 95, stream)
281
- }
262
+ // 4. Bitmap'i yükle
263
+ val originalBitmap = workFile.inputStream().use {
264
+ BitmapFactory.decodeStream(it, null, decodeOptions)
265
+ } ?: throw Exception("Bitmap decode edilemedi.")
282
266
 
267
+ // 5. Döndür
268
+ val matrix = Matrix().apply { postRotate(angle.toFloat()) }
269
+ val rotatedBitmap = Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.width, originalBitmap.height, matrix, true)
283
270
 
284
- // 4. ADIM: Belleği temizle
285
- originalBitmap.recycle()
286
- rotatedBitmap.recycle()
271
+ // 6. Kaydet
272
+ val outputFile = File.createTempFile("rotated_", ".jpg", reactContext.cacheDir)
273
+ outputFile.outputStream().use {
274
+ rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 95, it)
275
+ }
287
276
 
277
+ // Bellek temizliği
278
+ originalBitmap.recycle()
279
+ rotatedBitmap.recycle()
288
280
 
289
- // 5. ADIM: Yeni oluşturulan dosyanın URI'sini döndür
290
- promise.resolve(Uri.fromFile(outputFile).toString())
281
+ promise.resolve(Uri.fromFile(outputFile).toString())
291
282
 
292
- } catch (e: OutOfMemoryError) {
293
- promise.reject("E_OOM_ROTATE", "Resim döndürülürken hafıza yetersiz kaldı.")
294
- } catch (e: Exception) {
295
- promise.reject("E_ROTATE_URI", "Döndürme hatası: ${e.message}")
296
- }
297
- }.start()
298
- }
283
+ } catch (e: Exception) {
284
+ promise.reject("E_ROTATE_URI", e.message)
285
+ } finally {
286
+ // KRİTİK: İndirilen/kopyalanan geçici dosyayı SİL
287
+ workFile?.delete()
288
+ }
289
+ }.start()
290
+ }
299
291
 
300
292
 
301
293
  @ReactMethod
@@ -393,139 +385,80 @@ class MyUploaderModule(private val reactContext: ReactApplicationContext) :
393
385
  }
394
386
 
395
387
  @ReactMethod
396
- fun CropImageWithUri(fileUri: String, crop: ReadableMap, promise: Promise) {
397
- Thread {
398
- var originalBitmap: Bitmap? = null
399
- var rotatedBitmap: Bitmap? = null
400
- var croppedBitmap: Bitmap? = null
401
-
402
- try {
403
- val sourceUri = Uri.parse(fileUri)
404
- val contentResolver = reactContext.contentResolver
405
-
406
- // --- 1. İYİLEŞTİRME: EXIF Döndürme Bilgisini Oku ---
407
- var orientation = 0
408
- try {
409
- // InputStream'i EXIF okumak için bir kere açıyoruz
410
- contentResolver.openInputStream(sourceUri)?.use { inputStream ->
411
- val exif = ExifInterface(inputStream)
412
- orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
413
- }
414
- } catch (e: Exception) {
415
- // EXIF okunamıyorsa (örn: PNG), görmezden gel.
416
- println("EXIF bilgisi okunamadı: ${e.message}")
417
- }
418
-
419
- // --- 2. İYİLEŞTİRME: OOM Riskini Azaltmak için BitmapFactory.Options ---
420
- // Önce, sadece resmin boyutlarını almak için `inJustDecodeBounds` kullanıyoruz.
421
- // Bu, resmi belleğe yüklemeden boyutlarını okumamızı sağlar.
422
- val boundsOptions = BitmapFactory.Options()
423
- boundsOptions.inJustDecodeBounds = true
424
- contentResolver.openInputStream(sourceUri)?.use { inputStream ->
425
- BitmapFactory.decodeStream(inputStream, null, boundsOptions)
426
- }
388
+ fun CropImageWithUri(fileUri: String, crop: ReadableMap, promise: Promise) {
389
+ Thread {
390
+ var workFile: File? = null
391
+ try {
392
+ // 1. Dosyayı yerele al
393
+ workFile = getWorkFile(fileUri)
427
394
 
428
- val originalWidth = boundsOptions.outWidth
429
- val originalHeight = boundsOptions.outHeight
430
-
431
- // Bellek optimizasyonu için bir `inSampleSize` hesaplayalım.
432
- // Örneğin, resim 2048 pikselden büyükse, onu küçülterek oku.
433
- val decodeOptions = BitmapFactory.Options()
434
- var inSampleSize = 1
435
- val maxDimension = 2048 // Uygulamanızın ihtiyacına göre bu değeri ayarlayın
436
- if (originalHeight > maxDimension || originalWidth > maxDimension) {
437
- val halfHeight: Int = originalHeight / 2
438
- val halfWidth: Int = originalWidth / 2
439
- while (halfHeight / inSampleSize >= maxDimension && halfWidth / inSampleSize >= maxDimension) {
440
- inSampleSize *= 2
441
- }
442
- }
443
- decodeOptions.inSampleSize = inSampleSize
444
-
445
- // Şimdi resmi, hesaplanan `inSampleSize` ile belleğe yüklüyoruz.
446
- val originalBitmap = contentResolver.openInputStream(sourceUri)?.use { inputStream ->
447
- BitmapFactory.decodeStream(inputStream, null, decodeOptions)
448
- } ?: throw Exception("Bitmap oluşturulamadı (decodeStream).")
449
-
450
-
451
- // --- 3. İYİLEŞTİRME: Döndürme Matrisini Oluştur ---
452
- val matrix = Matrix()
453
- // EXIF bilgisine göre döndürme matrisini ayarla
454
- when (orientation) {
455
- ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
456
- ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
457
- ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
458
- ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.preScale(-1.0f, 1.0f)
459
- ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.preScale(1.0f, -1.0f)
460
- // Diğer karmaşık oryantasyonlar da eklenebilir...
461
- }
462
-
463
- // Döndürme işlemini uygula
464
- val rotatedBitmap =
465
- if (!matrix.isIdentity) {
466
- Bitmap.createBitmap(
467
- originalBitmap,0, 0, originalBitmap.width, originalBitmap.height, matrix, true
468
- ).also {
469
- originalBitmap.recycle()
470
- }
471
- } else {
472
- originalBitmap
473
- }
395
+ // 2. EXIF bilgisini yerel dosyadan oku (URL'de bu çalışmazdı, şimdi çalışır)
396
+ val orientation = try {
397
+ ExifInterface(workFile.absolutePath).getAttributeInt(
398
+ ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL
399
+ )
400
+ } catch (_: Exception) { ExifInterface.ORIENTATION_NORMAL }
474
401
 
402
+ // 3. Boyut ölçümü
403
+ val boundsOptions = BitmapFactory.Options().apply { inJustDecodeBounds = true }
404
+ workFile.inputStream().use { BitmapFactory.decodeStream(it, null, boundsOptions) }
475
405
 
476
- // 4. ADIM: Kırpma koordinatlarını hesapla (Artık DÖNDÜRÜLMÜŞ bitmap üzerinden)
477
- val rx = crop.getDouble("x")
478
- val ry = crop.getDouble("y")
479
- val rw = crop.getDouble("width")
480
- val rh = crop.getDouble("height")
481
-
482
- // NOT: Artık rotatedBitmap'in boyutlarını kullanıyoruz!
483
- val px = (rx * rotatedBitmap.width).toInt().coerceIn(0, rotatedBitmap.width - 1)
484
- val py = (ry * rotatedBitmap.height).toInt().coerceIn(0, rotatedBitmap.height - 1)
485
- val pw = (rw * rotatedBitmap.width).toInt().coerceAtMost(rotatedBitmap.width - px).coerceAtLeast(1)
486
- val ph = (rh * rotatedBitmap.height).toInt().coerceAtMost(rotatedBitmap.height - py).coerceAtLeast(1)
406
+ val decodeOptions = BitmapFactory.Options().apply {
407
+ inSampleSize = calculateInSampleSize(boundsOptions, 2048, 2048)
408
+ }
487
409
 
410
+ // 4. Bitmap yükle
411
+ val originalBitmap = workFile.inputStream().use {
412
+ BitmapFactory.decodeStream(it, null, decodeOptions)
413
+ } ?: throw Exception("Bitmap yüklenemedi.")
414
+
415
+ // 5. EXIF Rotasyonu uygula
416
+ val matrix = Matrix()
417
+ when (orientation) {
418
+ ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
419
+ ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
420
+ ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
421
+ }
488
422
 
489
- // 5. ADIM: Bitmap'i kırp
490
- val croppedBitmap = Bitmap.createBitmap(rotatedBitmap, px, py, pw, ph)
423
+ val rotatedBitmap = if (!matrix.isIdentity) {
424
+ Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.width, originalBitmap.height, matrix, true)
425
+ .also { originalBitmap.recycle() }
426
+ } else originalBitmap
491
427
 
428
+ // 6. Kırpma koordinatları
429
+ val rx = crop.getDouble("x")
430
+ val ry = crop.getDouble("y")
431
+ val rw = crop.getDouble("width")
432
+ val rh = crop.getDouble("height")
492
433
 
493
- // 6. ADIM: Kırpılmış Bitmap'i geçici bir dosyaya kaydet
494
- // ... (Bu kısım önceki kodla aynı, değişmedi) ...
495
- val mimeType = contentResolver.getType(sourceUri) ?: "image/jpeg"
496
- val fileExtension = when {
497
- mimeType.contains("png") -> "png"
498
- mimeType.contains("webp") -> "webp"
499
- else -> "jpg"
500
- }
501
- val cacheDir = reactContext.cacheDir
502
- val outputFile = createTempFile("cropped_", ".$fileExtension", cacheDir)
503
- val fileOutputStream = outputFile.outputStream()
504
- val compressFormat = when (fileExtension) {
505
- "png" -> Bitmap.CompressFormat.PNG
506
- "webp" -> if (android.os.Build.VERSION.SDK_INT >= 30) Bitmap.CompressFormat.WEBP_LOSSLESS else Bitmap.CompressFormat.WEBP
507
- else -> Bitmap.CompressFormat.JPEG
508
- }
509
- fileOutputStream.use { stream ->
510
- croppedBitmap.compress(compressFormat, 95, stream)
511
- }
434
+ val px = (rx * rotatedBitmap.width).toInt().coerceIn(0, rotatedBitmap.width - 1)
435
+ val py = (ry * rotatedBitmap.height).toInt().coerceIn(0, rotatedBitmap.height - 1)
436
+ val pw = (rw * rotatedBitmap.width).toInt().coerceAtMost(rotatedBitmap.width - px).coerceAtLeast(1)
437
+ val ph = (rh * rotatedBitmap.height).toInt().coerceAtMost(rotatedBitmap.height - py).coerceAtLeast(1)
512
438
 
439
+ val croppedBitmap = Bitmap.createBitmap(rotatedBitmap, px, py, pw, ph)
513
440
 
514
- if (originalBitmap != rotatedBitmap) originalBitmap?.recycle()
515
- rotatedBitmap?.recycle()
516
- croppedBitmap?.recycle()
441
+ // 7. Çıktıyı kaydet
442
+ val outputFile = File.createTempFile("cropped_", ".jpg", reactContext.cacheDir)
443
+ outputFile.outputStream().use {
444
+ croppedBitmap.compress(Bitmap.CompressFormat.JPEG, 95, it)
445
+ }
517
446
 
447
+ // Temizlik
448
+ if (originalBitmap != rotatedBitmap) originalBitmap.recycle()
449
+ rotatedBitmap.recycle()
450
+ croppedBitmap.recycle()
518
451
 
519
- // 8. ADIM: Yeni oluşturulan dosyanın URI'sini döndür
520
- promise.resolve(Uri.fromFile(outputFile).toString())
452
+ promise.resolve(Uri.fromFile(outputFile).toString())
521
453
 
522
- } catch (e: OutOfMemoryError) {
523
- promise.reject("E_OOM_CROP", "Resim işlenirken hafıza yetersiz kaldı.")
524
- } catch (e: Exception) {
525
- promise.reject("E_CROP_URI", "Kırpma hatası: ${e.message}")
526
- }
527
- }.start()
528
- }
454
+ } catch (e: Exception) {
455
+ promise.reject("E_CROP_URI", e.message)
456
+ } finally {
457
+ // KRİTİK: Geçici dosyayı SİL
458
+ workFile?.delete()
459
+ }
460
+ }.start()
461
+ }
529
462
 
530
463
  @ReactMethod
531
464
  fun DeleteTempFiles(fileUris: ReadableArray, promise: Promise) {
@@ -635,85 +568,182 @@ class MyUploaderModule(private val reactContext: ReactApplicationContext) :
635
568
 
636
569
  override fun onNewIntent(intent: Intent) {}
637
570
 
638
- private fun copyFileToInternalStorage(uri: Uri, originalFileName: String): String? {
639
- return try {
640
- val inputStream = reactContext.contentResolver.openInputStream(uri) ?: return null
641
-
642
- // Dosya isminin çakışmaması için timestamp ekliyoruz
643
- val uniqueFileName = "picked_${System.currentTimeMillis()}_$originalFileName"
644
- val tempFile = File(reactContext.cacheDir, uniqueFileName)
645
-
646
- val outputStream = FileOutputStream(tempFile)
647
-
648
- // Stream kopyalama işlemi (Hafıza dostu)
649
- inputStream.use { input ->
650
- outputStream.use { output ->
651
- input.copyTo(output)
652
- }
653
- }
654
-
655
- // file:///... formatında URI döndürür
656
- Uri.fromFile(tempFile).toString()
571
+
572
+ private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
573
+ val (height: Int, width: Int) = options.outHeight to options.outWidth
574
+ var inSampleSize = 1
575
+ if (height > reqHeight || width > reqWidth) {
576
+ val halfHeight = height / 2
577
+ val halfWidth = width / 2
578
+ while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
579
+ inSampleSize *= 2
580
+ }
581
+ }
582
+ return inSampleSize
583
+ }
584
+ private fun rotateBitmapIfRequired(uri: Uri, bitmap: Bitmap): Bitmap {
585
+ val inputStream = reactContext.contentResolver.openInputStream(uri)
586
+
587
+ // 🟢 ExifInterface kullanımı (AndroidX sürümü ile)
588
+ val exif = inputStream?.use { input ->
589
+ try {
590
+ ExifInterface(input)
657
591
  } catch (e: Exception) {
658
- Log.e("MyUploader", "Kalıcı kopyalama hatası: ${e.message}")
659
592
  null
660
593
  }
594
+ } ?: return bitmap
595
+
596
+ val orientation = exif.getAttributeInt(
597
+ ExifInterface.TAG_ORIENTATION,
598
+ ExifInterface.ORIENTATION_NORMAL
599
+ )
600
+
601
+ val matrix = Matrix()
602
+ when (orientation) {
603
+ ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
604
+ ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
605
+ ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
606
+ else -> return bitmap
661
607
  }
662
608
 
609
+ return try {
610
+ val rotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
611
+ if (rotated != bitmap) {
612
+ bitmap.recycle() // Eski bitmap'i bellekten boşalt
613
+ }
614
+ rotated
615
+ } catch (e: Exception) {
616
+ bitmap
617
+ }
618
+ }
663
619
 
664
- private fun processSelectedFiles(uris: List<Uri>): WritableArray {
665
- val fileObjects = WritableNativeArray()
666
- val maxSizeBytes = (maxSize * 1024 * 1024).toLong()
620
+ // // SENIOR_UPDATE: PNG transparan alanları beyaz yapma (Siyah arka planı önler)
621
+ private fun fillWhiteBackground(bitmap: Bitmap): Bitmap {
622
+ val newBitmap = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.RGB_565)
623
+ val canvas = android.graphics.Canvas(newBitmap)
624
+ canvas.drawColor(android.graphics.Color.WHITE)
625
+ canvas.drawBitmap(bitmap, 0f, 0f, null)
626
+ bitmap.recycle()
627
+ return newBitmap
628
+ }
667
629
 
668
- for (uri in uris) {
669
- reactContext.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
670
- if (cursor.moveToFirst()) {
671
- val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
672
- val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
673
630
 
674
- val fileName = if (nameIndex != -1) cursor.getString(nameIndex) else guessFileNameFromUri(uri)
675
- val fileSize = if (sizeIndex != -1) cursor.getLong(sizeIndex) else -1L
676
631
 
677
- var trueSize = -1L
678
- try {
679
- reactContext.contentResolver.openAssetFileDescriptor(uri, "r")?.use { afd ->
680
- trueSize = afd.length
681
- }
682
- } catch (e: FileNotFoundException) {
683
- // Bazı durumlarda bu metod hata verebilir, görmezden gel ve devam et
684
- }
685
-
686
- if (trueSize <= 0) trueSize = fileSize
632
+ private fun convertToJpgAndSaveInternal(uri: Uri, originalFileName: String): String? {
633
+ return try {
634
+ val contentResolver = reactContext.contentResolver
687
635
 
688
- if (maxSizeBytes > 0 && trueSize > 0 && trueSize > maxSizeBytes) {
689
- throw FileTooLargeException("Seçilen dosya ($fileName) belirtilen ${maxSize}MB boyutundan büyük.")
690
- }
636
+ // 1. ADIM: Sadece boyutları oku (Memory harcamaz)
637
+ val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
638
+ contentResolver.openInputStream(uri)?.use { BitmapFactory.decodeStream(it, null, options) }
691
639
 
692
- val mimeType = reactContext.contentResolver.getType(uri) ?: URLConnection.guessContentTypeFromName(fileName) ?: "application/octet-stream"
693
- val base64 = if (this.withBase64) {
694
- encodeFileToBase64(uri)
695
- } else {
696
- null
640
+ // 2. ADIM: Örnekleme miktarını hesapla (Max 2048px genişlik/yükseklik standardı)
641
+ options.inSampleSize = calculateInSampleSize(options, 2048, 2048)
642
+ options.inJustDecodeBounds = false
643
+ // Bellek tasarrufu için 16-bit kullan (Senior Tip)
644
+ options.inPreferredConfig = Bitmap.Config.RGB_565
645
+
646
+ // 3. ADIM: Bitmap'i güvenli boyutta oluştur
647
+ var bitmap = contentResolver.openInputStream(uri)?.use {
648
+ BitmapFactory.decodeStream(it, null, options)
649
+ } ?: return null
650
+
651
+ // 4. ADIM: Rotasyon düzeltme
652
+ bitmap = rotateBitmapIfRequired(uri, bitmap)
653
+
654
+ // 5. ADIM: Saydamlık (Alpha) temizleme
655
+ bitmap = fillWhiteBackground(bitmap)
656
+
657
+ // 6. ADIM: JPEG olarak kaydet
658
+ val nameWithoutExt = originalFileName.substringBeforeLast(".")
659
+ val tempFile = File(reactContext.cacheDir, "picked_${System.currentTimeMillis()}_$nameWithoutExt.jpg")
660
+
661
+ FileOutputStream(tempFile).use { stream ->
662
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 85, stream) // %85 kalite/boyut dengesi
663
+ }
664
+
665
+ bitmap.recycle()
666
+ Uri.fromFile(tempFile).toString()
667
+ } catch (e: Exception) {
668
+ Log.e("MyUploader", "Pipeline Error: ${e.message}")
669
+ null
670
+ }
671
+ }
672
+
673
+
674
+
675
+
676
+
677
+ private fun processSelectedFiles(uris: List<Uri>): WritableArray {
678
+ val fileObjects = WritableNativeArray()
679
+ val maxSizeBytes = (maxSize * 1024 * 1024).toLong()
680
+
681
+ for (uri in uris) {
682
+ reactContext.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
683
+ if (cursor.moveToFirst()) {
684
+ // 1. Orijinal dosya bilgilerini al (Sadece limit kontrolü için)
685
+ val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
686
+ val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
687
+
688
+ val originalFileName = if (nameIndex != -1) cursor.getString(nameIndex) else guessFileNameFromUri(uri)
689
+ val originalFileSize = if (sizeIndex != -1) cursor.getLong(sizeIndex) else -1L
690
+
691
+ var trueSize = -1L
692
+ try {
693
+ reactContext.contentResolver.openAssetFileDescriptor(uri, "r")?.use { afd ->
694
+ trueSize = afd.length
697
695
  }
696
+ } catch (e: FileNotFoundException) { }
697
+
698
+ if (trueSize <= 0) trueSize = originalFileSize
698
699
 
699
- val internalFileUri = copyFileToInternalStorage(uri, fileName) ?: uri.toString()
700
+ // 2. Orijinal dosya boyutu kontrolü
701
+ if (maxSizeBytes > 0 && trueSize > 0 && trueSize > maxSizeBytes) {
702
+ throw FileTooLargeException("Seçilen dosya ($originalFileName) belirtilen ${maxSize}MB boyutundan büyük.")
703
+ }
700
704
 
705
+ // 3. KRİTİK DÜZELTME: Önce JPEG'e dönüştür (Pipeline çalışsın)
706
+ // Bu fonksiyon artık Rotate ve Arka Plan fix işlemlerini de içeriyor
707
+ val internalFileUri = convertToJpgAndSaveInternal(uri, originalFileName) ?: uri.toString()
708
+ val finalUri = Uri.parse(internalFileUri)
701
709
 
702
- val fileMap = WritableNativeMap().apply {
703
- putString("fileName", fileName)
704
- putDouble("fileSize", if (trueSize > 0) trueSize.toDouble() else fileSize.toDouble())
705
- putString("fileType", mimeType)
706
- putString("fileUri", internalFileUri)
707
- if (withBase64 && base64 != null) {
708
- putString("base64", base64)
709
- }
710
+ // 4. Yeni dosyanın gerçek bilgilerini al
711
+ val finalFile = File(finalUri.path ?: "")
712
+ val finalSize = finalFile.length()
713
+ val finalFileName = if (originalFileName.lowercase().endsWith(".jpg") || originalFileName.lowercase().endsWith(".jpeg")) {
714
+ originalFileName
715
+ } else {
716
+ "${originalFileName.substringBeforeLast(".")}.jpg"
717
+ }
718
+
719
+ // 5. KRİTİK DÜZELTME: Base64'ü YENİ oluşturulan JPEG üzerinden al
720
+ // Böylece sunucuya giden base64 ile fileType (image/jpeg) tam eşleşir.
721
+ val base64 = if (this.withBase64) {
722
+ // Sadece 5MB altındaki dosyaların base64'ünü çıkar (JS performansı için)
723
+ if (finalSize < 5 * 1024 * 1024) {
724
+ encodeFileToBase64(finalUri)
725
+ } else {
726
+ null // Çok büyükse null bırak, JS çökmesin
710
727
  }
711
- fileObjects.pushMap(fileMap)
728
+ } else {
729
+ null
712
730
  }
713
- } ?:throw FileNotFoundException("Content ile dosya bilgisi alınamadı:$uri")
714
- }
715
- return fileObjects
731
+
732
+ val fileMap = WritableNativeMap().apply {
733
+ putString("fileName", finalFileName)
734
+ putDouble("fileSize", finalSize.toDouble()) // Orijinal değil, yeni boyut
735
+ putString("fileType", "image/jpeg") // Sabit JPEG
736
+ putString("fileUri", internalFileUri)
737
+ if (withBase64 && base64 != null) {
738
+ putString("base64", base64)
739
+ }
740
+ }
741
+ fileObjects.pushMap(fileMap)
742
+ }
743
+ } ?: throw FileNotFoundException("Content ile dosya bilgisi alınamadı: $uri")
716
744
  }
745
+ return fileObjects
746
+ }
717
747
 
718
748
  private fun guessFileNameFromUri (uri:Uri):String{
719
749
  return uri.lastPathSegment?.substringBefore("?") ?: "unknown_file"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-my-uploader-android",
3
- "version": "1.0.54",
3
+ "version": "1.0.56",
4
4
  "description": "file uploader for android",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./src/index.d.ts",