react-native-my-uploader-android 1.0.53 → 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,56 +635,182 @@ class MyUploaderModule(private val reactContext: ReactApplicationContext) :
635
635
 
636
636
  override fun onNewIntent(intent: Intent) {}
637
637
 
638
- private fun processSelectedFiles(uris: List<Uri>): WritableArray {
639
- val fileObjects = WritableNativeArray()
640
- val maxSizeBytes = (maxSize * 1024 * 1024).toLong()
641
638
 
642
- for (uri in uris) {
643
- reactContext.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
644
- if (cursor.moveToFirst()) {
645
- val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
646
- val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
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)
658
+ } catch (e: Exception) {
659
+ null
660
+ }
661
+ } ?: return bitmap
647
662
 
648
- val fileName = if (nameIndex != -1) cursor.getString(nameIndex) else guessFileNameFromUri(uri)
649
- val fileSize = if (sizeIndex != -1) cursor.getLong(sizeIndex) else -1L
663
+ val orientation = exif.getAttributeInt(
664
+ ExifInterface.TAG_ORIENTATION,
665
+ ExifInterface.ORIENTATION_NORMAL
666
+ )
650
667
 
651
- var trueSize = -1L
652
- try {
653
- reactContext.contentResolver.openAssetFileDescriptor(uri, "r")?.use { afd ->
654
- trueSize = afd.length
655
- }
656
- } catch (e: FileNotFoundException) {
657
- // Bazı durumlarda bu metod hata verebilir, görmezden gel ve devam et
658
- }
659
-
660
- if (trueSize <= 0) trueSize = fileSize
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
674
+ }
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
+ }
686
+
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
+ }
696
+
697
+
698
+
699
+ private fun convertToJpgAndSaveInternal(uri: Uri, originalFileName: String): String? {
700
+ return try {
701
+ val contentResolver = reactContext.contentResolver
702
+
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) }
706
+
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)
661
723
 
662
- if (maxSizeBytes > 0 && trueSize > 0 && trueSize > maxSizeBytes) {
663
- throw FileTooLargeException("Seçilen dosya ($fileName) belirtilen ${maxSize}MB boyutundan büyük.")
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
664
762
  }
763
+ } catch (e: FileNotFoundException) { }
764
+
765
+ if (trueSize <= 0) trueSize = originalFileSize
766
+
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
+ }
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)
776
+
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
+ }
665
785
 
666
- val mimeType = reactContext.contentResolver.getType(uri) ?: URLConnection.guessContentTypeFromName(fileName) ?: "application/octet-stream"
667
- val base64 = if (this.withBase64) {
668
- encodeFileToBase64(uri)
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)
669
792
  } else {
670
- null
793
+ null // Çok büyükse null bırak, JS çökmesin
671
794
  }
795
+ } else {
796
+ null
797
+ }
672
798
 
673
- val fileMap = WritableNativeMap().apply {
674
- putString("fileName", fileName)
675
- putDouble("fileSize", if (trueSize > 0) trueSize.toDouble() else fileSize.toDouble())
676
- putString("fileType", mimeType)
677
- putString("fileUri", uri.toString())
678
- if (withBase64 && base64 != null) {
679
- putString("base64", base64)
680
- }
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)
681
806
  }
682
- fileObjects.pushMap(fileMap)
683
807
  }
684
- } ?:throw FileNotFoundException("Content ile dosya bilgisi alınamadı:$uri")
685
- }
686
- return fileObjects
808
+ fileObjects.pushMap(fileMap)
809
+ }
810
+ } ?: throw FileNotFoundException("Content ile dosya bilgisi alınamadı: $uri")
687
811
  }
812
+ return fileObjects
813
+ }
688
814
 
689
815
  private fun guessFileNameFromUri (uri:Uri):String{
690
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.53",
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",