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

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
@@ -635,85 +635,182 @@ class MyUploaderModule(private val reactContext: ReactApplicationContext) :
635
635
 
636
636
  override fun onNewIntent(intent: Intent) {}
637
637
 
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()
638
+
639
+ private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
640
+ val (height: Int, width: Int) = options.outHeight to options.outWidth
641
+ var inSampleSize = 1
642
+ if (height > reqHeight || width > reqWidth) {
643
+ val halfHeight = height / 2
644
+ val halfWidth = width / 2
645
+ while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
646
+ inSampleSize *= 2
647
+ }
648
+ }
649
+ return inSampleSize
650
+ }
651
+ private fun rotateBitmapIfRequired(uri: Uri, bitmap: Bitmap): Bitmap {
652
+ val inputStream = reactContext.contentResolver.openInputStream(uri)
653
+
654
+ // 🟢 ExifInterface kullanımı (AndroidX sürümü ile)
655
+ val exif = inputStream?.use { input ->
656
+ try {
657
+ ExifInterface(input)
657
658
  } catch (e: Exception) {
658
- Log.e("MyUploader", "Kalıcı kopyalama hatası: ${e.message}")
659
659
  null
660
660
  }
661
+ } ?: return bitmap
662
+
663
+ val orientation = exif.getAttributeInt(
664
+ ExifInterface.TAG_ORIENTATION,
665
+ ExifInterface.ORIENTATION_NORMAL
666
+ )
667
+
668
+ val matrix = Matrix()
669
+ when (orientation) {
670
+ ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
671
+ ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
672
+ ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
673
+ else -> return bitmap
661
674
  }
662
675
 
676
+ return try {
677
+ val rotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
678
+ if (rotated != bitmap) {
679
+ bitmap.recycle() // Eski bitmap'i bellekten boşalt
680
+ }
681
+ rotated
682
+ } catch (e: Exception) {
683
+ bitmap
684
+ }
685
+ }
663
686
 
664
- private fun processSelectedFiles(uris: List<Uri>): WritableArray {
665
- val fileObjects = WritableNativeArray()
666
- val maxSizeBytes = (maxSize * 1024 * 1024).toLong()
687
+ // // SENIOR_UPDATE: PNG transparan alanları beyaz yapma (Siyah arka planı önler)
688
+ private fun fillWhiteBackground(bitmap: Bitmap): Bitmap {
689
+ val newBitmap = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.RGB_565)
690
+ val canvas = android.graphics.Canvas(newBitmap)
691
+ canvas.drawColor(android.graphics.Color.WHITE)
692
+ canvas.drawBitmap(bitmap, 0f, 0f, null)
693
+ bitmap.recycle()
694
+ return newBitmap
695
+ }
667
696
 
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
697
 
674
- val fileName = if (nameIndex != -1) cursor.getString(nameIndex) else guessFileNameFromUri(uri)
675
- val fileSize = if (sizeIndex != -1) cursor.getLong(sizeIndex) else -1L
676
698
 
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
699
+ private fun convertToJpgAndSaveInternal(uri: Uri, originalFileName: String): String? {
700
+ return try {
701
+ val contentResolver = reactContext.contentResolver
687
702
 
688
- if (maxSizeBytes > 0 && trueSize > 0 && trueSize > maxSizeBytes) {
689
- throw FileTooLargeException("Seçilen dosya ($fileName) belirtilen ${maxSize}MB boyutundan büyük.")
690
- }
703
+ // 1. ADIM: Sadece boyutları oku (Memory harcamaz)
704
+ val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
705
+ contentResolver.openInputStream(uri)?.use { BitmapFactory.decodeStream(it, null, options) }
691
706
 
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
707
+ // 2. ADIM: Örnekleme miktarını hesapla (Max 2048px genişlik/yükseklik standardı)
708
+ options.inSampleSize = calculateInSampleSize(options, 2048, 2048)
709
+ options.inJustDecodeBounds = false
710
+ // Bellek tasarrufu için 16-bit kullan (Senior Tip)
711
+ options.inPreferredConfig = Bitmap.Config.RGB_565
712
+
713
+ // 3. ADIM: Bitmap'i güvenli boyutta oluştur
714
+ var bitmap = contentResolver.openInputStream(uri)?.use {
715
+ BitmapFactory.decodeStream(it, null, options)
716
+ } ?: return null
717
+
718
+ // 4. ADIM: Rotasyon düzeltme
719
+ bitmap = rotateBitmapIfRequired(uri, bitmap)
720
+
721
+ // 5. ADIM: Saydamlık (Alpha) temizleme
722
+ bitmap = fillWhiteBackground(bitmap)
723
+
724
+ // 6. ADIM: JPEG olarak kaydet
725
+ val nameWithoutExt = originalFileName.substringBeforeLast(".")
726
+ val tempFile = File(reactContext.cacheDir, "picked_${System.currentTimeMillis()}_$nameWithoutExt.jpg")
727
+
728
+ FileOutputStream(tempFile).use { stream ->
729
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 85, stream) // %85 kalite/boyut dengesi
730
+ }
731
+
732
+ bitmap.recycle()
733
+ Uri.fromFile(tempFile).toString()
734
+ } catch (e: Exception) {
735
+ Log.e("MyUploader", "Pipeline Error: ${e.message}")
736
+ null
737
+ }
738
+ }
739
+
740
+
741
+
742
+
743
+
744
+ private fun processSelectedFiles(uris: List<Uri>): WritableArray {
745
+ val fileObjects = WritableNativeArray()
746
+ val maxSizeBytes = (maxSize * 1024 * 1024).toLong()
747
+
748
+ for (uri in uris) {
749
+ reactContext.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
750
+ if (cursor.moveToFirst()) {
751
+ // 1. Orijinal dosya bilgilerini al (Sadece limit kontrolü için)
752
+ val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
753
+ val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
754
+
755
+ val originalFileName = if (nameIndex != -1) cursor.getString(nameIndex) else guessFileNameFromUri(uri)
756
+ val originalFileSize = if (sizeIndex != -1) cursor.getLong(sizeIndex) else -1L
757
+
758
+ var trueSize = -1L
759
+ try {
760
+ reactContext.contentResolver.openAssetFileDescriptor(uri, "r")?.use { afd ->
761
+ trueSize = afd.length
697
762
  }
763
+ } catch (e: FileNotFoundException) { }
764
+
765
+ if (trueSize <= 0) trueSize = originalFileSize
698
766
 
699
- val internalFileUri = copyFileToInternalStorage(uri, fileName) ?: uri.toString()
767
+ // 2. Orijinal dosya boyutu kontrolü
768
+ if (maxSizeBytes > 0 && trueSize > 0 && trueSize > maxSizeBytes) {
769
+ throw FileTooLargeException("Seçilen dosya ($originalFileName) belirtilen ${maxSize}MB boyutundan büyük.")
770
+ }
700
771
 
772
+ // 3. KRİTİK DÜZELTME: Önce JPEG'e dönüştür (Pipeline çalışsın)
773
+ // Bu fonksiyon artık Rotate ve Arka Plan fix işlemlerini de içeriyor
774
+ val internalFileUri = convertToJpgAndSaveInternal(uri, originalFileName) ?: uri.toString()
775
+ val finalUri = Uri.parse(internalFileUri)
701
776
 
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
- }
777
+ // 4. Yeni dosyanın gerçek bilgilerini al
778
+ val finalFile = File(finalUri.path ?: "")
779
+ val finalSize = finalFile.length()
780
+ val finalFileName = if (originalFileName.lowercase().endsWith(".jpg") || originalFileName.lowercase().endsWith(".jpeg")) {
781
+ originalFileName
782
+ } else {
783
+ "${originalFileName.substringBeforeLast(".")}.jpg"
784
+ }
785
+
786
+ // 5. KRİTİK DÜZELTME: Base64'ü YENİ oluşturulan JPEG üzerinden al
787
+ // Böylece sunucuya giden base64 ile fileType (image/jpeg) tam eşleşir.
788
+ val base64 = if (this.withBase64) {
789
+ // Sadece 5MB altındaki dosyaların base64'ünü çıkar (JS performansı için)
790
+ if (finalSize < 5 * 1024 * 1024) {
791
+ encodeFileToBase64(finalUri)
792
+ } else {
793
+ null // Çok büyükse null bırak, JS çökmesin
710
794
  }
711
- fileObjects.pushMap(fileMap)
795
+ } else {
796
+ null
712
797
  }
713
- } ?:throw FileNotFoundException("Content ile dosya bilgisi alınamadı:$uri")
714
- }
715
- return fileObjects
798
+
799
+ val fileMap = WritableNativeMap().apply {
800
+ putString("fileName", finalFileName)
801
+ putDouble("fileSize", finalSize.toDouble()) // Orijinal değil, yeni boyut
802
+ putString("fileType", "image/jpeg") // Sabit JPEG
803
+ putString("fileUri", internalFileUri)
804
+ if (withBase64 && base64 != null) {
805
+ putString("base64", base64)
806
+ }
807
+ }
808
+ fileObjects.pushMap(fileMap)
809
+ }
810
+ } ?: throw FileNotFoundException("Content ile dosya bilgisi alınamadı: $uri")
716
811
  }
812
+ return fileObjects
813
+ }
717
814
 
718
815
  private fun guessFileNameFromUri (uri:Uri):String{
719
816
  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.55",
4
4
  "description": "file uploader for android",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./src/index.d.ts",