diff --git a/android/build.gradle b/android/build.gradle index b3ee973..a6211eb 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() @@ -13,6 +13,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } +apply plugin: 'kotlin-android' rootProject.allprojects { repositories { @@ -25,8 +26,12 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 30 - + compileSdkVersion 33 + 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" + } diff --git a/android/src/main/kotlin/com/example/imagegallerysaver/ImageGallerySaverPlugin.kt b/android/src/main/kotlin/com/example/imagegallerysaver/ImageGallerySaverPlugin.kt index c88c80f..54876b8 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) {