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
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
280
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
290
|
-
promise.resolve(Uri.fromFile(outputFile).toString())
|
|
281
|
+
promise.resolve(Uri.fromFile(outputFile).toString())
|
|
291
282
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
490
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
|
|
520
|
-
promise.resolve(Uri.fromFile(outputFile).toString())
|
|
452
|
+
promise.resolve(Uri.fromFile(outputFile).toString())
|
|
521
453
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
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
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
689
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
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
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
|
|
728
|
+
} else {
|
|
729
|
+
null
|
|
712
730
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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"
|