Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compact Android scope storage #239

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Compact Android scope storage
lwj1994 committed Feb 13, 2023
commit e3c5ba6b893e078beaac72fd3604437ca1a481a4
9 changes: 5 additions & 4 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
jcenter()
mavenCentral()
}

dependencies {
@@ -17,21 +17,22 @@ buildscript {
rootProject.allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 30
compileSdkVersion 33

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
minSdkVersion 16
minSdkVersion 21
targetSdkVersion 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions {
Original file line number Diff line number Diff line change
@@ -1,55 +1,89 @@
package com.example.imagegallerysaver

import android.annotation.TargetApi
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Environment
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.text.TextUtils
import android.text.format.DateUtils
import android.webkit.MimeTypeMap
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import android.text.TextUtils
import android.webkit.MimeTypeMap

import java.text.SimpleDateFormat
import java.util.*

class ImageGallerySaverPlugin : FlutterPlugin, MethodCallHandler {
private var applicationContext: Context? = null
private var methodChannel: MethodChannel? = null

override fun onMethodCall(call: MethodCall, result: Result): Unit {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when {
call.method == "saveImageToGallery" -> {
val image = call.argument<ByteArray>("imageBytes") ?: return
val quality = call.argument<Int>("quality") ?: return
val name = call.argument<String>("name")

result.success(saveImageToGallery(BitmapFactory.decodeByteArray(image, 0, image.size), quality, name))
val folder = call.argument<String>("folder")
if (Build.VERSION.SDK_INT >= 29) {
result.success(
saveImageToGallery29(
applicationContext!!,
BitmapFactory.decodeByteArray(image, 0, image.size),
name ?: "",
folder = folder ?: "",
),
)
} else {
result.success(
saveImageToGallery(
BitmapFactory.decodeByteArray(
image,
0,
image.size,
),
quality,
name ?: "",
),
)
}
}
call.method == "saveFileToGallery" -> {
val path = call.argument<String>("file") ?: return
val name = call.argument<String>("name")
result.success(saveFileToGallery(path, name))
val folder = call.argument<String>("folder")

if (Build.VERSION.SDK_INT >= 29) {
result.success(
saveFileToGallery29(
applicationContext!!,
path,
name ?: "",
folder ?: "",
),
)
} else {
result.success(saveFileToGallery(path, name))
}
}
else -> result.notImplemented()
}

}


private fun generateUri(extension: String = "", name: String? = null): Uri {
var fileName = name ?: System.currentTimeMillis().toString()

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
var uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI

@@ -66,7 +100,8 @@ class ImageGallerySaverPlugin : FlutterPlugin, MethodCallHandler {
}
return applicationContext?.contentResolver?.insert(uri, values)!!
} else {
val storePath = Environment.getExternalStorageDirectory().absolutePath + File.separator + Environment.DIRECTORY_PICTURES
val storePath =
Environment.getExternalStorageDirectory().absolutePath + File.separator + Environment.DIRECTORY_PICTURES
val appDir = File(storePath)
if (!appDir.exists()) {
appDir.mkdir()
@@ -79,16 +114,26 @@ class ImageGallerySaverPlugin : FlutterPlugin, MethodCallHandler {
}

private fun getMIMEType(extension: String): String? {
var type: String? = null;
var type: String? = null
if (!TextUtils.isEmpty(extension)) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase())
}
return type
}

private fun saveImageToGallery(bmp: Bitmap, quality: Int, name: String?): HashMap<String, Any?> {
private fun saveImageToGallery(
bmp: Bitmap,
quality: Int,
name: String,
): HashMap<String, Any?> {
val context = applicationContext
val fileUri = generateUri("jpg", name = name)
val currentTime: Long = System.currentTimeMillis()
val imageDate: String =
SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()).format(Date(currentTime))
val screenshotFileNameTemplate = "%s.jpg"
val imageFileName: String =
name.ifEmpty { String.format(screenshotFileNameTemplate, imageDate) }
val fileUri = generateUri("jpg", name = imageFileName)
return try {
val fos = context?.contentResolver?.openOutputStream(fileUri)!!
println("ImageGallerySaverPlugin $quality")
@@ -103,6 +148,163 @@ class ImageGallerySaverPlugin : FlutterPlugin, MethodCallHandler {
}
}

/**
* android 10 以上版本
*/
@TargetApi(Build.VERSION_CODES.Q)
fun saveImageToGallery29(
context: Context,
image: Bitmap,
name: String?,
folder: String = "",
): HashMap<String, Any?> {
val currentTime: Long = System.currentTimeMillis()
val imageDate: String =
SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()).format(Date(currentTime))
val screenshotFileNameTemplate = "%s.png"
val mImageFileName: String = name ?: String.format(screenshotFileNameTemplate, imageDate)
val values = ContentValues()

values.put(
MediaStore.MediaColumns.RELATIVE_PATH,
if (folder.isEmpty()) {
Environment.DIRECTORY_PICTURES
} else {
"${Environment.DIRECTORY_PICTURES}${File.separator}$folder"
},
)
values.put(MediaStore.MediaColumns.DISPLAY_NAME, mImageFileName)
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
values.put(MediaStore.MediaColumns.DATE_ADDED, currentTime / 1000)
values.put(MediaStore.MediaColumns.DATE_MODIFIED, currentTime / 1000)
values.put(
MediaStore.MediaColumns.DATE_EXPIRES,
(currentTime + DateUtils.DAY_IN_MILLIS) / 1000,
)
values.put(MediaStore.MediaColumns.IS_PENDING, 1)
val resolver: ContentResolver = context.getContentResolver()
val uri: Uri? = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
if (uri == null) return SaveResultModel(false, null, "").toHashMap()

try {
resolver.openOutputStream(uri).use { out ->
if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
return SaveResultModel(false, null, "").toHashMap()
}
}
values.clear()
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
values.putNull(MediaStore.MediaColumns.DATE_EXPIRES)
resolver.update(uri, values, null, null)
} catch (e: IOException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
resolver.delete(uri, null)
}
return SaveResultModel(false, null, "").toHashMap()
}
return SaveResultModel(true, null, "").toHashMap()
}

@TargetApi(Build.VERSION_CODES.Q)
private fun saveFileToGallery29(
context: Context,
filePath: String,
name: String,
folder: String,
): HashMap<String, Any?> {
var fileName = filePath
if (filePath.contains('/')) {
fileName = filePath.substringAfterLast("/")
}
var type = "png"
if (fileName.contains(".")) {
type = fileName.substringAfterLast(".")
}
val isImage = type.equals("png", ignoreCase = true) ||
type.equals("webp", ignoreCase = true) ||
type.equals("jpg", ignoreCase = true) ||
type.equals("jpeg", ignoreCase = true) ||
type.equals("heic", ignoreCase = true)
type.equals("gif", ignoreCase = true)
type.equals("apng", ignoreCase = true)
type.equals("raw", ignoreCase = true)
type.equals("svg", ignoreCase = true)
type.equals("bmp", ignoreCase = true)
type.equals("tif", ignoreCase = true)

filePath.substringAfterLast(".")
val currentTime: Long = System.currentTimeMillis()
val imageDate: String =
SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()).format(Date(currentTime))
val screenshotFileNameTemplate = "%s.$type"
val mImageFileName: String =
name.ifEmpty { String.format(screenshotFileNameTemplate, imageDate) }
val values = ContentValues()

values.put(
MediaStore.MediaColumns.RELATIVE_PATH,
if (folder.isEmpty()) {
if (isImage) {
Environment.DIRECTORY_PICTURES
} else {
Environment.DIRECTORY_MOVIES
}
} else {
if (isImage) {
"${Environment.DIRECTORY_PICTURES}${File.separator}$folder"
} else {
"${Environment.DIRECTORY_MOVIES}${File.separator}$folder"
}
},
)
values.put(MediaStore.MediaColumns.DISPLAY_NAME, mImageFileName)
try {
values.put(
MediaStore.MediaColumns.MIME_TYPE,
if (isImage) "image/$type" else "video/$type",
)
} catch (e: java.lang.Exception) {
}
values.put(MediaStore.MediaColumns.DATE_ADDED, currentTime / 1000)
values.put(MediaStore.MediaColumns.DATE_MODIFIED, currentTime / 1000)
values.put(
MediaStore.MediaColumns.DATE_EXPIRES,
(currentTime + DateUtils.DAY_IN_MILLIS) / 1000,
)
values.put(MediaStore.MediaColumns.IS_PENDING, 1)
val resolver: ContentResolver = context.getContentResolver()
val uri: Uri? = resolver.insert(
if (isImage) {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else {
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
},
values,
)
if (uri == null) return SaveResultModel(false, null, "").toHashMap()
try {
val fileInputStream = FileInputStream(filePath)
val data = ByteArray(1024)
var read = 0
resolver.openOutputStream(uri).use { out ->
while ((fileInputStream.read(data, 0, data.size).also { read = it }) != -1) {
out?.write(data, 0, read)
}
}
values.clear()
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
values.putNull(MediaStore.MediaColumns.DATE_EXPIRES)
fileInputStream.close()
resolver.update(uri, values, null, null)
} catch (e: IOException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
resolver.delete(uri, null)
}
return SaveResultModel(false, null, "").toHashMap()
}
return SaveResultModel(true, null, "").toHashMap()
}

private fun saveFileToGallery(filePath: String, name: String?): HashMap<String, Any?> {
val context = applicationContext
return try {
@@ -135,21 +337,22 @@ class ImageGallerySaverPlugin : FlutterPlugin, MethodCallHandler {

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
applicationContext = null
methodChannel!!.setMethodCallHandler(null);
methodChannel = null;
methodChannel!!.setMethodCallHandler(null)
methodChannel = null
}

private fun onAttachedToEngine(applicationContext: Context, messenger: BinaryMessenger) {
this.applicationContext = applicationContext
methodChannel = MethodChannel(messenger, "image_gallery_saver")
methodChannel!!.setMethodCallHandler(this)
}

}

class SaveResultModel(var isSuccess: Boolean,
var filePath: String? = null,
var errorMessage: String? = null) {
class SaveResultModel(
var isSuccess: Boolean,
var filePath: String? = null,
var errorMessage: String? = null,
) {
fun toHashMap(): HashMap<String, Any?> {
val hashMap = HashMap<String, Any?>()
hashMap["isSuccess"] = isSuccess
6 changes: 3 additions & 3 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 31
compileSdkVersion 33

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@@ -43,8 +43,8 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.imagegallerysaverexample"
minSdkVersion 16
targetSdkVersion 29
minSdkVersion 21
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Loading