From 9b8c6bb5c3842fa90d14bfee5a32a6ceb4ed56bc Mon Sep 17 00:00:00 2001 From: ramesh-aureus <122423235+ramesh-aureus@users.noreply.github.com> Date: Wed, 18 Sep 2024 14:06:39 +0700 Subject: [PATCH 1/4] Update build.gradle --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index b3ee973e..b7e192e0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' android { compileSdkVersion 30 - + namespace 'com.example.imagegallerysaver' sourceSets { main.java.srcDirs += 'src/main/kotlin' } From b7720974147473672c8909c19d649abf2d44ff87 Mon Sep 17 00:00:00 2001 From: ramesh-aureus <122423235+ramesh-aureus@users.noreply.github.com> Date: Wed, 18 Sep 2024 19:11:33 +0700 Subject: [PATCH 2/4] Update build.gradle --- android/build.gradle | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index b7e192e0..d4ec026d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -13,6 +13,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } +apply plugin: 'kotlin-android' rootProject.allprojects { repositories { @@ -27,6 +28,10 @@ apply plugin: 'kotlin-android' android { compileSdkVersion 30 namespace 'com.example.imagegallerysaver' + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } sourceSets { main.java.srcDirs += 'src/main/kotlin' } @@ -34,11 +39,15 @@ android { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + kotlinOptions { + jvmTarget = '17' // Ensure Kotlin targets Java 17 + } lintOptions { disable 'InvalidPackage' } } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + } From cc6d3828938250b1b5a172f018a06d6f87fdc66e Mon Sep 17 00:00:00 2001 From: ramesh-aureus <122423235+ramesh-aureus@users.noreply.github.com> Date: Thu, 9 Jan 2025 12:20:42 +0700 Subject: [PATCH 3/4] Update ImageGallerySaverPlugin.kt --- .../ImageGallerySaverPlugin.kt | 238 +++++++++--------- 1 file changed, 115 insertions(+), 123 deletions(-) diff --git a/android/src/main/kotlin/com/example/imagegallerysaver/ImageGallerySaverPlugin.kt b/android/src/main/kotlin/com/example/imagegallerysaver/ImageGallerySaverPlugin.kt index c88c80fc..54876b82 100644 --- a/android/src/main/kotlin/com/example/imagegallerysaver/ImageGallerySaverPlugin.kt +++ b/android/src/main/kotlin/com/example/imagegallerysaver/ImageGallerySaverPlugin.kt @@ -70,51 +70,27 @@ class ImageGallerySaverPlugin : FlutterPlugin, MethodCallHandler { } private fun generateUri(extension: String = "", name: String? = null): Uri? { - var fileName = name ?: System.currentTimeMillis().toString() + val fileName = name ?: System.currentTimeMillis().toString() val mimeType = getMIMEType(extension) - val isVideo = mimeType?.startsWith("video")==true - - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - // >= android 10 - val uri = when { - isVideo -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI - else -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI - } - - val values = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) - put( - MediaStore.MediaColumns.RELATIVE_PATH, when { - isVideo -> Environment.DIRECTORY_MOVIES - else -> Environment.DIRECTORY_PICTURES - } - ) - if (!TextUtils.isEmpty(mimeType)) { - put(when {isVideo -> MediaStore.Video.Media.MIME_TYPE - else -> MediaStore.Images.Media.MIME_TYPE - }, mimeType) - } - } - - applicationContext?.contentResolver?.insert(uri, values) - - } else { - // < android 10 - val storePath = - Environment.getExternalStoragePublicDirectory(when { - isVideo -> Environment.DIRECTORY_MOVIES - else -> Environment.DIRECTORY_PICTURES - }).absolutePath - val appDir = File(storePath).apply { - if (!exists()) { - mkdir() - } + val isVideo = mimeType?.startsWith("video") == true + + val uri = when { + isVideo -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI + else -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI + } + + val values = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, "$fileName.$extension") + put(MediaStore.MediaColumns.RELATIVE_PATH, when { + isVideo -> Environment.DIRECTORY_MOVIES + else -> Environment.DIRECTORY_PICTURES + }) + mimeType?.let { + put(MediaStore.MediaColumns.MIME_TYPE, it) } - - val file = - File(appDir, if (extension.isNotEmpty()) "$fileName.$extension" else fileName) - Uri.fromFile(file) } + + return applicationContext?.contentResolver?.insert(uri, values) } /** @@ -137,103 +113,119 @@ class ImageGallerySaverPlugin : FlutterPlugin, MethodCallHandler { * @param context context * @param fileUri file path */ - private fun sendBroadcast(context: Context, fileUri: Uri?) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + + + private fun saveImageToGallery( + bmp: Bitmap?, + quality: Int?, + name: String? +): HashMap { + // Check parameters + if (bmp == null || quality == null) { + return SaveResultModel(false, null, "parameters error").toHashMap() + } + // Check applicationContext + val context = applicationContext + ?: return SaveResultModel(false, null, "applicationContext null").toHashMap() + + var fileUri: Uri? = null + var fos: OutputStream? = null + var success = false + + try { + fileUri = generateUri("jpg", name = name) + if (fileUri != null) { + fos = context.contentResolver.openOutputStream(fileUri) + if (fos != null) { + bmp.compress(Bitmap.CompressFormat.JPEG, quality, fos) + fos.flush() + success = true + } + } + + // For Android 9 and below, manually trigger media scanning + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && fileUri != null) { val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) mediaScanIntent.data = fileUri context.sendBroadcast(mediaScanIntent) } + } catch (e: IOException) { + return SaveResultModel(false, null, e.toString()).toHashMap() + } finally { + fos?.close() + bmp.recycle() } - private fun saveImageToGallery( - bmp: Bitmap?, - quality: Int?, - name: String? - ): HashMap { - // check parameters - if (bmp == null || quality == null) { - return SaveResultModel(false, null, "parameters error").toHashMap() - } - // check applicationContext - val context = applicationContext - ?: return SaveResultModel(false, null, "applicationContext null").toHashMap() - var fileUri: Uri? = null - var fos: OutputStream? = null - var success = false - try { - fileUri = generateUri("jpg", name = name) - if (fileUri != null) { - fos = context.contentResolver.openOutputStream(fileUri) - if (fos != null) { - println("ImageGallerySaverPlugin $quality") - bmp.compress(Bitmap.CompressFormat.JPEG, quality, fos) - fos.flush() - success = true - } - } - } catch (e: IOException) { - SaveResultModel(false, null, e.toString()).toHashMap() - } finally { - fos?.close() - bmp.recycle() - } - return if (success) { - sendBroadcast(context, fileUri) - SaveResultModel(fileUri.toString().isNotEmpty(), fileUri.toString(), null).toHashMap() - } else { - SaveResultModel(false, null, "saveImageToGallery fail").toHashMap() - } + return if (success) { + SaveResultModel(true, fileUri.toString(), null).toHashMap() + } else { + SaveResultModel(false, null, "saveImageToGallery fail").toHashMap() } +} - private fun saveFileToGallery(filePath: String?, name: String?): HashMap { - // check parameters - if (filePath == null) { - return SaveResultModel(false, null, "parameters error").toHashMap() + + private fun saveFileToGallery(filePath: String?, name: String?): HashMap { + // Check parameters + if (filePath == null) { + return SaveResultModel(false, null, "parameters error").toHashMap() + } + + val context = applicationContext ?: return SaveResultModel( + false, + null, + "applicationContext null" + ).toHashMap() + + var fileUri: Uri? = null + var outputStream: OutputStream? = null + var fileInputStream: FileInputStream? = null + var success = false + + try { + val originalFile = File(filePath) + if (!originalFile.exists()) { + return SaveResultModel(false, null, "$filePath does not exist").toHashMap() } - val context = applicationContext ?: return SaveResultModel( - false, - null, - "applicationContext null" - ).toHashMap() - var fileUri: Uri? = null - var outputStream: OutputStream? = null - var fileInputStream: FileInputStream? = null - var success = false - - try { - val originalFile = File(filePath) - if(!originalFile.exists()) return SaveResultModel(false, null, "$filePath does not exist").toHashMap() - fileUri = generateUri(originalFile.extension, name) - if (fileUri != null) { - outputStream = context.contentResolver?.openOutputStream(fileUri) - if (outputStream != null) { - fileInputStream = FileInputStream(originalFile) - - val buffer = ByteArray(10240) - var count = 0 - while (fileInputStream.read(buffer).also { count = it } > 0) { - outputStream.write(buffer, 0, count) - } - - outputStream.flush() - success = true + + fileUri = generateUri(originalFile.extension, name) + if (fileUri != null) { + outputStream = context.contentResolver?.openOutputStream(fileUri) + if (outputStream != null) { + fileInputStream = FileInputStream(originalFile) + + val buffer = ByteArray(10240) + var count: Int + while (fileInputStream.read(buffer).also { count = it } > 0) { + outputStream.write(buffer, 0, count) } + + outputStream.flush() + success = true } - } catch (e: IOException) { - SaveResultModel(false, null, e.toString()).toHashMap() - } finally { - outputStream?.close() - fileInputStream?.close() } - return if (success) { - sendBroadcast(context, fileUri) - SaveResultModel(fileUri.toString().isNotEmpty(), fileUri.toString(), null).toHashMap() - } else { - SaveResultModel(false, null, "saveFileToGallery fail").toHashMap() + + // For Android 9 and below, manually trigger media scanning + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && fileUri != null) { + val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) + mediaScanIntent.data = fileUri + context.sendBroadcast(mediaScanIntent) } + } catch (e: IOException) { + return SaveResultModel(false, null, e.toString()).toHashMap() + } finally { + outputStream?.close() + fileInputStream?.close() + } + + return if (success) { + SaveResultModel(true, fileUri.toString(), null).toHashMap() + } else { + SaveResultModel(false, null, "saveFileToGallery fail").toHashMap() } } +} + class SaveResultModel(var isSuccess: Boolean, var filePath: String? = null, var errorMessage: String? = null) { From f0e5fac521b60f1ddc84963b554a07a0666b5ba0 Mon Sep 17 00:00:00 2001 From: ramesh-aureus <122423235+ramesh-aureus@users.noreply.github.com> Date: Thu, 9 Jan 2025 12:21:48 +0700 Subject: [PATCH 4/4] Update build.gradle --- android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index d4ec026d..a6211eb9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,7 +2,7 @@ group 'com.example.imagegallerysaver' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.9.0' repositories { google() mavenCentral() @@ -26,7 +26,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 30 + compileSdkVersion 33 namespace 'com.example.imagegallerysaver' compileOptions { sourceCompatibility JavaVersion.VERSION_17