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
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
-
|
|
649
|
-
|
|
663
|
+
val orientation = exif.getAttributeInt(
|
|
664
|
+
ExifInterface.TAG_ORIENTATION,
|
|
665
|
+
ExifInterface.ORIENTATION_NORMAL
|
|
666
|
+
)
|
|
650
667
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
663
|
-
|
|
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
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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"
|