mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-27 09:24:45 +01:00
Format native code
This commit is contained in:
parent
6809503e0b
commit
68007d5d3e
45 changed files with 2888 additions and 3107 deletions
|
@ -21,224 +21,214 @@ fun Bitmap.aspectRatio() = width / height.toFloat()
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalContracts::class)
|
@OptIn(ExperimentalContracts::class)
|
||||||
inline fun <T> Bitmap.use(block: (Bitmap) -> T): T {
|
inline fun <T> Bitmap.use(block: (Bitmap) -> T): T {
|
||||||
contract {
|
contract {
|
||||||
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return block(this)
|
return block(this)
|
||||||
} finally {
|
} finally {
|
||||||
recycle()
|
recycle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class BitmapResizeMethod {
|
enum class BitmapResizeMethod {
|
||||||
FIT, FILL,
|
FIT, FILL,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BitmapUtil {
|
interface BitmapUtil {
|
||||||
companion object {
|
companion object {
|
||||||
fun loadImageFixed(
|
fun loadImageFixed(
|
||||||
context: Context, uri: Uri, targetW: Int, targetH: Int
|
context: Context, uri: Uri, targetW: Int, targetH: Int
|
||||||
): Bitmap {
|
): Bitmap {
|
||||||
val opt = loadImageBounds(context, uri)
|
val opt = loadImageBounds(context, uri)
|
||||||
val subsample = calcBitmapSubsample(
|
val subsample = calcBitmapSubsample(
|
||||||
opt.outWidth,
|
opt.outWidth, opt.outHeight, targetW, targetH,
|
||||||
opt.outHeight,
|
BitmapResizeMethod.FILL
|
||||||
targetW,
|
)
|
||||||
targetH,
|
if (subsample > 1) {
|
||||||
BitmapResizeMethod.FILL
|
logD(
|
||||||
)
|
TAG,
|
||||||
if (subsample > 1) {
|
"Subsample image to fixed: $subsample ${opt.outWidth}x${opt.outHeight} -> ${targetW}x$targetH"
|
||||||
logD(
|
)
|
||||||
TAG,
|
}
|
||||||
"Subsample image to fixed: $subsample ${opt.outWidth}x${opt.outHeight} -> ${targetW}x$targetH"
|
val outOpt = BitmapFactory.Options().apply {
|
||||||
)
|
inSampleSize = subsample
|
||||||
}
|
}
|
||||||
val outOpt = BitmapFactory.Options().apply {
|
val bitmap = loadImage(context, uri, outOpt)
|
||||||
inSampleSize = subsample
|
if (subsample > 1) {
|
||||||
}
|
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
|
||||||
val bitmap = loadImage(context, uri, outOpt)
|
}
|
||||||
if (subsample > 1) {
|
return Bitmap.createScaledBitmap(bitmap, targetW, targetH, true)
|
||||||
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
|
}
|
||||||
}
|
|
||||||
return Bitmap.createScaledBitmap(bitmap, targetW, targetH, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a bitmap
|
* Load a bitmap
|
||||||
*
|
*
|
||||||
* If @c resizeMethod == FIT, make sure the size of the bitmap can fit
|
* If @c resizeMethod == FIT, make sure the size of the bitmap can fit
|
||||||
* inside the bound defined by @c targetW and @c targetH, i.e.,
|
* inside the bound defined by @c targetW and @c targetH, i.e.,
|
||||||
* bitmap.w <= @c targetW and bitmap.h <= @c targetH
|
* bitmap.w <= @c targetW and bitmap.h <= @c targetH
|
||||||
*
|
*
|
||||||
* If @c resizeMethod == FILL, make sure the size of the bitmap can
|
* If @c resizeMethod == FILL, make sure the size of the bitmap can
|
||||||
* completely fill the bound defined by @c targetW and @c targetH, i.e.,
|
* completely fill the bound defined by @c targetW and @c targetH, i.e.,
|
||||||
* bitmap.w >= @c targetW and bitmap.h >= @c targetH
|
* bitmap.w >= @c targetW and bitmap.h >= @c targetH
|
||||||
*
|
*
|
||||||
* If bitmap is smaller than the bound and @c shouldUpscale == true, it
|
* If bitmap is smaller than the bound and @c shouldUpscale == true, it
|
||||||
* will be upscaled
|
* will be upscaled
|
||||||
*
|
*
|
||||||
* @param context
|
* @param context
|
||||||
* @param uri
|
* @param uri
|
||||||
* @param targetW
|
* @param targetW
|
||||||
* @param targetH
|
* @param targetH
|
||||||
* @param resizeMethod
|
* @param resizeMethod
|
||||||
* @param isAllowSwapSide
|
* @param isAllowSwapSide
|
||||||
* @param shouldUpscale
|
* @param shouldUpscale
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun loadImage(
|
fun loadImage(
|
||||||
context: Context,
|
context: Context,
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
targetW: Int,
|
targetW: Int,
|
||||||
targetH: Int,
|
targetH: Int,
|
||||||
resizeMethod: BitmapResizeMethod,
|
resizeMethod: BitmapResizeMethod,
|
||||||
isAllowSwapSide: Boolean = false,
|
isAllowSwapSide: Boolean = false,
|
||||||
shouldUpscale: Boolean = true,
|
shouldUpscale: Boolean = true,
|
||||||
shouldFixOrientation: Boolean = false,
|
shouldFixOrientation: Boolean = false,
|
||||||
): Bitmap {
|
): Bitmap {
|
||||||
val opt = loadImageBounds(context, uri)
|
val opt = loadImageBounds(context, uri)
|
||||||
val shouldSwapSide =
|
val shouldSwapSide =
|
||||||
isAllowSwapSide && opt.outWidth != opt.outHeight && (opt.outWidth >= opt.outHeight) != (targetW >= targetH)
|
isAllowSwapSide && opt.outWidth != opt.outHeight && (opt.outWidth >= opt.outHeight) != (targetW >= targetH)
|
||||||
val dstW = if (shouldSwapSide) targetH else targetW
|
val dstW = if (shouldSwapSide) targetH else targetW
|
||||||
val dstH = if (shouldSwapSide) targetW else targetH
|
val dstH = if (shouldSwapSide) targetW else targetH
|
||||||
val subsample = calcBitmapSubsample(
|
val subsample = calcBitmapSubsample(
|
||||||
opt.outWidth, opt.outHeight, dstW, dstH, resizeMethod
|
opt.outWidth, opt.outHeight, dstW, dstH, resizeMethod
|
||||||
)
|
)
|
||||||
if (subsample > 1) {
|
if (subsample > 1) {
|
||||||
logD(
|
logD(
|
||||||
TAG,
|
TAG,
|
||||||
"Subsample image to ${resizeMethod.name}: $subsample ${opt.outWidth}x${opt.outHeight} -> ${dstW}x$dstH" + (if (shouldSwapSide) " (swapped)" else "")
|
"Subsample image to ${resizeMethod.name}: $subsample ${opt.outWidth}x${opt.outHeight} -> ${dstW}x$dstH" + (if (shouldSwapSide) " (swapped)" else "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val outOpt = BitmapFactory.Options().apply {
|
val outOpt = BitmapFactory.Options().apply {
|
||||||
inSampleSize = subsample
|
inSampleSize = subsample
|
||||||
}
|
}
|
||||||
val bitmap = loadImage(context, uri, outOpt)
|
val bitmap = loadImage(context, uri, outOpt)
|
||||||
if (subsample > 1) {
|
if (subsample > 1) {
|
||||||
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
|
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
|
||||||
}
|
}
|
||||||
if (bitmap.width < dstW && bitmap.height < dstH && !shouldUpscale) {
|
if (bitmap.width < dstW && bitmap.height < dstH && !shouldUpscale) {
|
||||||
return if (shouldFixOrientation) {
|
return if (shouldFixOrientation) {
|
||||||
fixOrientation(context, uri, bitmap)
|
fixOrientation(context, uri, bitmap)
|
||||||
} else {
|
} else {
|
||||||
bitmap
|
bitmap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = when (resizeMethod) {
|
val result = when (resizeMethod) {
|
||||||
BitmapResizeMethod.FIT -> Bitmap.createScaledBitmap(
|
BitmapResizeMethod.FIT -> Bitmap.createScaledBitmap(
|
||||||
bitmap,
|
bitmap, minOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
|
||||||
minOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
|
minOf(dstH, (dstW / bitmap.aspectRatio()).toInt()), true
|
||||||
minOf(dstH, (dstW / bitmap.aspectRatio()).toInt()),
|
)
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
BitmapResizeMethod.FILL -> Bitmap.createScaledBitmap(
|
BitmapResizeMethod.FILL -> Bitmap.createScaledBitmap(
|
||||||
bitmap,
|
bitmap, maxOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
|
||||||
maxOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
|
maxOf(dstH, (dstW / bitmap.aspectRatio()).toInt()), true
|
||||||
maxOf(dstH, (dstW / bitmap.aspectRatio()).toInt()),
|
)
|
||||||
true
|
}
|
||||||
)
|
return if (shouldFixOrientation) {
|
||||||
}
|
fixOrientation(context, uri, result)
|
||||||
return if (shouldFixOrientation) {
|
} else {
|
||||||
fixOrientation(context, uri, result)
|
result
|
||||||
} else {
|
}
|
||||||
result
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotate the image to its visible orientation
|
* Rotate the image to its visible orientation
|
||||||
*/
|
*/
|
||||||
private fun fixOrientation(
|
private fun fixOrientation(
|
||||||
context: Context, uri: Uri, bitmap: Bitmap
|
context: Context, uri: Uri, bitmap: Bitmap
|
||||||
): Bitmap {
|
): Bitmap {
|
||||||
return try {
|
return try {
|
||||||
openUriInputStream(context, uri)!!.use {
|
openUriInputStream(context, uri)!!.use {
|
||||||
val iExif = ExifInterface(it)
|
val iExif = ExifInterface(it)
|
||||||
val orientation =
|
val orientation =
|
||||||
iExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
|
iExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
|
||||||
logI(
|
logI(
|
||||||
TAG,
|
TAG,
|
||||||
"[fixOrientation] Input file orientation: $orientation"
|
"[fixOrientation] Input file orientation: $orientation"
|
||||||
)
|
)
|
||||||
val rotate = when (orientation) {
|
val rotate = when (orientation) {
|
||||||
ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSPOSE -> 90f
|
ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSPOSE -> 90f
|
||||||
|
|
||||||
ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_VERTICAL -> 180f
|
ExifInterface.ORIENTATION_ROTATE_180, ExifInterface.ORIENTATION_FLIP_VERTICAL -> 180f
|
||||||
|
|
||||||
ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSVERSE -> 270f
|
ExifInterface.ORIENTATION_ROTATE_270, ExifInterface.ORIENTATION_TRANSVERSE -> 270f
|
||||||
|
|
||||||
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> 0f
|
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> 0f
|
||||||
else -> return bitmap
|
else -> return bitmap
|
||||||
}
|
}
|
||||||
val mat = Matrix()
|
val mat = Matrix()
|
||||||
mat.postRotate(rotate)
|
mat.postRotate(rotate)
|
||||||
if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || orientation == ExifInterface.ORIENTATION_TRANSVERSE || orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || orientation == ExifInterface.ORIENTATION_TRANSPOSE) {
|
if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || orientation == ExifInterface.ORIENTATION_TRANSVERSE || orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || orientation == ExifInterface.ORIENTATION_TRANSPOSE) {
|
||||||
mat.postScale(-1f, 1f)
|
mat.postScale(-1f, 1f)
|
||||||
}
|
}
|
||||||
Bitmap.createBitmap(
|
Bitmap.createBitmap(
|
||||||
bitmap, 0, 0, bitmap.width, bitmap.height, mat, true
|
bitmap, 0, 0, bitmap.width, bitmap.height, mat, true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
logE(
|
logE(
|
||||||
TAG,
|
TAG,
|
||||||
"[fixOrientation] Failed fixing, assume normal orientation",
|
"[fixOrientation] Failed fixing, assume normal orientation",
|
||||||
e
|
e
|
||||||
)
|
)
|
||||||
bitmap
|
bitmap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openUriInputStream(
|
private fun openUriInputStream(
|
||||||
context: Context, uri: Uri
|
context: Context, uri: Uri
|
||||||
): InputStream? {
|
): InputStream? {
|
||||||
return if (UriUtil.isAssetUri(uri)) {
|
return if (UriUtil.isAssetUri(uri)) {
|
||||||
context.assets.open(UriUtil.getAssetUriPath(uri))
|
context.assets.open(UriUtil.getAssetUriPath(uri))
|
||||||
} else {
|
} else {
|
||||||
context.contentResolver.openInputStream(uri)
|
context.contentResolver.openInputStream(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadImageBounds(
|
private fun loadImageBounds(
|
||||||
context: Context, uri: Uri
|
context: Context, uri: Uri
|
||||||
): BitmapFactory.Options {
|
): BitmapFactory.Options {
|
||||||
openUriInputStream(context, uri)!!.use {
|
openUriInputStream(context, uri)!!.use {
|
||||||
val opt = BitmapFactory.Options().apply {
|
val opt = BitmapFactory.Options().apply {
|
||||||
inJustDecodeBounds = true
|
inJustDecodeBounds = true
|
||||||
}
|
}
|
||||||
BitmapFactory.decodeStream(it, null, opt)
|
BitmapFactory.decodeStream(it, null, opt)
|
||||||
return opt
|
return opt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadImage(
|
private fun loadImage(
|
||||||
context: Context, uri: Uri, opt: BitmapFactory.Options
|
context: Context, uri: Uri, opt: BitmapFactory.Options
|
||||||
): Bitmap {
|
): Bitmap {
|
||||||
openUriInputStream(context, uri)!!.use {
|
openUriInputStream(context, uri)!!.use {
|
||||||
return BitmapFactory.decodeStream(it, null, opt)!!
|
return BitmapFactory.decodeStream(it, null, opt)!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calcBitmapSubsample(
|
private fun calcBitmapSubsample(
|
||||||
originalW: Int,
|
originalW: Int, originalH: Int, targetW: Int, targetH: Int,
|
||||||
originalH: Int,
|
resizeMethod: BitmapResizeMethod
|
||||||
targetW: Int,
|
): Int {
|
||||||
targetH: Int,
|
return when (resizeMethod) {
|
||||||
resizeMethod: BitmapResizeMethod
|
BitmapResizeMethod.FIT -> maxOf(
|
||||||
): Int {
|
originalW / targetW, originalH / targetH
|
||||||
return when (resizeMethod) {
|
)
|
||||||
BitmapResizeMethod.FIT -> maxOf(
|
|
||||||
originalW / targetW, originalH / targetH
|
|
||||||
)
|
|
||||||
|
|
||||||
BitmapResizeMethod.FILL -> minOf(
|
BitmapResizeMethod.FILL -> minOf(
|
||||||
originalW / targetW, originalH / targetH
|
originalW / targetW, originalH / targetH
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val TAG = "BitmapUtil"
|
private const val TAG = "BitmapUtil"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,10 +113,10 @@ interface MediaStoreUtil {
|
||||||
throw PermissionException("Permission not granted")
|
throw PermissionException("Permission not granted")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("Deprecation")
|
@Suppress("Deprecation") val path =
|
||||||
val path = Environment.getExternalStoragePublicDirectory(
|
Environment.getExternalStoragePublicDirectory(
|
||||||
Environment.DIRECTORY_DOWNLOADS
|
Environment.DIRECTORY_DOWNLOADS
|
||||||
)
|
)
|
||||||
val prefix = if (subDir != null) "$subDir/" else ""
|
val prefix = if (subDir != null) "$subDir/" else ""
|
||||||
var file = File(path, prefix + filename)
|
var file = File(path, prefix + filename)
|
||||||
val baseFilename = file.nameWithoutExtension
|
val baseFilename = file.nameWithoutExtension
|
||||||
|
@ -142,8 +142,8 @@ interface MediaStoreUtil {
|
||||||
|
|
||||||
private fun triggerMediaScan(context: Context, uri: Uri) {
|
private fun triggerMediaScan(context: Context, uri: Uri) {
|
||||||
val scanIntent = Intent().apply {
|
val scanIntent = Intent().apply {
|
||||||
@Suppress("Deprecation")
|
@Suppress("Deprecation") action =
|
||||||
action = Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
|
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
|
||||||
data = uri
|
data = uri
|
||||||
}
|
}
|
||||||
context.sendBroadcast(scanIntent)
|
context.sendBroadcast(scanIntent)
|
||||||
|
|
|
@ -8,35 +8,35 @@ import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
|
||||||
interface PermissionUtil {
|
interface PermissionUtil {
|
||||||
companion object {
|
companion object {
|
||||||
const val REQUEST_CODE = K.PERMISSION_REQUEST_CODE
|
const val REQUEST_CODE = K.PERMISSION_REQUEST_CODE
|
||||||
|
|
||||||
fun request(
|
fun request(
|
||||||
activity: Activity, vararg permissions: String
|
activity: Activity, vararg permissions: String
|
||||||
) {
|
) {
|
||||||
ActivityCompat.requestPermissions(
|
ActivityCompat.requestPermissions(
|
||||||
activity, permissions, REQUEST_CODE
|
activity, permissions, REQUEST_CODE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasReadExternalStorage(context: Context): Boolean {
|
fun hasReadExternalStorage(context: Context): Boolean {
|
||||||
return ContextCompat.checkSelfPermission(
|
return ContextCompat.checkSelfPermission(
|
||||||
context, Manifest.permission.READ_EXTERNAL_STORAGE
|
context, Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestReadExternalStorage(activity: Activity) = request(
|
fun requestReadExternalStorage(activity: Activity) = request(
|
||||||
activity, Manifest.permission.READ_EXTERNAL_STORAGE
|
activity, Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
)
|
)
|
||||||
|
|
||||||
fun hasWriteExternalStorage(context: Context): Boolean {
|
fun hasWriteExternalStorage(context: Context): Boolean {
|
||||||
return ContextCompat.checkSelfPermission(
|
return ContextCompat.checkSelfPermission(
|
||||||
context, Manifest.permission.WRITE_EXTERNAL_STORAGE
|
context, Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestWriteExternalStorage(activity: Activity) = request(
|
fun requestWriteExternalStorage(activity: Activity) = request(
|
||||||
activity, Manifest.permission.WRITE_EXTERNAL_STORAGE
|
activity, Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,38 +7,37 @@ import java.nio.ByteBuffer
|
||||||
* Container of pixel data stored in RGBA format
|
* Container of pixel data stored in RGBA format
|
||||||
*/
|
*/
|
||||||
class Rgba8Image(
|
class Rgba8Image(
|
||||||
val pixel: ByteArray, val width: Int, val height: Int
|
val pixel: ByteArray, val width: Int, val height: Int
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun fromJson(json: Map<String, Any>) = Rgba8Image(
|
fun fromJson(json: Map<String, Any>) = Rgba8Image(
|
||||||
json["pixel"] as ByteArray,
|
json["pixel"] as ByteArray, json["width"] as Int,
|
||||||
json["width"] as Int,
|
json["height"] as Int
|
||||||
json["height"] as Int
|
)
|
||||||
)
|
|
||||||
|
|
||||||
fun fromBitmap(src: Bitmap): Rgba8Image {
|
fun fromBitmap(src: Bitmap): Rgba8Image {
|
||||||
assert(src.config == Bitmap.Config.ARGB_8888)
|
assert(src.config == Bitmap.Config.ARGB_8888)
|
||||||
val buffer = ByteBuffer.allocate(src.width * src.height * 4).also {
|
val buffer = ByteBuffer.allocate(src.width * src.height * 4).also {
|
||||||
src.copyPixelsToBuffer(it)
|
src.copyPixelsToBuffer(it)
|
||||||
}
|
}
|
||||||
return Rgba8Image(buffer.array(), src.width, src.height)
|
return Rgba8Image(buffer.array(), src.width, src.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toJson() = mapOf<String, Any>(
|
fun toJson() = mapOf<String, Any>(
|
||||||
"pixel" to pixel,
|
"pixel" to pixel,
|
||||||
"width" to width,
|
"width" to width,
|
||||||
"height" to height,
|
"height" to height,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toBitmap(): Bitmap {
|
fun toBitmap(): Bitmap {
|
||||||
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
.apply {
|
.apply {
|
||||||
copyPixelsFromBuffer(ByteBuffer.wrap(pixel))
|
copyPixelsFromBuffer(ByteBuffer.wrap(pixel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
assert(pixel.size == width * height * 4)
|
assert(pixel.size == width * height * 4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,11 @@ import java.io.Serializable
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
|
|
||||||
fun getPendingIntentFlagImmutable(): Int {
|
fun getPendingIntentFlagImmutable(): Int {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_IMMUTABLE else 0
|
||||||
PendingIntent.FLAG_IMMUTABLE else 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPendingIntentFlagMutable(): Int {
|
fun getPendingIntentFlagMutable(): Int {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0
|
||||||
PendingIntent.FLAG_MUTABLE else 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <T> HttpURLConnection.use(block: (HttpURLConnection) -> T): T {
|
inline fun <T> HttpURLConnection.use(block: (HttpURLConnection) -> T): T {
|
||||||
|
|
|
@ -5,9 +5,9 @@ import android.net.Uri
|
||||||
internal interface MessageEvent
|
internal interface MessageEvent
|
||||||
|
|
||||||
internal data class ImageProcessorCompletedEvent(
|
internal data class ImageProcessorCompletedEvent(
|
||||||
val result: Uri,
|
val result: Uri,
|
||||||
) : MessageEvent
|
) : MessageEvent
|
||||||
|
|
||||||
internal data class ImageProcessorFailedEvent(
|
internal data class ImageProcessorFailedEvent(
|
||||||
val exception: Throwable,
|
val exception: Throwable,
|
||||||
) : MessageEvent
|
) : MessageEvent
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package com.nkming.nc_photos.np_platform_image_processor
|
package com.nkming.nc_photos.np_platform_image_processor
|
||||||
|
|
||||||
internal class HttpException(statusCode: Int, message: String) :
|
internal class HttpException(statusCode: Int, message: String) :
|
||||||
Exception(message)
|
Exception(message)
|
||||||
|
|
||||||
internal class NativeException(message: String) : Exception(message)
|
internal class NativeException(message: String) : Exception(message)
|
||||||
|
|
|
@ -21,386 +21,282 @@ import io.flutter.plugin.common.MethodChannel
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
internal class ImageProcessorChannelHandler(context: Context) :
|
internal class ImageProcessorChannelHandler(context: Context) :
|
||||||
MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
|
MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
|
||||||
companion object {
|
companion object {
|
||||||
const val METHOD_CHANNEL = "${K.LIB_ID}/image_processor_method"
|
const val METHOD_CHANNEL = "${K.LIB_ID}/image_processor_method"
|
||||||
|
|
||||||
private const val TAG = "ImageProcessorChannelHandler"
|
private const val TAG = "ImageProcessorChannelHandler"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"zeroDce" -> {
|
"zeroDce" -> {
|
||||||
try {
|
try {
|
||||||
zeroDce(
|
zeroDce(
|
||||||
call.argument("fileUrl")!!,
|
call.argument("fileUrl")!!, call.argument("headers"),
|
||||||
call.argument("headers"),
|
call.argument("filename")!!,
|
||||||
call.argument("filename")!!,
|
call.argument("maxWidth")!!,
|
||||||
call.argument("maxWidth")!!,
|
call.argument("maxHeight")!!,
|
||||||
call.argument("maxHeight")!!,
|
call.argument<Boolean>("isSaveToServer")!!,
|
||||||
call.argument<Boolean>("isSaveToServer")!!,
|
call.argument("iteration")!!, result
|
||||||
call.argument("iteration")!!,
|
)
|
||||||
result
|
} catch (e: Throwable) {
|
||||||
)
|
logE(TAG, "Uncaught exception", e)
|
||||||
} catch (e: Throwable) {
|
result.error("systemException", e.toString(), null)
|
||||||
logE(TAG, "Uncaught exception", e)
|
}
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"deepLab3Portrait" -> {
|
"deepLab3Portrait" -> {
|
||||||
try {
|
try {
|
||||||
deepLab3Portrait(
|
deepLab3Portrait(
|
||||||
call.argument("fileUrl")!!,
|
call.argument("fileUrl")!!, call.argument("headers"),
|
||||||
call.argument("headers"),
|
call.argument("filename")!!,
|
||||||
call.argument("filename")!!,
|
call.argument("maxWidth")!!,
|
||||||
call.argument("maxWidth")!!,
|
call.argument("maxHeight")!!,
|
||||||
call.argument("maxHeight")!!,
|
call.argument<Boolean>("isSaveToServer")!!,
|
||||||
call.argument<Boolean>("isSaveToServer")!!,
|
call.argument("radius")!!, result
|
||||||
call.argument("radius")!!,
|
)
|
||||||
result
|
} catch (e: Throwable) {
|
||||||
)
|
logE(TAG, "Uncaught exception", e)
|
||||||
} catch (e: Throwable) {
|
result.error("systemException", e.toString(), null)
|
||||||
logE(TAG, "Uncaught exception", e)
|
}
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"esrgan" -> {
|
"esrgan" -> {
|
||||||
try {
|
try {
|
||||||
esrgan(
|
esrgan(
|
||||||
call.argument("fileUrl")!!,
|
call.argument("fileUrl")!!, call.argument("headers"),
|
||||||
call.argument("headers"),
|
call.argument("filename")!!,
|
||||||
call.argument("filename")!!,
|
call.argument("maxWidth")!!,
|
||||||
call.argument("maxWidth")!!,
|
call.argument("maxHeight")!!,
|
||||||
call.argument("maxHeight")!!,
|
call.argument<Boolean>("isSaveToServer")!!, result
|
||||||
call.argument<Boolean>("isSaveToServer")!!,
|
)
|
||||||
result
|
} catch (e: Throwable) {
|
||||||
)
|
logE(TAG, "Uncaught exception", e)
|
||||||
} catch (e: Throwable) {
|
result.error("systemException", e.toString(), null)
|
||||||
logE(TAG, "Uncaught exception", e)
|
}
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"arbitraryStyleTransfer" -> {
|
"arbitraryStyleTransfer" -> {
|
||||||
try {
|
try {
|
||||||
arbitraryStyleTransfer(
|
arbitraryStyleTransfer(
|
||||||
call.argument("fileUrl")!!,
|
call.argument("fileUrl")!!, call.argument("headers"),
|
||||||
call.argument("headers"),
|
call.argument("filename")!!,
|
||||||
call.argument("filename")!!,
|
call.argument("maxWidth")!!,
|
||||||
call.argument("maxWidth")!!,
|
call.argument("maxHeight")!!,
|
||||||
call.argument("maxHeight")!!,
|
call.argument<Boolean>("isSaveToServer")!!,
|
||||||
call.argument<Boolean>("isSaveToServer")!!,
|
call.argument("styleUri")!!, call.argument("weight")!!,
|
||||||
call.argument("styleUri")!!,
|
result
|
||||||
call.argument("weight")!!,
|
)
|
||||||
result
|
} catch (e: Throwable) {
|
||||||
)
|
logE(TAG, "Uncaught exception", e)
|
||||||
} catch (e: Throwable) {
|
result.error("systemException", e.toString(), null)
|
||||||
logE(TAG, "Uncaught exception", e)
|
}
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"deepLab3ColorPop" -> {
|
"deepLab3ColorPop" -> {
|
||||||
try {
|
try {
|
||||||
deepLab3ColorPop(
|
deepLab3ColorPop(
|
||||||
call.argument("fileUrl")!!,
|
call.argument("fileUrl")!!, call.argument("headers"),
|
||||||
call.argument("headers"),
|
call.argument("filename")!!,
|
||||||
call.argument("filename")!!,
|
call.argument("maxWidth")!!,
|
||||||
call.argument("maxWidth")!!,
|
call.argument("maxHeight")!!,
|
||||||
call.argument("maxHeight")!!,
|
call.argument<Boolean>("isSaveToServer")!!,
|
||||||
call.argument<Boolean>("isSaveToServer")!!,
|
call.argument("weight")!!, result
|
||||||
call.argument("weight")!!,
|
)
|
||||||
result
|
} catch (e: Throwable) {
|
||||||
)
|
logE(TAG, "Uncaught exception", e)
|
||||||
} catch (e: Throwable) {
|
result.error("systemException", e.toString(), null)
|
||||||
logE(TAG, "Uncaught exception", e)
|
}
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"neurOp" -> {
|
"neurOp" -> {
|
||||||
try {
|
try {
|
||||||
neurOp(
|
neurOp(
|
||||||
call.argument("fileUrl")!!,
|
call.argument("fileUrl")!!, call.argument("headers"),
|
||||||
call.argument("headers"),
|
call.argument("filename")!!,
|
||||||
call.argument("filename")!!,
|
call.argument("maxWidth")!!,
|
||||||
call.argument("maxWidth")!!,
|
call.argument("maxHeight")!!,
|
||||||
call.argument("maxHeight")!!,
|
call.argument<Boolean>("isSaveToServer")!!, result
|
||||||
call.argument<Boolean>("isSaveToServer")!!,
|
)
|
||||||
result
|
} catch (e: Throwable) {
|
||||||
)
|
logE(TAG, "Uncaught exception", e)
|
||||||
} catch (e: Throwable) {
|
result.error("systemException", e.toString(), null)
|
||||||
logE(TAG, "Uncaught exception", e)
|
}
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"filter" -> {
|
"filter" -> {
|
||||||
try {
|
try {
|
||||||
filter(
|
filter(
|
||||||
call.argument("fileUrl")!!,
|
call.argument("fileUrl")!!, call.argument("headers"),
|
||||||
call.argument("headers"),
|
call.argument("filename")!!,
|
||||||
call.argument("filename")!!,
|
call.argument("maxWidth")!!,
|
||||||
call.argument("maxWidth")!!,
|
call.argument("maxHeight")!!,
|
||||||
call.argument("maxHeight")!!,
|
call.argument<Boolean>("isSaveToServer")!!,
|
||||||
call.argument<Boolean>("isSaveToServer")!!,
|
call.argument("filters")!!, result
|
||||||
call.argument("filters")!!,
|
)
|
||||||
result
|
} catch (e: Throwable) {
|
||||||
)
|
logE(TAG, "Uncaught exception", e)
|
||||||
} catch (e: Throwable) {
|
result.error("systemException", e.toString(), null)
|
||||||
logE(TAG, "Uncaught exception", e)
|
}
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"filterPreview" -> {
|
"filterPreview" -> {
|
||||||
try {
|
try {
|
||||||
filterPreview(
|
filterPreview(
|
||||||
call.argument("rgba8")!!,
|
call.argument("rgba8")!!, call.argument("filters")!!,
|
||||||
call.argument("filters")!!,
|
result
|
||||||
result
|
)
|
||||||
)
|
} catch (e: Throwable) {
|
||||||
} catch (e: Throwable) {
|
logE(TAG, "Uncaught exception", e)
|
||||||
logE(TAG, "Uncaught exception", e)
|
result.error("systemException", e.toString(), null)
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
||||||
eventSink = events
|
eventSink = events
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancel(arguments: Any?) {
|
override fun onCancel(arguments: Any?) {
|
||||||
eventSink = null
|
eventSink = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun zeroDce(
|
private fun zeroDce(
|
||||||
fileUrl: String,
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
headers: Map<String, String>?,
|
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, iteration: Int,
|
||||||
filename: String,
|
result: MethodChannel.Result
|
||||||
maxWidth: Int,
|
) = method(fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer,
|
||||||
maxHeight: Int,
|
ImageProcessorService.METHOD_ZERO_DCE, result, onIntent = {
|
||||||
isSaveToServer: Boolean,
|
it.putExtra(ImageProcessorService.EXTRA_ITERATION, iteration)
|
||||||
iteration: Int,
|
})
|
||||||
result: MethodChannel.Result
|
|
||||||
) = method(fileUrl,
|
|
||||||
headers,
|
|
||||||
filename,
|
|
||||||
maxWidth,
|
|
||||||
maxHeight,
|
|
||||||
isSaveToServer,
|
|
||||||
ImageProcessorService.METHOD_ZERO_DCE,
|
|
||||||
result,
|
|
||||||
onIntent = {
|
|
||||||
it.putExtra(ImageProcessorService.EXTRA_ITERATION, iteration)
|
|
||||||
})
|
|
||||||
|
|
||||||
private fun deepLab3Portrait(
|
private fun deepLab3Portrait(
|
||||||
fileUrl: String,
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
headers: Map<String, String>?,
|
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, radius: Int,
|
||||||
filename: String,
|
result: MethodChannel.Result
|
||||||
maxWidth: Int,
|
) = method(fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer,
|
||||||
maxHeight: Int,
|
ImageProcessorService.METHOD_DEEP_LAP_PORTRAIT, result, onIntent = {
|
||||||
isSaveToServer: Boolean,
|
it.putExtra(ImageProcessorService.EXTRA_RADIUS, radius)
|
||||||
radius: Int,
|
})
|
||||||
result: MethodChannel.Result
|
|
||||||
) = method(fileUrl,
|
|
||||||
headers,
|
|
||||||
filename,
|
|
||||||
maxWidth,
|
|
||||||
maxHeight,
|
|
||||||
isSaveToServer,
|
|
||||||
ImageProcessorService.METHOD_DEEP_LAP_PORTRAIT,
|
|
||||||
result,
|
|
||||||
onIntent = {
|
|
||||||
it.putExtra(ImageProcessorService.EXTRA_RADIUS, radius)
|
|
||||||
})
|
|
||||||
|
|
||||||
private fun esrgan(
|
private fun esrgan(
|
||||||
fileUrl: String,
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
headers: Map<String, String>?,
|
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean,
|
||||||
filename: String,
|
result: MethodChannel.Result
|
||||||
maxWidth: Int,
|
) = method(
|
||||||
maxHeight: Int,
|
fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer,
|
||||||
isSaveToServer: Boolean,
|
ImageProcessorService.METHOD_ESRGAN, result
|
||||||
result: MethodChannel.Result
|
)
|
||||||
) = method(
|
|
||||||
fileUrl,
|
|
||||||
headers,
|
|
||||||
filename,
|
|
||||||
maxWidth,
|
|
||||||
maxHeight,
|
|
||||||
isSaveToServer,
|
|
||||||
ImageProcessorService.METHOD_ESRGAN,
|
|
||||||
result
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun arbitraryStyleTransfer(
|
private fun arbitraryStyleTransfer(
|
||||||
fileUrl: String,
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
headers: Map<String, String>?,
|
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean,
|
||||||
filename: String,
|
styleUri: String, weight: Float, result: MethodChannel.Result
|
||||||
maxWidth: Int,
|
) = method(fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer,
|
||||||
maxHeight: Int,
|
ImageProcessorService.METHOD_ARBITRARY_STYLE_TRANSFER, result,
|
||||||
isSaveToServer: Boolean,
|
onIntent = {
|
||||||
styleUri: String,
|
it.putExtra(
|
||||||
weight: Float,
|
ImageProcessorService.EXTRA_STYLE_URI, Uri.parse(styleUri)
|
||||||
result: MethodChannel.Result
|
)
|
||||||
) = method(fileUrl,
|
it.putExtra(ImageProcessorService.EXTRA_WEIGHT, weight)
|
||||||
headers,
|
})
|
||||||
filename,
|
|
||||||
maxWidth,
|
|
||||||
maxHeight,
|
|
||||||
isSaveToServer,
|
|
||||||
ImageProcessorService.METHOD_ARBITRARY_STYLE_TRANSFER,
|
|
||||||
result,
|
|
||||||
onIntent = {
|
|
||||||
it.putExtra(
|
|
||||||
ImageProcessorService.EXTRA_STYLE_URI, Uri.parse(styleUri)
|
|
||||||
)
|
|
||||||
it.putExtra(ImageProcessorService.EXTRA_WEIGHT, weight)
|
|
||||||
})
|
|
||||||
|
|
||||||
private fun deepLab3ColorPop(
|
private fun deepLab3ColorPop(
|
||||||
fileUrl: String,
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
headers: Map<String, String>?,
|
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, weight: Float,
|
||||||
filename: String,
|
result: MethodChannel.Result
|
||||||
maxWidth: Int,
|
) = method(fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer,
|
||||||
maxHeight: Int,
|
ImageProcessorService.METHOD_DEEP_LAP_COLOR_POP, result, onIntent = {
|
||||||
isSaveToServer: Boolean,
|
it.putExtra(ImageProcessorService.EXTRA_WEIGHT, weight)
|
||||||
weight: Float,
|
})
|
||||||
result: MethodChannel.Result
|
|
||||||
) = method(fileUrl,
|
|
||||||
headers,
|
|
||||||
filename,
|
|
||||||
maxWidth,
|
|
||||||
maxHeight,
|
|
||||||
isSaveToServer,
|
|
||||||
ImageProcessorService.METHOD_DEEP_LAP_COLOR_POP,
|
|
||||||
result,
|
|
||||||
onIntent = {
|
|
||||||
it.putExtra(ImageProcessorService.EXTRA_WEIGHT, weight)
|
|
||||||
})
|
|
||||||
|
|
||||||
private fun neurOp(
|
private fun neurOp(
|
||||||
fileUrl: String,
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
headers: Map<String, String>?,
|
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean,
|
||||||
filename: String,
|
result: MethodChannel.Result
|
||||||
maxWidth: Int,
|
) = method(
|
||||||
maxHeight: Int,
|
fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer,
|
||||||
isSaveToServer: Boolean,
|
ImageProcessorService.METHOD_NEUR_OP, result
|
||||||
result: MethodChannel.Result
|
)
|
||||||
) = method(
|
|
||||||
fileUrl,
|
|
||||||
headers,
|
|
||||||
filename,
|
|
||||||
maxWidth,
|
|
||||||
maxHeight,
|
|
||||||
isSaveToServer,
|
|
||||||
ImageProcessorService.METHOD_NEUR_OP,
|
|
||||||
result
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun filter(
|
private fun filter(
|
||||||
fileUrl: String,
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
headers: Map<String, String>?,
|
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean,
|
||||||
filename: String,
|
filters: List<Map<String, Any>>, result: MethodChannel.Result
|
||||||
maxWidth: Int,
|
) {
|
||||||
maxHeight: Int,
|
// convert to serializable
|
||||||
isSaveToServer: Boolean,
|
val l = arrayListOf<Serializable>()
|
||||||
filters: List<Map<String, Any>>,
|
filters.mapTo(l, { HashMap(it) })
|
||||||
result: MethodChannel.Result
|
method(fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer,
|
||||||
) {
|
ImageProcessorService.METHOD_FILTER, result, onIntent = {
|
||||||
// convert to serializable
|
it.putExtra(ImageProcessorService.EXTRA_FILTERS, l)
|
||||||
val l = arrayListOf<Serializable>()
|
})
|
||||||
filters.mapTo(l, { HashMap(it) })
|
}
|
||||||
method(fileUrl,
|
|
||||||
headers,
|
|
||||||
filename,
|
|
||||||
maxWidth,
|
|
||||||
maxHeight,
|
|
||||||
isSaveToServer,
|
|
||||||
ImageProcessorService.METHOD_FILTER,
|
|
||||||
result,
|
|
||||||
onIntent = {
|
|
||||||
it.putExtra(ImageProcessorService.EXTRA_FILTERS, l)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun filterPreview(
|
private fun filterPreview(
|
||||||
rgba8: Map<String, Any>,
|
rgba8: Map<String, Any>, filters: List<Map<String, Any>>,
|
||||||
filters: List<Map<String, Any>>,
|
result: MethodChannel.Result
|
||||||
result: MethodChannel.Result
|
) {
|
||||||
) {
|
var img = Rgba8Image.fromJson(rgba8)
|
||||||
var img = Rgba8Image.fromJson(rgba8)
|
for (f in filters.map(ImageFilter::fromJson)) {
|
||||||
for (f in filters.map(ImageFilter::fromJson)) {
|
img = f.apply(img)
|
||||||
img = f.apply(img)
|
}
|
||||||
}
|
result.success(img.toJson())
|
||||||
result.success(img.toJson())
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun method(
|
private fun method(
|
||||||
fileUrl: String,
|
fileUrl: String, headers: Map<String, String>?, filename: String,
|
||||||
headers: Map<String, String>?,
|
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, method: String,
|
||||||
filename: String,
|
result: MethodChannel.Result, onIntent: ((Intent) -> Unit)? = null
|
||||||
maxWidth: Int,
|
) {
|
||||||
maxHeight: Int,
|
val intent = Intent(context, ImageProcessorService::class.java).apply {
|
||||||
isSaveToServer: Boolean,
|
putExtra(ImageProcessorService.EXTRA_METHOD, method)
|
||||||
method: String,
|
putExtra(ImageProcessorService.EXTRA_FILE_URL, fileUrl)
|
||||||
result: MethodChannel.Result,
|
putExtra(ImageProcessorService.EXTRA_HEADERS,
|
||||||
onIntent: ((Intent) -> Unit)? = null
|
headers?.let { HashMap(it) })
|
||||||
) {
|
putExtra(ImageProcessorService.EXTRA_FILENAME, filename)
|
||||||
val intent = Intent(context, ImageProcessorService::class.java).apply {
|
putExtra(ImageProcessorService.EXTRA_MAX_WIDTH, maxWidth)
|
||||||
putExtra(ImageProcessorService.EXTRA_METHOD, method)
|
putExtra(ImageProcessorService.EXTRA_MAX_HEIGHT, maxHeight)
|
||||||
putExtra(ImageProcessorService.EXTRA_FILE_URL, fileUrl)
|
putExtra(
|
||||||
putExtra(ImageProcessorService.EXTRA_HEADERS,
|
ImageProcessorService.EXTRA_IS_SAVE_TO_SERVER, isSaveToServer
|
||||||
headers?.let { HashMap(it) })
|
)
|
||||||
putExtra(ImageProcessorService.EXTRA_FILENAME, filename)
|
onIntent?.invoke(this)
|
||||||
putExtra(ImageProcessorService.EXTRA_MAX_WIDTH, maxWidth)
|
}
|
||||||
putExtra(ImageProcessorService.EXTRA_MAX_HEIGHT, maxHeight)
|
ContextCompat.startForegroundService(context, intent)
|
||||||
putExtra(
|
result.success(null)
|
||||||
ImageProcessorService.EXTRA_IS_SAVE_TO_SERVER, isSaveToServer
|
}
|
||||||
)
|
|
||||||
onIntent?.invoke(this)
|
|
||||||
}
|
|
||||||
ContextCompat.startForegroundService(context, intent)
|
|
||||||
result.success(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
private var eventSink: EventChannel.EventSink? = null
|
private var eventSink: EventChannel.EventSink? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface ImageFilter {
|
internal interface ImageFilter {
|
||||||
companion object {
|
companion object {
|
||||||
fun fromJson(json: Map<String, Any>): ImageFilter {
|
fun fromJson(json: Map<String, Any>): ImageFilter {
|
||||||
return when (json["type"]) {
|
return when (json["type"]) {
|
||||||
"brightness" -> Brightness((json["weight"] as Double).toFloat())
|
"brightness" -> Brightness((json["weight"] as Double).toFloat())
|
||||||
"contrast" -> Contrast((json["weight"] as Double).toFloat())
|
"contrast" -> Contrast((json["weight"] as Double).toFloat())
|
||||||
"whitePoint" -> WhitePoint((json["weight"] as Double).toFloat())
|
"whitePoint" -> WhitePoint((json["weight"] as Double).toFloat())
|
||||||
"blackPoint" -> BlackPoint((json["weight"] as Double).toFloat())
|
"blackPoint" -> BlackPoint((json["weight"] as Double).toFloat())
|
||||||
"saturation" -> Saturation((json["weight"] as Double).toFloat())
|
"saturation" -> Saturation((json["weight"] as Double).toFloat())
|
||||||
"warmth" -> Warmth((json["weight"] as Double).toFloat())
|
"warmth" -> Warmth((json["weight"] as Double).toFloat())
|
||||||
"tint" -> Tint((json["weight"] as Double).toFloat())
|
"tint" -> Tint((json["weight"] as Double).toFloat())
|
||||||
"orientation" -> Orientation(json["degree"] as Int)
|
"orientation" -> Orientation(json["degree"] as Int)
|
||||||
"crop" -> Crop(
|
"crop" -> Crop(
|
||||||
json["top"] as Double,
|
json["top"] as Double, json["left"] as Double,
|
||||||
json["left"] as Double,
|
json["bottom"] as Double, json["right"] as Double
|
||||||
json["bottom"] as Double,
|
)
|
||||||
json["right"] as Double
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> throw IllegalArgumentException(
|
else -> throw IllegalArgumentException(
|
||||||
"Unknown type: ${json["type"]}"
|
"Unknown type: ${json["type"]}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun apply(rgba8: Rgba8Image): Rgba8Image
|
fun apply(rgba8: Rgba8Image): Rgba8Image
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,16 +1,16 @@
|
||||||
package com.nkming.nc_photos.np_platform_image_processor
|
package com.nkming.nc_photos.np_platform_image_processor
|
||||||
|
|
||||||
internal interface K {
|
internal interface K {
|
||||||
companion object {
|
companion object {
|
||||||
const val LIB_ID = "com.nkming.nc_photos.np_platform_image_processor"
|
const val LIB_ID = "com.nkming.nc_photos.np_platform_image_processor"
|
||||||
|
|
||||||
const val IMAGE_PROCESSOR_SERVICE_NOTIFICATION_ID = 5000
|
const val IMAGE_PROCESSOR_SERVICE_NOTIFICATION_ID = 5000
|
||||||
const val IMAGE_PROCESSOR_SERVICE_RESULT_NOTIFICATION_ID = 5001
|
const val IMAGE_PROCESSOR_SERVICE_RESULT_NOTIFICATION_ID = 5001
|
||||||
const val IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID = 5002
|
const val IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID = 5002
|
||||||
|
|
||||||
const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT =
|
const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT =
|
||||||
"${LIB_ID}.ACTION_SHOW_IMAGE_PROCESSOR_RESULT"
|
"${LIB_ID}.ACTION_SHOW_IMAGE_PROCESSOR_RESULT"
|
||||||
|
|
||||||
const val EXTRA_IMAGE_RESULT_URI = "${LIB_ID}.EXTRA_IMAGE_RESULT_URI"
|
const val EXTRA_IMAGE_RESULT_URI = "${LIB_ID}.EXTRA_IMAGE_RESULT_URI"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ package com.nkming.nc_photos.np_platform_image_processor
|
||||||
|
|
||||||
// To be removed
|
// To be removed
|
||||||
internal interface NativeEvent {
|
internal interface NativeEvent {
|
||||||
fun getId(): String
|
fun getId(): String
|
||||||
fun getData(): String? = null
|
fun getData(): String? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ImageProcessorUploadSuccessEvent : NativeEvent {
|
internal class ImageProcessorUploadSuccessEvent : NativeEvent {
|
||||||
companion object {
|
companion object {
|
||||||
const val id = "ImageProcessorUploadSuccessEvent"
|
const val id = "ImageProcessorUploadSuccessEvent"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getId() = id
|
override fun getId() = id
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,68 +5,68 @@ import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
internal class NativeEventChannelHandler : MethodChannel.MethodCallHandler,
|
internal class NativeEventChannelHandler : MethodChannel.MethodCallHandler,
|
||||||
EventChannel.StreamHandler {
|
EventChannel.StreamHandler {
|
||||||
companion object {
|
companion object {
|
||||||
const val EVENT_CHANNEL = "${K.LIB_ID}/native_event"
|
const val EVENT_CHANNEL = "${K.LIB_ID}/native_event"
|
||||||
const val METHOD_CHANNEL = "${K.LIB_ID}/native_event_method"
|
const val METHOD_CHANNEL = "${K.LIB_ID}/native_event_method"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fire native events on the native side
|
* Fire native events on the native side
|
||||||
*/
|
*/
|
||||||
fun fire(eventObj: NativeEvent) {
|
fun fire(eventObj: NativeEvent) {
|
||||||
synchronized(eventSinks) {
|
synchronized(eventSinks) {
|
||||||
for (s in eventSinks.values) {
|
for (s in eventSinks.values) {
|
||||||
s.success(buildMap {
|
s.success(buildMap {
|
||||||
put("event", eventObj.getId())
|
put("event", eventObj.getId())
|
||||||
eventObj.getData()?.also { put("data", it) }
|
eventObj.getData()?.also { put("data", it) }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val eventSinks = mutableMapOf<Int, EventChannel.EventSink>()
|
private val eventSinks = mutableMapOf<Int, EventChannel.EventSink>()
|
||||||
private var nextId = 0
|
private var nextId = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"fire" -> {
|
"fire" -> {
|
||||||
try {
|
try {
|
||||||
fire(
|
fire(
|
||||||
call.argument("event")!!, call.argument("data"), result
|
call.argument("event")!!, call.argument("data"), result
|
||||||
)
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
||||||
synchronized(eventSinks) {
|
synchronized(eventSinks) {
|
||||||
eventSinks[id] = events
|
eventSinks[id] = events
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancel(arguments: Any?) {
|
override fun onCancel(arguments: Any?) {
|
||||||
synchronized(eventSinks) {
|
synchronized(eventSinks) {
|
||||||
eventSinks.remove(id)
|
eventSinks.remove(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fire(
|
private fun fire(
|
||||||
event: String, data: String?, result: MethodChannel.Result
|
event: String, data: String?, result: MethodChannel.Result
|
||||||
) {
|
) {
|
||||||
synchronized(eventSinks) {
|
synchronized(eventSinks) {
|
||||||
for (s in eventSinks.values) {
|
for (s in eventSinks.values) {
|
||||||
s.success(buildMap {
|
s.success(buildMap {
|
||||||
put("event", event)
|
put("event", event)
|
||||||
if (data != null) put("data", data)
|
if (data != null) put("data", data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val id = nextId++
|
private val id = nextId++
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,52 +6,51 @@ import io.flutter.plugin.common.EventChannel
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
class NpPlatformImageProcessorPlugin : FlutterPlugin {
|
class NpPlatformImageProcessorPlugin : FlutterPlugin {
|
||||||
companion object {
|
companion object {
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("np_platform_image_processor")
|
System.loadLibrary("np_platform_image_processor")
|
||||||
}
|
}
|
||||||
|
|
||||||
const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT =
|
const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT =
|
||||||
K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT
|
K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT
|
||||||
const val EXTRA_IMAGE_RESULT_URI =
|
const val EXTRA_IMAGE_RESULT_URI = K.EXTRA_IMAGE_RESULT_URI
|
||||||
K.EXTRA_IMAGE_RESULT_URI
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttachedToEngine(
|
override fun onAttachedToEngine(
|
||||||
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
|
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
|
||||||
) {
|
) {
|
||||||
imageProcessorMethodChannel = MethodChannel(
|
imageProcessorMethodChannel = MethodChannel(
|
||||||
flutterPluginBinding.binaryMessenger,
|
flutterPluginBinding.binaryMessenger,
|
||||||
ImageProcessorChannelHandler.METHOD_CHANNEL
|
ImageProcessorChannelHandler.METHOD_CHANNEL
|
||||||
)
|
)
|
||||||
imageProcessorMethodChannel.setMethodCallHandler(
|
imageProcessorMethodChannel.setMethodCallHandler(
|
||||||
ImageProcessorChannelHandler(
|
ImageProcessorChannelHandler(
|
||||||
flutterPluginBinding.applicationContext
|
flutterPluginBinding.applicationContext
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val nativeEventHandler = NativeEventChannelHandler()
|
val nativeEventHandler = NativeEventChannelHandler()
|
||||||
nativeEventChannel = EventChannel(
|
nativeEventChannel = EventChannel(
|
||||||
flutterPluginBinding.binaryMessenger,
|
flutterPluginBinding.binaryMessenger,
|
||||||
NativeEventChannelHandler.EVENT_CHANNEL
|
NativeEventChannelHandler.EVENT_CHANNEL
|
||||||
)
|
)
|
||||||
nativeEventChannel.setStreamHandler(nativeEventHandler)
|
nativeEventChannel.setStreamHandler(nativeEventHandler)
|
||||||
nativeEventMethodChannel = MethodChannel(
|
nativeEventMethodChannel = MethodChannel(
|
||||||
flutterPluginBinding.binaryMessenger,
|
flutterPluginBinding.binaryMessenger,
|
||||||
NativeEventChannelHandler.METHOD_CHANNEL
|
NativeEventChannelHandler.METHOD_CHANNEL
|
||||||
)
|
)
|
||||||
nativeEventMethodChannel.setMethodCallHandler(nativeEventHandler)
|
nativeEventMethodChannel.setMethodCallHandler(nativeEventHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromEngine(
|
override fun onDetachedFromEngine(
|
||||||
@NonNull binding: FlutterPlugin.FlutterPluginBinding
|
@NonNull binding: FlutterPlugin.FlutterPluginBinding
|
||||||
) {
|
) {
|
||||||
imageProcessorMethodChannel.setMethodCallHandler(null)
|
imageProcessorMethodChannel.setMethodCallHandler(null)
|
||||||
nativeEventChannel.setStreamHandler(null)
|
nativeEventChannel.setStreamHandler(null)
|
||||||
nativeEventMethodChannel.setMethodCallHandler(null)
|
nativeEventMethodChannel.setMethodCallHandler(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var imageProcessorMethodChannel: MethodChannel
|
private lateinit var imageProcessorMethodChannel: MethodChannel
|
||||||
private lateinit var nativeEventChannel: EventChannel
|
private lateinit var nativeEventChannel: EventChannel
|
||||||
private lateinit var nativeEventMethodChannel: MethodChannel
|
private lateinit var nativeEventMethodChannel: MethodChannel
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,79 +10,62 @@ import com.nkming.nc_photos.np_android_core.logI
|
||||||
import com.nkming.nc_photos.np_android_core.use
|
import com.nkming.nc_photos.np_android_core.use
|
||||||
|
|
||||||
internal class ArbitraryStyleTransfer(
|
internal class ArbitraryStyleTransfer(
|
||||||
context: Context,
|
context: Context, maxWidth: Int, maxHeight: Int, styleUri: Uri,
|
||||||
maxWidth: Int,
|
weight: Float
|
||||||
maxHeight: Int,
|
|
||||||
styleUri: Uri,
|
|
||||||
weight: Float
|
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "ArbitraryStyleTransfer"
|
const val TAG = "ArbitraryStyleTransfer"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun infer(imageUri: Uri): Bitmap {
|
fun infer(imageUri: Uri): Bitmap {
|
||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
val rgb8Image = BitmapUtil.loadImage(
|
val rgb8Image = BitmapUtil.loadImage(
|
||||||
context,
|
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
||||||
imageUri,
|
isAllowSwapSide = true, shouldUpscale = false,
|
||||||
maxWidth,
|
shouldFixOrientation = true
|
||||||
maxHeight,
|
).use {
|
||||||
BitmapResizeMethod.FIT,
|
width = it.width
|
||||||
isAllowSwapSide = true,
|
height = it.height
|
||||||
shouldUpscale = false,
|
TfLiteHelper.bitmapToRgb8Array(it)
|
||||||
shouldFixOrientation = true
|
}
|
||||||
).use {
|
val rgb8Style = BitmapUtil.loadImage(
|
||||||
width = it.width
|
context, styleUri, 256, 256, BitmapResizeMethod.FILL,
|
||||||
height = it.height
|
isAllowSwapSide = false, shouldUpscale = true
|
||||||
TfLiteHelper.bitmapToRgb8Array(it)
|
).use {
|
||||||
}
|
val styleBitmap = if (it.width != 256 || it.height != 256) {
|
||||||
val rgb8Style = BitmapUtil.loadImage(
|
val x = (it.width - 256) / 2
|
||||||
context,
|
val y = (it.height - 256) / 2
|
||||||
styleUri,
|
logI(
|
||||||
256,
|
TAG,
|
||||||
256,
|
"[infer] Resize and crop style image: ${it.width}x${it.height} -> 256x256 ($x, $y)"
|
||||||
BitmapResizeMethod.FILL,
|
)
|
||||||
isAllowSwapSide = false,
|
// crop
|
||||||
shouldUpscale = true
|
Bitmap.createBitmap(it, x, y, 256, 256)
|
||||||
).use {
|
} else {
|
||||||
val styleBitmap = if (it.width != 256 || it.height != 256) {
|
it
|
||||||
val x = (it.width - 256) / 2
|
}
|
||||||
val y = (it.height - 256) / 2
|
styleBitmap.use {
|
||||||
logI(
|
TfLiteHelper.bitmapToRgb8Array(styleBitmap)
|
||||||
TAG,
|
}
|
||||||
"[infer] Resize and crop style image: ${it.width}x${it.height} -> 256x256 ($x, $y)"
|
}
|
||||||
)
|
val am = context.assets
|
||||||
// crop
|
|
||||||
Bitmap.createBitmap(it, x, y, 256, 256)
|
|
||||||
} else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
styleBitmap.use {
|
|
||||||
TfLiteHelper.bitmapToRgb8Array(styleBitmap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val am = context.assets
|
|
||||||
|
|
||||||
return inferNative(
|
return inferNative(
|
||||||
am, rgb8Image, width, height, rgb8Style, weight
|
am, rgb8Image, width, height, rgb8Style, weight
|
||||||
).let {
|
).let {
|
||||||
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
|
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun inferNative(
|
private external fun inferNative(
|
||||||
am: AssetManager,
|
am: AssetManager, image: ByteArray, width: Int, height: Int,
|
||||||
image: ByteArray,
|
style: ByteArray, weight: Float
|
||||||
width: Int,
|
): ByteArray
|
||||||
height: Int,
|
|
||||||
style: ByteArray,
|
|
||||||
weight: Float
|
|
||||||
): ByteArray
|
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
private val maxWidth = maxWidth
|
private val maxWidth = maxWidth
|
||||||
private val maxHeight = maxHeight
|
private val maxHeight = maxHeight
|
||||||
private val styleUri = styleUri
|
private val styleUri = styleUri
|
||||||
private val weight = weight
|
private val weight = weight
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
|
||||||
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
||||||
|
|
||||||
internal class BlackPoint(val weight: Float) : ImageFilter {
|
internal class BlackPoint(val weight: Float) : ImageFilter {
|
||||||
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
||||||
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
||||||
rgba8.width,
|
rgba8.width, rgba8.height
|
||||||
rgba8.height
|
)
|
||||||
)
|
|
||||||
|
|
||||||
private external fun applyNative(
|
private external fun applyNative(
|
||||||
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
||||||
): ByteArray
|
): ByteArray
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
|
||||||
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
||||||
|
|
||||||
internal class Brightness(val weight: Float) : ImageFilter {
|
internal class Brightness(val weight: Float) : ImageFilter {
|
||||||
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
||||||
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
||||||
rgba8.width,
|
rgba8.width, rgba8.height
|
||||||
rgba8.height
|
)
|
||||||
)
|
|
||||||
|
|
||||||
private external fun applyNative(
|
private external fun applyNative(
|
||||||
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
||||||
): ByteArray
|
): ByteArray
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
|
||||||
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
||||||
|
|
||||||
internal class Contrast(val weight: Float) : ImageFilter {
|
internal class Contrast(val weight: Float) : ImageFilter {
|
||||||
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
||||||
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
||||||
rgba8.width,
|
rgba8.width, rgba8.height
|
||||||
rgba8.height
|
)
|
||||||
)
|
|
||||||
|
|
||||||
private external fun applyNative(
|
private external fun applyNative(
|
||||||
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
||||||
): ByteArray
|
): ByteArray
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
|
||||||
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
||||||
|
|
||||||
internal class Cool(val weight: Float) : ImageFilter {
|
internal class Cool(val weight: Float) : ImageFilter {
|
||||||
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
||||||
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
||||||
rgba8.width,
|
rgba8.width, rgba8.height
|
||||||
rgba8.height
|
)
|
||||||
)
|
|
||||||
|
|
||||||
private external fun applyNative(
|
private external fun applyNative(
|
||||||
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
||||||
): ByteArray
|
): ByteArray
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,27 +5,22 @@ import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
||||||
import java.lang.Integer.max
|
import java.lang.Integer.max
|
||||||
|
|
||||||
internal class Crop(
|
internal class Crop(
|
||||||
val top: Double, val left: Double, val bottom: Double, val right: Double
|
val top: Double, val left: Double, val bottom: Double, val right: Double
|
||||||
) : ImageFilter {
|
) : ImageFilter {
|
||||||
override fun apply(rgba8: Rgba8Image): Rgba8Image {
|
override fun apply(rgba8: Rgba8Image): Rgba8Image {
|
||||||
// prevent w/h == 0
|
// prevent w/h == 0
|
||||||
val width = max((rgba8.width * (right - left)).toInt(), 1)
|
val width = max((rgba8.width * (right - left)).toInt(), 1)
|
||||||
val height = max((rgba8.height * (bottom - top)).toInt(), 1)
|
val height = max((rgba8.height * (bottom - top)).toInt(), 1)
|
||||||
val top = (rgba8.height * top).toInt()
|
val top = (rgba8.height * top).toInt()
|
||||||
val left = (rgba8.width * left).toInt()
|
val left = (rgba8.width * left).toInt()
|
||||||
val data = applyNative(
|
val data = applyNative(
|
||||||
rgba8.pixel, rgba8.width, rgba8.height, top, left, width, height
|
rgba8.pixel, rgba8.width, rgba8.height, top, left, width, height
|
||||||
)
|
)
|
||||||
return Rgba8Image(data, width, height)
|
return Rgba8Image(data, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun applyNative(
|
private external fun applyNative(
|
||||||
rgba8: ByteArray,
|
rgba8: ByteArray, width: Int, height: Int, top: Int, left: Int,
|
||||||
width: Int,
|
dstWidth: Int, dstHeight: Int
|
||||||
height: Int,
|
): ByteArray
|
||||||
top: Int,
|
|
||||||
left: Int,
|
|
||||||
dstWidth: Int,
|
|
||||||
dstHeight: Int
|
|
||||||
): ByteArray
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,79 +16,66 @@ import com.nkming.nc_photos.np_android_core.use
|
||||||
* See: https://github.com/tensorflow/models/tree/master/research/deeplab
|
* See: https://github.com/tensorflow/models/tree/master/research/deeplab
|
||||||
*/
|
*/
|
||||||
internal class DeepLab3Portrait(
|
internal class DeepLab3Portrait(
|
||||||
context: Context, maxWidth: Int, maxHeight: Int, radius: Int
|
context: Context, maxWidth: Int, maxHeight: Int, radius: Int
|
||||||
) {
|
) {
|
||||||
fun infer(imageUri: Uri): Bitmap {
|
fun infer(imageUri: Uri): Bitmap {
|
||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
val rgb8Image = BitmapUtil.loadImage(
|
val rgb8Image = BitmapUtil.loadImage(
|
||||||
context,
|
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
||||||
imageUri,
|
isAllowSwapSide = true, shouldUpscale = false,
|
||||||
maxWidth,
|
shouldFixOrientation = true
|
||||||
maxHeight,
|
).use {
|
||||||
BitmapResizeMethod.FIT,
|
width = it.width
|
||||||
isAllowSwapSide = true,
|
height = it.height
|
||||||
shouldUpscale = false,
|
TfLiteHelper.bitmapToRgb8Array(it)
|
||||||
shouldFixOrientation = true
|
}
|
||||||
).use {
|
val am = context.assets
|
||||||
width = it.width
|
|
||||||
height = it.height
|
|
||||||
TfLiteHelper.bitmapToRgb8Array(it)
|
|
||||||
}
|
|
||||||
val am = context.assets
|
|
||||||
|
|
||||||
return inferNative(am, rgb8Image, width, height, radius).let {
|
return inferNative(am, rgb8Image, width, height, radius).let {
|
||||||
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
|
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun inferNative(
|
private external fun inferNative(
|
||||||
am: AssetManager, image: ByteArray, width: Int, height: Int, radius: Int
|
am: AssetManager, image: ByteArray, width: Int, height: Int, radius: Int
|
||||||
): ByteArray
|
): ByteArray
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
private val maxWidth = maxWidth
|
private val maxWidth = maxWidth
|
||||||
private val maxHeight = maxHeight
|
private val maxHeight = maxHeight
|
||||||
private val radius = radius
|
private val radius = radius
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeepLab3ColorPop(
|
class DeepLab3ColorPop(
|
||||||
context: Context, maxWidth: Int, maxHeight: Int, weight: Float
|
context: Context, maxWidth: Int, maxHeight: Int, weight: Float
|
||||||
) {
|
) {
|
||||||
fun infer(imageUri: Uri): Bitmap {
|
fun infer(imageUri: Uri): Bitmap {
|
||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
val rgb8Image = BitmapUtil.loadImage(
|
val rgb8Image = BitmapUtil.loadImage(
|
||||||
context,
|
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
||||||
imageUri,
|
isAllowSwapSide = true, shouldUpscale = false,
|
||||||
maxWidth,
|
shouldFixOrientation = true
|
||||||
maxHeight,
|
).use {
|
||||||
BitmapResizeMethod.FIT,
|
width = it.width
|
||||||
isAllowSwapSide = true,
|
height = it.height
|
||||||
shouldUpscale = false,
|
TfLiteHelper.bitmapToRgb8Array(it)
|
||||||
shouldFixOrientation = true
|
}
|
||||||
).use {
|
val am = context.assets
|
||||||
width = it.width
|
|
||||||
height = it.height
|
|
||||||
TfLiteHelper.bitmapToRgb8Array(it)
|
|
||||||
}
|
|
||||||
val am = context.assets
|
|
||||||
|
|
||||||
return inferNative(am, rgb8Image, width, height, weight).let {
|
return inferNative(am, rgb8Image, width, height, weight).let {
|
||||||
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
|
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun inferNative(
|
private external fun inferNative(
|
||||||
am: AssetManager,
|
am: AssetManager, image: ByteArray, width: Int, height: Int,
|
||||||
image: ByteArray,
|
weight: Float
|
||||||
width: Int,
|
): ByteArray
|
||||||
height: Int,
|
|
||||||
weight: Float
|
|
||||||
): ByteArray
|
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
private val maxWidth = maxWidth
|
private val maxWidth = maxWidth
|
||||||
private val maxHeight = maxHeight
|
private val maxHeight = maxHeight
|
||||||
private val weight = weight
|
private val weight = weight
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,35 +9,30 @@ import com.nkming.nc_photos.np_android_core.BitmapUtil
|
||||||
import com.nkming.nc_photos.np_android_core.use
|
import com.nkming.nc_photos.np_android_core.use
|
||||||
|
|
||||||
internal class Esrgan(context: Context, maxWidth: Int, maxHeight: Int) {
|
internal class Esrgan(context: Context, maxWidth: Int, maxHeight: Int) {
|
||||||
fun infer(imageUri: Uri): Bitmap {
|
fun infer(imageUri: Uri): Bitmap {
|
||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
val rgb8Image = BitmapUtil.loadImage(
|
val rgb8Image = BitmapUtil.loadImage(
|
||||||
context,
|
context, imageUri, maxWidth / 4, maxHeight / 4,
|
||||||
imageUri,
|
BitmapResizeMethod.FIT, isAllowSwapSide = true,
|
||||||
maxWidth / 4,
|
shouldUpscale = false, shouldFixOrientation = true
|
||||||
maxHeight / 4,
|
).use {
|
||||||
BitmapResizeMethod.FIT,
|
width = it.width
|
||||||
isAllowSwapSide = true,
|
height = it.height
|
||||||
shouldUpscale = false,
|
TfLiteHelper.bitmapToRgb8Array(it)
|
||||||
shouldFixOrientation = true
|
}
|
||||||
).use {
|
val am = context.assets
|
||||||
width = it.width
|
|
||||||
height = it.height
|
|
||||||
TfLiteHelper.bitmapToRgb8Array(it)
|
|
||||||
}
|
|
||||||
val am = context.assets
|
|
||||||
|
|
||||||
return inferNative(am, rgb8Image, width, height).let {
|
return inferNative(am, rgb8Image, width, height).let {
|
||||||
TfLiteHelper.rgb8ArrayToBitmap(it, width * 4, height * 4)
|
TfLiteHelper.rgb8ArrayToBitmap(it, width * 4, height * 4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun inferNative(
|
private external fun inferNative(
|
||||||
am: AssetManager, image: ByteArray, width: Int, height: Int
|
am: AssetManager, image: ByteArray, width: Int, height: Int
|
||||||
): ByteArray
|
): ByteArray
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
private val maxWidth = maxWidth
|
private val maxWidth = maxWidth
|
||||||
private val maxHeight = maxHeight
|
private val maxHeight = maxHeight
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,34 +10,29 @@ import com.nkming.nc_photos.np_android_core.use
|
||||||
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
||||||
|
|
||||||
internal class ImageFilterProcessor(
|
internal class ImageFilterProcessor(
|
||||||
context: Context, maxWidth: Int, maxHeight: Int, filters: List<ImageFilter>
|
context: Context, maxWidth: Int, maxHeight: Int, filters: List<ImageFilter>
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "ImageFilterProcessor"
|
const val TAG = "ImageFilterProcessor"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun apply(imageUri: Uri): Bitmap {
|
fun apply(imageUri: Uri): Bitmap {
|
||||||
var img = BitmapUtil.loadImage(
|
var img = BitmapUtil.loadImage(
|
||||||
context,
|
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
||||||
imageUri,
|
isAllowSwapSide = true, shouldUpscale = false,
|
||||||
maxWidth,
|
shouldFixOrientation = true
|
||||||
maxHeight,
|
).use {
|
||||||
BitmapResizeMethod.FIT,
|
Rgba8Image(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height)
|
||||||
isAllowSwapSide = true,
|
}
|
||||||
shouldUpscale = false,
|
|
||||||
shouldFixOrientation = true
|
|
||||||
).use {
|
|
||||||
Rgba8Image(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (f in filters) {
|
for (f in filters) {
|
||||||
img = f.apply(img)
|
img = f.apply(img)
|
||||||
}
|
}
|
||||||
return img.toBitmap()
|
return img.toBitmap()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
private val maxWidth = maxWidth
|
private val maxWidth = maxWidth
|
||||||
private val maxHeight = maxHeight
|
private val maxHeight = maxHeight
|
||||||
private val filters = filters
|
private val filters = filters
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,75 +8,75 @@ import com.nkming.nc_photos.np_android_core.logI
|
||||||
* that the viewer will rotate the image when displaying the image
|
* that the viewer will rotate the image when displaying the image
|
||||||
*/
|
*/
|
||||||
internal class LosslessRotator {
|
internal class LosslessRotator {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "LosslessRotator"
|
const val TAG = "LosslessRotator"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the Orientation tag in @a dstExif according to the value in
|
* Set the Orientation tag in @a dstExif according to the value in
|
||||||
* @a srcExif
|
* @a srcExif
|
||||||
*
|
*
|
||||||
* @param degree Either 0, 90, 180, -90 or -180
|
* @param degree Either 0, 90, 180, -90 or -180
|
||||||
* @param srcExif ExifInterface of the src file
|
* @param srcExif ExifInterface of the src file
|
||||||
* @param dstExif ExifInterface of the dst file
|
* @param dstExif ExifInterface of the dst file
|
||||||
*/
|
*/
|
||||||
operator fun invoke(
|
operator fun invoke(
|
||||||
degree: Int, srcExif: ExifInterface, dstExif: ExifInterface
|
degree: Int, srcExif: ExifInterface, dstExif: ExifInterface
|
||||||
) {
|
) {
|
||||||
assert(degree in listOf(0, 90, 180, -90, -180))
|
assert(degree in listOf(0, 90, 180, -90, -180))
|
||||||
val srcOrientation =
|
val srcOrientation =
|
||||||
srcExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
|
srcExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
|
||||||
val dstOrientation = rotateExifOrientationValue(srcOrientation, degree)
|
val dstOrientation = rotateExifOrientationValue(srcOrientation, degree)
|
||||||
logI(TAG, "[invoke] $degree, $srcOrientation -> $dstOrientation")
|
logI(TAG, "[invoke] $degree, $srcOrientation -> $dstOrientation")
|
||||||
dstExif.setAttribute(
|
dstExif.setAttribute(
|
||||||
ExifInterface.TAG_ORIENTATION, dstOrientation.toString()
|
ExifInterface.TAG_ORIENTATION, dstOrientation.toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new orientation representing the resulting value after rotating
|
* Return a new orientation representing the resulting value after rotating
|
||||||
* @a value
|
* @a value
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value
|
||||||
* @param degree Either 0, 90, 180, -90 or -180
|
* @param degree Either 0, 90, 180, -90 or -180
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private fun rotateExifOrientationValue(value: Int, degree: Int): Int {
|
private fun rotateExifOrientationValue(value: Int, degree: Int): Int {
|
||||||
if (degree == 0) {
|
if (degree == 0) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
var newValue = rotateExifOrientationValue90Ccw(value)
|
var newValue = rotateExifOrientationValue90Ccw(value)
|
||||||
if (degree == 90) {
|
if (degree == 90) {
|
||||||
return newValue
|
return newValue
|
||||||
}
|
}
|
||||||
newValue = rotateExifOrientationValue90Ccw(newValue)
|
newValue = rotateExifOrientationValue90Ccw(newValue)
|
||||||
if (degree == 180 || degree == -180) {
|
if (degree == 180 || degree == -180) {
|
||||||
return newValue
|
return newValue
|
||||||
}
|
}
|
||||||
newValue = rotateExifOrientationValue90Ccw(newValue)
|
newValue = rotateExifOrientationValue90Ccw(newValue)
|
||||||
return newValue
|
return newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new orientation representing the resulting value after rotating
|
* Return a new orientation representing the resulting value after rotating
|
||||||
* @a value for 90 degree CCW
|
* @a value for 90 degree CCW
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private fun rotateExifOrientationValue90Ccw(value: Int): Int {
|
private fun rotateExifOrientationValue90Ccw(value: Int): Int {
|
||||||
return when (value) {
|
return when (value) {
|
||||||
0, 1 -> 8
|
0, 1 -> 8
|
||||||
8 -> 3
|
8 -> 3
|
||||||
3 -> 6
|
3 -> 6
|
||||||
6 -> 1
|
6 -> 1
|
||||||
2 -> 7
|
2 -> 7
|
||||||
7 -> 4
|
7 -> 4
|
||||||
4 -> 5
|
4 -> 5
|
||||||
5 -> 2
|
5 -> 2
|
||||||
else -> throw IllegalArgumentException(
|
else -> throw IllegalArgumentException(
|
||||||
"Invalid EXIF Orientation value: $value"
|
"Invalid EXIF Orientation value: $value"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,35 +9,30 @@ import com.nkming.nc_photos.np_android_core.BitmapUtil
|
||||||
import com.nkming.nc_photos.np_android_core.use
|
import com.nkming.nc_photos.np_android_core.use
|
||||||
|
|
||||||
internal class NeurOp(context: Context, maxWidth: Int, maxHeight: Int) {
|
internal class NeurOp(context: Context, maxWidth: Int, maxHeight: Int) {
|
||||||
fun infer(imageUri: Uri): Bitmap {
|
fun infer(imageUri: Uri): Bitmap {
|
||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
val rgb8Image = BitmapUtil.loadImage(
|
val rgb8Image = BitmapUtil.loadImage(
|
||||||
context,
|
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
||||||
imageUri,
|
isAllowSwapSide = true, shouldUpscale = false,
|
||||||
maxWidth,
|
shouldFixOrientation = true
|
||||||
maxHeight,
|
).use {
|
||||||
BitmapResizeMethod.FIT,
|
width = it.width
|
||||||
isAllowSwapSide = true,
|
height = it.height
|
||||||
shouldUpscale = false,
|
TfLiteHelper.bitmapToRgb8Array(it)
|
||||||
shouldFixOrientation = true
|
}
|
||||||
).use {
|
val am = context.assets
|
||||||
width = it.width
|
|
||||||
height = it.height
|
|
||||||
TfLiteHelper.bitmapToRgb8Array(it)
|
|
||||||
}
|
|
||||||
val am = context.assets
|
|
||||||
|
|
||||||
return inferNative(am, rgb8Image, width, height).let {
|
return inferNative(am, rgb8Image, width, height).let {
|
||||||
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
|
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun inferNative(
|
private external fun inferNative(
|
||||||
am: AssetManager, image: ByteArray, width: Int, height: Int
|
am: AssetManager, image: ByteArray, width: Int, height: Int
|
||||||
): ByteArray
|
): ByteArray
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
private val maxWidth = maxWidth
|
private val maxWidth = maxWidth
|
||||||
private val maxHeight = maxHeight
|
private val maxHeight = maxHeight
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,15 @@ import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
internal class Orientation(val degree: Int) : ImageFilter {
|
internal class Orientation(val degree: Int) : ImageFilter {
|
||||||
override fun apply(rgba8: Rgba8Image): Rgba8Image {
|
override fun apply(rgba8: Rgba8Image): Rgba8Image {
|
||||||
val data = applyNative(rgba8.pixel, rgba8.width, rgba8.height, degree)
|
val data = applyNative(rgba8.pixel, rgba8.width, rgba8.height, degree)
|
||||||
return Rgba8Image(
|
return Rgba8Image(
|
||||||
data,
|
data, if (abs(degree) == 90) rgba8.height else rgba8.width,
|
||||||
if (abs(degree) == 90) rgba8.height else rgba8.width,
|
if (abs(degree) == 90) rgba8.width else rgba8.height
|
||||||
if (abs(degree) == 90) rgba8.width else rgba8.height
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private external fun applyNative(
|
private external fun applyNative(
|
||||||
rgba8: ByteArray, width: Int, height: Int, degree: Int
|
rgba8: ByteArray, width: Int, height: Int, degree: Int
|
||||||
): ByteArray
|
): ByteArray
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
|
||||||
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
||||||
|
|
||||||
internal class Saturation(val weight: Float) : ImageFilter {
|
internal class Saturation(val weight: Float) : ImageFilter {
|
||||||
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
||||||
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
||||||
rgba8.width,
|
rgba8.width, rgba8.height
|
||||||
rgba8.height
|
)
|
||||||
)
|
|
||||||
|
|
||||||
private external fun applyNative(
|
private external fun applyNative(
|
||||||
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
||||||
): ByteArray
|
): ByteArray
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,80 +5,80 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
|
||||||
import java.nio.IntBuffer
|
import java.nio.IntBuffer
|
||||||
|
|
||||||
internal interface TfLiteHelper {
|
internal interface TfLiteHelper {
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Convert an ARGB_8888 Android bitmap to a RGB8 byte array
|
* Convert an ARGB_8888 Android bitmap to a RGB8 byte array
|
||||||
*
|
*
|
||||||
* @param bitmap
|
* @param bitmap
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun bitmapToRgb8Array(bitmap: Bitmap): ByteArray {
|
fun bitmapToRgb8Array(bitmap: Bitmap): ByteArray {
|
||||||
val buffer = IntBuffer.allocate(bitmap.width * bitmap.height)
|
val buffer = IntBuffer.allocate(bitmap.width * bitmap.height)
|
||||||
bitmap.copyPixelsToBuffer(buffer)
|
bitmap.copyPixelsToBuffer(buffer)
|
||||||
val rgb8 = ByteArray(bitmap.width * bitmap.height * 3)
|
val rgb8 = ByteArray(bitmap.width * bitmap.height * 3)
|
||||||
buffer.array().forEachIndexed { i, it ->
|
buffer.array().forEachIndexed { i, it ->
|
||||||
run {
|
run {
|
||||||
rgb8[i * 3] = (it and 0xFF).toByte()
|
rgb8[i * 3] = (it and 0xFF).toByte()
|
||||||
rgb8[i * 3 + 1] = (it shr 8 and 0xFF).toByte()
|
rgb8[i * 3 + 1] = (it shr 8 and 0xFF).toByte()
|
||||||
rgb8[i * 3 + 2] = (it shr 16 and 0xFF).toByte()
|
rgb8[i * 3 + 2] = (it shr 16 and 0xFF).toByte()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rgb8
|
return rgb8
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an ARGB_8888 Android bitmap to a RGBA byte array
|
* Convert an ARGB_8888 Android bitmap to a RGBA byte array
|
||||||
*
|
*
|
||||||
* @param bitmap
|
* @param bitmap
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun bitmapToRgba8Array(bitmap: Bitmap): ByteArray {
|
fun bitmapToRgba8Array(bitmap: Bitmap): ByteArray {
|
||||||
return Rgba8Image.fromBitmap(bitmap).pixel
|
return Rgba8Image.fromBitmap(bitmap).pixel
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a RGB8 byte array to an ARGB_8888 Android bitmap
|
* Convert a RGB8 byte array to an ARGB_8888 Android bitmap
|
||||||
*
|
*
|
||||||
* @param rgb8
|
* @param rgb8
|
||||||
* @param width
|
* @param width
|
||||||
* @param height
|
* @param height
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
fun rgb8ArrayToBitmap(
|
fun rgb8ArrayToBitmap(
|
||||||
rgb8: ByteArray, width: Int, height: Int
|
rgb8: ByteArray, width: Int, height: Int
|
||||||
): Bitmap {
|
): Bitmap {
|
||||||
val buffer = IntBuffer.allocate(width * height)
|
val buffer = IntBuffer.allocate(width * height)
|
||||||
var i = 0
|
var i = 0
|
||||||
var pixel = 0
|
var pixel = 0
|
||||||
rgb8.forEach {
|
rgb8.forEach {
|
||||||
val value = it.toInt() and 0xFF
|
val value = it.toInt() and 0xFF
|
||||||
when (i++) {
|
when (i++) {
|
||||||
0 -> {
|
0 -> {
|
||||||
// A
|
// A
|
||||||
pixel = 0xFF shl 24
|
pixel = 0xFF shl 24
|
||||||
// R
|
// R
|
||||||
pixel = pixel or value
|
pixel = pixel or value
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
// G
|
// G
|
||||||
pixel = pixel or (value shl 8)
|
pixel = pixel or (value shl 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> {
|
2 -> {
|
||||||
// B
|
// B
|
||||||
pixel = pixel or (value shl 16)
|
pixel = pixel or (value shl 16)
|
||||||
|
|
||||||
buffer.put(pixel)
|
buffer.put(pixel)
|
||||||
i = 0
|
i = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer.rewind()
|
buffer.rewind()
|
||||||
val outputBitmap =
|
val outputBitmap =
|
||||||
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
outputBitmap.copyPixelsFromBuffer(buffer)
|
outputBitmap.copyPixelsFromBuffer(buffer)
|
||||||
return outputBitmap
|
return outputBitmap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
|
||||||
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
||||||
|
|
||||||
internal class Tint(val weight: Float) : ImageFilter {
|
internal class Tint(val weight: Float) : ImageFilter {
|
||||||
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
||||||
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
||||||
rgba8.width,
|
rgba8.width, rgba8.height
|
||||||
rgba8.height
|
)
|
||||||
)
|
|
||||||
|
|
||||||
private external fun applyNative(
|
private external fun applyNative(
|
||||||
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
||||||
): ByteArray
|
): ByteArray
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
|
||||||
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
||||||
|
|
||||||
internal class Warmth(val weight: Float) : ImageFilter {
|
internal class Warmth(val weight: Float) : ImageFilter {
|
||||||
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
||||||
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
||||||
rgba8.width,
|
rgba8.width, rgba8.height
|
||||||
rgba8.height
|
)
|
||||||
)
|
|
||||||
|
|
||||||
private external fun applyNative(
|
private external fun applyNative(
|
||||||
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
||||||
): ByteArray
|
): ByteArray
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
|
||||||
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
|
||||||
|
|
||||||
internal class WhitePoint(val weight: Float) : ImageFilter {
|
internal class WhitePoint(val weight: Float) : ImageFilter {
|
||||||
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
|
||||||
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
|
||||||
rgba8.width,
|
rgba8.width, rgba8.height
|
||||||
rgba8.height
|
)
|
||||||
)
|
|
||||||
|
|
||||||
private external fun applyNative(
|
private external fun applyNative(
|
||||||
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
rgba8: ByteArray, width: Int, height: Int, weight: Float
|
||||||
): ByteArray
|
): ByteArray
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,45 +9,34 @@ import com.nkming.nc_photos.np_android_core.BitmapUtil
|
||||||
import com.nkming.nc_photos.np_android_core.use
|
import com.nkming.nc_photos.np_android_core.use
|
||||||
|
|
||||||
internal class ZeroDce(
|
internal class ZeroDce(
|
||||||
context: Context,
|
context: Context, maxWidth: Int, maxHeight: Int, iteration: Int
|
||||||
maxWidth: Int,
|
|
||||||
maxHeight: Int,
|
|
||||||
iteration: Int
|
|
||||||
) {
|
) {
|
||||||
fun infer(imageUri: Uri): Bitmap {
|
fun infer(imageUri: Uri): Bitmap {
|
||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
val rgb8Image = BitmapUtil.loadImage(
|
val rgb8Image = BitmapUtil.loadImage(
|
||||||
context,
|
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
|
||||||
imageUri,
|
isAllowSwapSide = true, shouldUpscale = false,
|
||||||
maxWidth,
|
shouldFixOrientation = true
|
||||||
maxHeight,
|
).use {
|
||||||
BitmapResizeMethod.FIT,
|
width = it.width
|
||||||
isAllowSwapSide = true,
|
height = it.height
|
||||||
shouldUpscale = false,
|
TfLiteHelper.bitmapToRgb8Array(it)
|
||||||
shouldFixOrientation = true
|
}
|
||||||
).use {
|
val am = context.assets
|
||||||
width = it.width
|
|
||||||
height = it.height
|
|
||||||
TfLiteHelper.bitmapToRgb8Array(it)
|
|
||||||
}
|
|
||||||
val am = context.assets
|
|
||||||
|
|
||||||
return inferNative(am, rgb8Image, width, height, iteration).let {
|
return inferNative(am, rgb8Image, width, height, iteration).let {
|
||||||
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
|
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun inferNative(
|
private external fun inferNative(
|
||||||
am: AssetManager,
|
am: AssetManager, image: ByteArray, width: Int, height: Int,
|
||||||
image: ByteArray,
|
iteration: Int
|
||||||
width: Int,
|
): ByteArray
|
||||||
height: Int,
|
|
||||||
iteration: Int
|
|
||||||
): ByteArray
|
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
private val maxWidth = maxWidth
|
private val maxWidth = maxWidth
|
||||||
private val maxHeight = maxHeight
|
private val maxHeight = maxHeight
|
||||||
private val iteration = iteration
|
private val iteration = iteration
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,77 +18,76 @@ import io.flutter.plugin.common.MethodChannel
|
||||||
* fun unlock(lockId: Int): Unit
|
* fun unlock(lockId: Int): Unit
|
||||||
*/
|
*/
|
||||||
class LockChannelHandler : MethodChannel.MethodCallHandler {
|
class LockChannelHandler : MethodChannel.MethodCallHandler {
|
||||||
companion object {
|
companion object {
|
||||||
const val CHANNEL = "${K.LIB_ID}/lock"
|
const val CHANNEL = "${K.LIB_ID}/lock"
|
||||||
|
|
||||||
private val locks = mutableMapOf<Int, Boolean>()
|
private val locks = mutableMapOf<Int, Boolean>()
|
||||||
|
|
||||||
private const val TAG = "LockChannelHandler"
|
private const val TAG = "LockChannelHandler"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dismiss this handler instance
|
* Dismiss this handler instance
|
||||||
*
|
*
|
||||||
* All dangling locks locked via this instance will automatically be
|
* All dangling locks locked via this instance will automatically be
|
||||||
* unlocked
|
* unlocked
|
||||||
*/
|
*/
|
||||||
fun dismiss() {
|
fun dismiss() {
|
||||||
for (id in _lockedIds) {
|
for (id in _lockedIds) {
|
||||||
if (locks[id] == true) {
|
if (locks[id] == true) {
|
||||||
logW(TAG, "[dismiss] Automatically unlocking id: $id")
|
logW(TAG, "[dismiss] Automatically unlocking id: $id")
|
||||||
locks[id] = false
|
locks[id] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_lockedIds.clear()
|
_lockedIds.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"tryLock" -> {
|
"tryLock" -> {
|
||||||
try {
|
try {
|
||||||
tryLock(call.argument("lockId")!!, result)
|
tryLock(call.argument("lockId")!!, result)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"unlock" -> {
|
"unlock" -> {
|
||||||
try {
|
try {
|
||||||
unlock(call.argument("lockId")!!, result)
|
unlock(call.argument("lockId")!!, result)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryLock(lockId: Int, result: MethodChannel.Result) {
|
private fun tryLock(lockId: Int, result: MethodChannel.Result) {
|
||||||
if (locks[lockId] != true) {
|
if (locks[lockId] != true) {
|
||||||
locks[lockId] = true
|
locks[lockId] = true
|
||||||
_lockedIds.add(lockId)
|
_lockedIds.add(lockId)
|
||||||
result.success(true)
|
result.success(true)
|
||||||
} else {
|
} else {
|
||||||
result.success(false)
|
result.success(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unlock(lockId: Int, result: MethodChannel.Result) {
|
private fun unlock(lockId: Int, result: MethodChannel.Result) {
|
||||||
if (locks[lockId] == true) {
|
if (locks[lockId] == true) {
|
||||||
locks[lockId] = false
|
locks[lockId] = false
|
||||||
_lockedIds.remove(lockId)
|
_lockedIds.remove(lockId)
|
||||||
result.success(null)
|
result.success(null)
|
||||||
} else {
|
} else {
|
||||||
result.error(
|
result.error(
|
||||||
"notLockedException",
|
"notLockedException", "Cannot unlock without first locking",
|
||||||
"Cannot unlock without first locking",
|
null
|
||||||
null
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private val _lockedIds = mutableListOf<Int>()
|
private val _lockedIds = mutableListOf<Int>()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package com.nkming.nc_photos.np_platform_log
|
package com.nkming.nc_photos.np_platform_log
|
||||||
|
|
||||||
internal interface K {
|
internal interface K {
|
||||||
companion object {
|
companion object {
|
||||||
const val LIB_ID = "com.nkming.nc_photos.np_platform_log"
|
const val LIB_ID = "com.nkming.nc_photos.np_platform_log"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package com.nkming.nc_photos.np_platform_permission
|
package com.nkming.nc_photos.np_platform_permission
|
||||||
|
|
||||||
internal interface K {
|
internal interface K {
|
||||||
companion object {
|
companion object {
|
||||||
const val LIB_ID = "com.nkming.nc_photos.np_platform_permission"
|
const val LIB_ID = "com.nkming.nc_photos.np_platform_permission"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,83 +11,83 @@ import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.PluginRegistry
|
import io.flutter.plugin.common.PluginRegistry
|
||||||
|
|
||||||
class NpPlatformPermissionPlugin : FlutterPlugin, ActivityAware,
|
class NpPlatformPermissionPlugin : FlutterPlugin, ActivityAware,
|
||||||
PluginRegistry.RequestPermissionsResultListener {
|
PluginRegistry.RequestPermissionsResultListener {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "NpPlatformPermissionPlugin"
|
private const val TAG = "NpPlatformPermissionPlugin"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachedToEngine(
|
override fun onAttachedToEngine(
|
||||||
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
|
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
|
||||||
) {
|
) {
|
||||||
permissionChannelHandler =
|
permissionChannelHandler =
|
||||||
PermissionChannelHandler(flutterPluginBinding.applicationContext)
|
PermissionChannelHandler(flutterPluginBinding.applicationContext)
|
||||||
permissionChannel = EventChannel(
|
permissionChannel = EventChannel(
|
||||||
flutterPluginBinding.binaryMessenger,
|
flutterPluginBinding.binaryMessenger,
|
||||||
PermissionChannelHandler.EVENT_CHANNEL
|
PermissionChannelHandler.EVENT_CHANNEL
|
||||||
)
|
)
|
||||||
permissionChannel.setStreamHandler(permissionChannelHandler)
|
permissionChannel.setStreamHandler(permissionChannelHandler)
|
||||||
permissionMethodChannel = MethodChannel(
|
permissionMethodChannel = MethodChannel(
|
||||||
flutterPluginBinding.binaryMessenger,
|
flutterPluginBinding.binaryMessenger,
|
||||||
PermissionChannelHandler.METHOD_CHANNEL
|
PermissionChannelHandler.METHOD_CHANNEL
|
||||||
)
|
)
|
||||||
permissionMethodChannel.setMethodCallHandler(permissionChannelHandler)
|
permissionMethodChannel.setMethodCallHandler(permissionChannelHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromEngine(
|
override fun onDetachedFromEngine(
|
||||||
@NonNull binding: FlutterPlugin.FlutterPluginBinding
|
@NonNull binding: FlutterPlugin.FlutterPluginBinding
|
||||||
) {
|
) {
|
||||||
permissionChannel.setStreamHandler(null)
|
permissionChannel.setStreamHandler(null)
|
||||||
permissionMethodChannel.setMethodCallHandler(null)
|
permissionMethodChannel.setMethodCallHandler(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||||
permissionChannelHandler.onAttachedToActivity(binding)
|
permissionChannelHandler.onAttachedToActivity(binding)
|
||||||
pluginBinding = binding
|
pluginBinding = binding
|
||||||
binding.addRequestPermissionsResultListener(this)
|
binding.addRequestPermissionsResultListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReattachedToActivityForConfigChanges(
|
override fun onReattachedToActivityForConfigChanges(
|
||||||
binding: ActivityPluginBinding
|
binding: ActivityPluginBinding
|
||||||
) {
|
) {
|
||||||
permissionChannelHandler.onReattachedToActivityForConfigChanges(binding)
|
permissionChannelHandler.onReattachedToActivityForConfigChanges(binding)
|
||||||
pluginBinding = binding
|
pluginBinding = binding
|
||||||
binding.addRequestPermissionsResultListener(this)
|
binding.addRequestPermissionsResultListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivity() {
|
override fun onDetachedFromActivity() {
|
||||||
permissionChannelHandler.onDetachedFromActivity()
|
permissionChannelHandler.onDetachedFromActivity()
|
||||||
pluginBinding?.removeRequestPermissionsResultListener(this)
|
pluginBinding?.removeRequestPermissionsResultListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivityForConfigChanges() {
|
override fun onDetachedFromActivityForConfigChanges() {
|
||||||
permissionChannelHandler.onDetachedFromActivityForConfigChanges()
|
permissionChannelHandler.onDetachedFromActivityForConfigChanges()
|
||||||
pluginBinding?.removeRequestPermissionsResultListener(this)
|
pluginBinding?.removeRequestPermissionsResultListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(
|
override fun onRequestPermissionsResult(
|
||||||
requestCode: Int, permissions: Array<String>, grantResults: IntArray
|
requestCode: Int, permissions: Array<String>, grantResults: IntArray
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return try {
|
return try {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
PermissionUtil.REQUEST_CODE -> {
|
PermissionUtil.REQUEST_CODE -> {
|
||||||
permissionChannelHandler.onRequestPermissionsResult(
|
permissionChannelHandler.onRequestPermissionsResult(
|
||||||
requestCode, permissions, grantResults
|
requestCode, permissions, grantResults
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
logE(
|
logE(
|
||||||
TAG, "Failed while onActivityResult, requestCode=$requestCode"
|
TAG, "Failed while onActivityResult, requestCode=$requestCode"
|
||||||
)
|
)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var pluginBinding: ActivityPluginBinding? = null
|
private var pluginBinding: ActivityPluginBinding? = null
|
||||||
|
|
||||||
private lateinit var permissionChannel: EventChannel
|
private lateinit var permissionChannel: EventChannel
|
||||||
private lateinit var permissionMethodChannel: MethodChannel
|
private lateinit var permissionMethodChannel: MethodChannel
|
||||||
private lateinit var permissionChannelHandler: PermissionChannelHandler
|
private lateinit var permissionChannelHandler: PermissionChannelHandler
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,104 +11,104 @@ import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.PluginRegistry
|
import io.flutter.plugin.common.PluginRegistry
|
||||||
|
|
||||||
internal class PermissionChannelHandler(context: Context) :
|
internal class PermissionChannelHandler(context: Context) :
|
||||||
MethodChannel.MethodCallHandler, EventChannel.StreamHandler, ActivityAware,
|
MethodChannel.MethodCallHandler, EventChannel.StreamHandler, ActivityAware,
|
||||||
PluginRegistry.RequestPermissionsResultListener {
|
PluginRegistry.RequestPermissionsResultListener {
|
||||||
companion object {
|
companion object {
|
||||||
const val EVENT_CHANNEL = "${K.LIB_ID}/permission"
|
const val EVENT_CHANNEL = "${K.LIB_ID}/permission"
|
||||||
const val METHOD_CHANNEL = "${K.LIB_ID}/permission_method"
|
const val METHOD_CHANNEL = "${K.LIB_ID}/permission_method"
|
||||||
|
|
||||||
private const val TAG = "PermissionChannelHandler"
|
private const val TAG = "PermissionChannelHandler"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||||
activity = binding.activity
|
activity = binding.activity
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReattachedToActivityForConfigChanges(
|
override fun onReattachedToActivityForConfigChanges(
|
||||||
binding: ActivityPluginBinding
|
binding: ActivityPluginBinding
|
||||||
) {
|
) {
|
||||||
activity = binding.activity
|
activity = binding.activity
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivity() {
|
override fun onDetachedFromActivity() {
|
||||||
activity = null
|
activity = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivityForConfigChanges() {
|
override fun onDetachedFromActivityForConfigChanges() {
|
||||||
activity = null
|
activity = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(
|
override fun onRequestPermissionsResult(
|
||||||
requestCode: Int, permissions: Array<String>, grantResults: IntArray
|
requestCode: Int, permissions: Array<String>, grantResults: IntArray
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return if (requestCode == PermissionUtil.REQUEST_CODE) {
|
return if (requestCode == PermissionUtil.REQUEST_CODE) {
|
||||||
eventSink?.success(buildMap {
|
eventSink?.success(buildMap {
|
||||||
put("event", "RequestPermissionsResult")
|
put("event", "RequestPermissionsResult")
|
||||||
put(
|
put(
|
||||||
"grantResults",
|
"grantResults",
|
||||||
permissions.zip(grantResults.toTypedArray()).toMap()
|
permissions.zip(grantResults.toTypedArray()).toMap()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
||||||
eventSink = events
|
eventSink = events
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancel(arguments: Any?) {
|
override fun onCancel(arguments: Any?) {
|
||||||
eventSink = null
|
eventSink = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"request" -> {
|
"request" -> {
|
||||||
try {
|
try {
|
||||||
request(call.argument("permissions")!!, result)
|
request(call.argument("permissions")!!, result)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"hasWriteExternalStorage" -> {
|
"hasWriteExternalStorage" -> {
|
||||||
try {
|
try {
|
||||||
result.success(
|
result.success(
|
||||||
PermissionUtil.hasWriteExternalStorage(context)
|
PermissionUtil.hasWriteExternalStorage(context)
|
||||||
)
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"hasReadExternalStorage" -> {
|
"hasReadExternalStorage" -> {
|
||||||
try {
|
try {
|
||||||
result.success(
|
result.success(
|
||||||
PermissionUtil.hasReadExternalStorage(context)
|
PermissionUtil.hasReadExternalStorage(context)
|
||||||
)
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun request(
|
private fun request(
|
||||||
permissions: List<String>, result: MethodChannel.Result
|
permissions: List<String>, result: MethodChannel.Result
|
||||||
) {
|
) {
|
||||||
if (activity == null) {
|
if (activity == null) {
|
||||||
result.error("systemException", "Activity is not ready", null)
|
result.error("systemException", "Activity is not ready", null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
PermissionUtil.request(activity!!, *permissions.toTypedArray())
|
PermissionUtil.request(activity!!, *permissions.toTypedArray())
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private var eventSink: EventChannel.EventSink? = null
|
private var eventSink: EventChannel.EventSink? = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,70 +10,58 @@ import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
internal class ImageLoaderChannelHandler(context: Context) :
|
internal class ImageLoaderChannelHandler(context: Context) :
|
||||||
MethodChannel.MethodCallHandler {
|
MethodChannel.MethodCallHandler {
|
||||||
companion object {
|
companion object {
|
||||||
const val METHOD_CHANNEL = "${K.LIB_ID}/image_loader_method"
|
const val METHOD_CHANNEL = "${K.LIB_ID}/image_loader_method"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"loadUri" -> {
|
"loadUri" -> {
|
||||||
try {
|
try {
|
||||||
loadUri(
|
loadUri(
|
||||||
call.argument("fileUri")!!,
|
call.argument("fileUri")!!, call.argument("maxWidth")!!,
|
||||||
call.argument("maxWidth")!!,
|
call.argument("maxHeight")!!,
|
||||||
call.argument("maxHeight")!!,
|
call.argument("resizeMethod")!!,
|
||||||
call.argument("resizeMethod")!!,
|
call.argument("isAllowSwapSide")!!,
|
||||||
call.argument("isAllowSwapSide")!!,
|
call.argument("shouldUpscale")!!,
|
||||||
call.argument("shouldUpscale")!!,
|
call.argument("shouldFixOrientation")!!, result
|
||||||
call.argument("shouldFixOrientation")!!,
|
)
|
||||||
result
|
} catch (e: Throwable) {
|
||||||
)
|
result.error("systemException", e.toString(), null)
|
||||||
} catch (e: Throwable) {
|
}
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load and resize an image pointed by a uri
|
* Load and resize an image pointed by a uri
|
||||||
*
|
*
|
||||||
* @param fileUri
|
* @param fileUri
|
||||||
* @param maxWidth
|
* @param maxWidth
|
||||||
* @param maxHeight
|
* @param maxHeight
|
||||||
* @param resizeMethod
|
* @param resizeMethod
|
||||||
* @param isAllowSwapSide
|
* @param isAllowSwapSide
|
||||||
* @param shouldUpscale
|
* @param shouldUpscale
|
||||||
* @param shouldFixOrientation
|
* @param shouldFixOrientation
|
||||||
* @param result
|
* @param result
|
||||||
*/
|
*/
|
||||||
private fun loadUri(
|
private fun loadUri(
|
||||||
fileUri: String,
|
fileUri: String, maxWidth: Int, maxHeight: Int, resizeMethod: Int,
|
||||||
maxWidth: Int,
|
isAllowSwapSide: Boolean, shouldUpscale: Boolean,
|
||||||
maxHeight: Int,
|
shouldFixOrientation: Boolean, result: MethodChannel.Result
|
||||||
resizeMethod: Int,
|
) {
|
||||||
isAllowSwapSide: Boolean,
|
val image = BitmapUtil.loadImage(
|
||||||
shouldUpscale: Boolean,
|
context, Uri.parse(fileUri), maxWidth, maxHeight,
|
||||||
shouldFixOrientation: Boolean,
|
BitmapResizeMethod.values()[resizeMethod], isAllowSwapSide,
|
||||||
result: MethodChannel.Result
|
shouldUpscale, shouldFixOrientation
|
||||||
) {
|
).use {
|
||||||
val image = BitmapUtil.loadImage(
|
Rgba8Image.fromBitmap(it)
|
||||||
context,
|
}
|
||||||
Uri.parse(fileUri),
|
result.success(image.toJson())
|
||||||
maxWidth,
|
}
|
||||||
maxHeight,
|
|
||||||
BitmapResizeMethod.values()[resizeMethod],
|
|
||||||
isAllowSwapSide,
|
|
||||||
shouldUpscale,
|
|
||||||
shouldFixOrientation
|
|
||||||
).use {
|
|
||||||
Rgba8Image.fromBitmap(it)
|
|
||||||
}
|
|
||||||
result.success(image.toJson())
|
|
||||||
}
|
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package com.nkming.nc_photos.np_platform_raw_image
|
package com.nkming.nc_photos.np_platform_raw_image
|
||||||
|
|
||||||
internal interface K {
|
internal interface K {
|
||||||
companion object {
|
companion object {
|
||||||
const val LIB_ID = "com.nkming.nc_photos.np_platform_raw_image"
|
const val LIB_ID = "com.nkming.nc_photos.np_platform_raw_image"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,23 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
class NpPlatformRawImagePlugin : FlutterPlugin {
|
class NpPlatformRawImagePlugin : FlutterPlugin {
|
||||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onAttachedToEngine(
|
||||||
val imageLoaderChannelHandler =
|
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
|
||||||
ImageLoaderChannelHandler(flutterPluginBinding.applicationContext)
|
) {
|
||||||
imageLoaderMethodChannel = MethodChannel(
|
val imageLoaderChannelHandler =
|
||||||
flutterPluginBinding.binaryMessenger,
|
ImageLoaderChannelHandler(flutterPluginBinding.applicationContext)
|
||||||
ImageLoaderChannelHandler.METHOD_CHANNEL
|
imageLoaderMethodChannel = MethodChannel(
|
||||||
)
|
flutterPluginBinding.binaryMessenger,
|
||||||
imageLoaderMethodChannel.setMethodCallHandler(imageLoaderChannelHandler)
|
ImageLoaderChannelHandler.METHOD_CHANNEL
|
||||||
}
|
)
|
||||||
|
imageLoaderMethodChannel.setMethodCallHandler(imageLoaderChannelHandler)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onDetachedFromEngine(
|
||||||
imageLoaderMethodChannel.setMethodCallHandler(null)
|
@NonNull binding: FlutterPlugin.FlutterPluginBinding
|
||||||
}
|
) {
|
||||||
|
imageLoaderMethodChannel.setMethodCallHandler(null)
|
||||||
|
}
|
||||||
|
|
||||||
private lateinit var imageLoaderMethodChannel: MethodChannel
|
private lateinit var imageLoaderMethodChannel: MethodChannel
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,65 +11,65 @@ import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
|
||||||
internal class ContentUriChannelHandler(context: Context) :
|
internal class ContentUriChannelHandler(context: Context) :
|
||||||
MethodChannel.MethodCallHandler {
|
MethodChannel.MethodCallHandler {
|
||||||
companion object {
|
companion object {
|
||||||
const val METHOD_CHANNEL = "${K.LIB_ID}/content_uri_method"
|
const val METHOD_CHANNEL = "${K.LIB_ID}/content_uri_method"
|
||||||
|
|
||||||
private const val TAG = "ContentUriChannelHandler"
|
private const val TAG = "ContentUriChannelHandler"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"readUri" -> {
|
"readUri" -> {
|
||||||
try {
|
try {
|
||||||
readUri(call.argument("uri")!!, result)
|
readUri(call.argument("uri")!!, result)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"getUriForFile" -> {
|
"getUriForFile" -> {
|
||||||
try {
|
try {
|
||||||
getUriForFile(call.argument("filePath")!!, result)
|
getUriForFile(call.argument("filePath")!!, result)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readUri(uri: String, result: MethodChannel.Result) {
|
private fun readUri(uri: String, result: MethodChannel.Result) {
|
||||||
val uriTyped = Uri.parse(uri)
|
val uriTyped = Uri.parse(uri)
|
||||||
try {
|
try {
|
||||||
val bytes = if (UriUtil.isAssetUri(uriTyped)) {
|
val bytes = if (UriUtil.isAssetUri(uriTyped)) {
|
||||||
context.assets.open(UriUtil.getAssetUriPath(uriTyped)).use {
|
context.assets.open(UriUtil.getAssetUriPath(uriTyped)).use {
|
||||||
it.readBytes()
|
it.readBytes()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
context.contentResolver.openInputStream(uriTyped)!!.use {
|
context.contentResolver.openInputStream(uriTyped)!!.use {
|
||||||
it.readBytes()
|
it.readBytes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.success(bytes)
|
result.success(bytes)
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
result.error("fileNotFoundException", e.toString(), null)
|
result.error("fileNotFoundException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getUriForFile(filePath: String, result: MethodChannel.Result) {
|
private fun getUriForFile(filePath: String, result: MethodChannel.Result) {
|
||||||
try {
|
try {
|
||||||
val file = File(filePath)
|
val file = File(filePath)
|
||||||
val contentUri = FileProvider.getUriForFile(
|
val contentUri = FileProvider.getUriForFile(
|
||||||
context, "${context.packageName}.fileprovider", file
|
context, "${context.packageName}.fileprovider", file
|
||||||
)
|
)
|
||||||
result.success(contentUri.toString())
|
result.success(contentUri.toString())
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
logE(TAG, "[getUriForFile] Unsupported file path: $filePath")
|
logE(TAG, "[getUriForFile] Unsupported file path: $filePath")
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
package com.nkming.nc_photos.plugin
|
package com.nkming.nc_photos.plugin
|
||||||
|
|
||||||
internal interface K {
|
internal interface K {
|
||||||
companion object {
|
companion object {
|
||||||
const val DOWNLOAD_NOTIFICATION_ID_MIN = 1000
|
const val DOWNLOAD_NOTIFICATION_ID_MIN = 1000
|
||||||
const val DOWNLOAD_NOTIFICATION_ID_MAX = 2000
|
const val DOWNLOAD_NOTIFICATION_ID_MAX = 2000
|
||||||
|
|
||||||
const val MEDIA_STORE_DELETE_REQUEST_CODE = 11012
|
const val MEDIA_STORE_DELETE_REQUEST_CODE = 11012
|
||||||
|
|
||||||
const val LIB_ID = "com.nkming.nc_photos.plugin"
|
const val LIB_ID = "com.nkming.nc_photos.plugin"
|
||||||
|
|
||||||
const val ACTION_DOWNLOAD_CANCEL = "${LIB_ID}.ACTION_DOWNLOAD_CANCEL"
|
const val ACTION_DOWNLOAD_CANCEL = "${LIB_ID}.ACTION_DOWNLOAD_CANCEL"
|
||||||
|
|
||||||
const val EXTRA_NOTIFICATION_ID = "${LIB_ID}.EXTRA_NOTIFICATION_ID"
|
const val EXTRA_NOTIFICATION_ID = "${LIB_ID}.EXTRA_NOTIFICATION_ID"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import android.provider.MediaStore
|
||||||
import com.nkming.nc_photos.np_android_core.MediaStoreUtil
|
import com.nkming.nc_photos.np_android_core.MediaStoreUtil
|
||||||
import com.nkming.nc_photos.np_android_core.PermissionException
|
import com.nkming.nc_photos.np_android_core.PermissionException
|
||||||
import com.nkming.nc_photos.np_android_core.PermissionUtil
|
import com.nkming.nc_photos.np_android_core.PermissionUtil
|
||||||
import com.nkming.nc_photos.np_android_core.logD
|
|
||||||
import com.nkming.nc_photos.np_android_core.logE
|
import com.nkming.nc_photos.np_android_core.logE
|
||||||
import com.nkming.nc_photos.np_android_core.logI
|
import com.nkming.nc_photos.np_android_core.logI
|
||||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
import io.flutter.embedding.engine.plugins.activity.ActivityAware
|
||||||
|
@ -33,252 +32,250 @@ import java.io.File
|
||||||
* fun queryFiles(relativePath: String): List<Map>
|
* fun queryFiles(relativePath: String): List<Map>
|
||||||
*/
|
*/
|
||||||
internal class MediaStoreChannelHandler(context: Context) :
|
internal class MediaStoreChannelHandler(context: Context) :
|
||||||
MethodChannel.MethodCallHandler, EventChannel.StreamHandler,
|
MethodChannel.MethodCallHandler, EventChannel.StreamHandler, ActivityAware,
|
||||||
ActivityAware, PluginRegistry.ActivityResultListener {
|
PluginRegistry.ActivityResultListener {
|
||||||
companion object {
|
companion object {
|
||||||
const val EVENT_CHANNEL = "${K.LIB_ID}/media_store"
|
const val EVENT_CHANNEL = "${K.LIB_ID}/media_store"
|
||||||
const val METHOD_CHANNEL = "${K.LIB_ID}/media_store_method"
|
const val METHOD_CHANNEL = "${K.LIB_ID}/media_store_method"
|
||||||
|
|
||||||
private const val TAG = "MediaStoreChannelHandler"
|
private const val TAG = "MediaStoreChannelHandler"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||||
activity = binding.activity
|
activity = binding.activity
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReattachedToActivityForConfigChanges(
|
override fun onReattachedToActivityForConfigChanges(
|
||||||
binding: ActivityPluginBinding
|
binding: ActivityPluginBinding
|
||||||
) {
|
) {
|
||||||
activity = binding.activity
|
activity = binding.activity
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivity() {
|
override fun onDetachedFromActivity() {
|
||||||
activity = null
|
activity = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivityForConfigChanges() {
|
override fun onDetachedFromActivityForConfigChanges() {
|
||||||
activity = null
|
activity = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(
|
override fun onActivityResult(
|
||||||
requestCode: Int, resultCode: Int, data: Intent?
|
requestCode: Int, resultCode: Int, data: Intent?
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (requestCode == K.MEDIA_STORE_DELETE_REQUEST_CODE) {
|
if (requestCode == K.MEDIA_STORE_DELETE_REQUEST_CODE) {
|
||||||
eventSink?.success(buildMap {
|
eventSink?.success(buildMap {
|
||||||
put("event", "DeleteRequestResult")
|
put("event", "DeleteRequestResult")
|
||||||
put("resultCode", resultCode)
|
put("resultCode", resultCode)
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
||||||
eventSink = events
|
eventSink = events
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancel(arguments: Any?) {
|
override fun onCancel(arguments: Any?) {
|
||||||
eventSink = null
|
eventSink = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"saveFileToDownload" -> {
|
"saveFileToDownload" -> {
|
||||||
try {
|
try {
|
||||||
saveFileToDownload(
|
saveFileToDownload(
|
||||||
call.argument("content")!!, call.argument("filename")!!,
|
call.argument("content")!!, call.argument("filename")!!,
|
||||||
call.argument("subDir"), result
|
call.argument("subDir"), result
|
||||||
)
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.message, null)
|
result.error("systemException", e.message, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"copyFileToDownload" -> {
|
"copyFileToDownload" -> {
|
||||||
try {
|
try {
|
||||||
copyFileToDownload(
|
copyFileToDownload(
|
||||||
call.argument("fromFile")!!, call.argument("filename"),
|
call.argument("fromFile")!!, call.argument("filename"),
|
||||||
call.argument("subDir"), result
|
call.argument("subDir"), result
|
||||||
)
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.message, null)
|
result.error("systemException", e.message, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"queryFiles" -> {
|
"queryFiles" -> {
|
||||||
try {
|
try {
|
||||||
queryFiles(call.argument("relativePath")!!, result)
|
queryFiles(call.argument("relativePath")!!, result)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.message, null)
|
result.error("systemException", e.message, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"deleteFiles" -> {
|
"deleteFiles" -> {
|
||||||
try {
|
try {
|
||||||
deleteFiles(call.argument("uris")!!, result)
|
deleteFiles(call.argument("uris")!!, result)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.message, null)
|
result.error("systemException", e.message, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveFileToDownload(
|
private fun saveFileToDownload(
|
||||||
content: ByteArray, filename: String, subDir: String?,
|
content: ByteArray, filename: String, subDir: String?,
|
||||||
result: MethodChannel.Result
|
result: MethodChannel.Result
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val uri = MediaStoreUtil.saveFileToDownload(
|
val uri = MediaStoreUtil.saveFileToDownload(
|
||||||
context, content, filename, subDir
|
context, content, filename, subDir
|
||||||
)
|
)
|
||||||
result.success(uri.toString())
|
result.success(uri.toString())
|
||||||
} catch (e: PermissionException) {
|
} catch (e: PermissionException) {
|
||||||
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
|
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
|
||||||
result.error("permissionError", "Permission not granted", null)
|
result.error("permissionError", "Permission not granted", null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyFileToDownload(
|
private fun copyFileToDownload(
|
||||||
fromFile: String, filename: String?, subDir: String?,
|
fromFile: String, filename: String?, subDir: String?,
|
||||||
result: MethodChannel.Result
|
result: MethodChannel.Result
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val fromUri = inputToUri(fromFile)
|
val fromUri = inputToUri(fromFile)
|
||||||
val uri = MediaStoreUtil.copyFileToDownload(
|
val uri = MediaStoreUtil.copyFileToDownload(
|
||||||
context, fromUri, filename, subDir
|
context, fromUri, filename, subDir
|
||||||
)
|
)
|
||||||
result.success(uri.toString())
|
result.success(uri.toString())
|
||||||
} catch (e: PermissionException) {
|
} catch (e: PermissionException) {
|
||||||
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
|
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
|
||||||
result.error("permissionError", "Permission not granted", null)
|
result.error("permissionError", "Permission not granted", null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queryFiles(relativePath: String, result: MethodChannel.Result) {
|
private fun queryFiles(relativePath: String, result: MethodChannel.Result) {
|
||||||
if (!PermissionUtil.hasReadExternalStorage(context)) {
|
if (!PermissionUtil.hasReadExternalStorage(context)) {
|
||||||
activity?.let { PermissionUtil.requestReadExternalStorage(it) }
|
activity?.let { PermissionUtil.requestReadExternalStorage(it) }
|
||||||
result.error("permissionError", "Permission not granted", null)
|
result.error("permissionError", "Permission not granted", null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val pathColumnName: String
|
val pathColumnName: String
|
||||||
val pathArg: String
|
val pathArg: String
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
pathColumnName = MediaStore.Images.Media.RELATIVE_PATH
|
pathColumnName = MediaStore.Images.Media.RELATIVE_PATH
|
||||||
pathArg = "${relativePath}/%"
|
pathArg = "${relativePath}/%"
|
||||||
} else {
|
} else {
|
||||||
@Suppress("Deprecation")
|
@Suppress("Deprecation") pathColumnName =
|
||||||
pathColumnName = MediaStore.Images.Media.DATA
|
MediaStore.Images.Media.DATA
|
||||||
pathArg = "%/${relativePath}/%"
|
pathArg = "%/${relativePath}/%"
|
||||||
}
|
}
|
||||||
val projection = arrayOf(
|
val projection = arrayOf(
|
||||||
MediaStore.Images.Media._ID,
|
MediaStore.Images.Media._ID, MediaStore.Images.Media.DATE_MODIFIED,
|
||||||
MediaStore.Images.Media.DATE_MODIFIED,
|
MediaStore.Images.Media.MIME_TYPE,
|
||||||
MediaStore.Images.Media.MIME_TYPE,
|
MediaStore.Images.Media.DATE_TAKEN,
|
||||||
MediaStore.Images.Media.DATE_TAKEN,
|
MediaStore.Images.Media.DISPLAY_NAME, pathColumnName
|
||||||
MediaStore.Images.Media.DISPLAY_NAME,
|
)
|
||||||
pathColumnName
|
val selection = StringBuilder().apply {
|
||||||
)
|
append("${MediaStore.Images.Media.MIME_TYPE} LIKE ?")
|
||||||
val selection = StringBuilder().apply {
|
append("AND $pathColumnName LIKE ?")
|
||||||
append("${MediaStore.Images.Media.MIME_TYPE} LIKE ?")
|
}.toString()
|
||||||
append("AND $pathColumnName LIKE ?")
|
val selectionArgs = arrayOf("image/%", pathArg)
|
||||||
}.toString()
|
val files = context.contentResolver.query(
|
||||||
val selectionArgs = arrayOf("image/%", pathArg)
|
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection,
|
||||||
val files = context.contentResolver.query(
|
selectionArgs, null
|
||||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
)!!.use {
|
||||||
projection, selection, selectionArgs, null
|
val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
|
||||||
)!!.use {
|
val dateModifiedColumn =
|
||||||
val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
|
it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED)
|
||||||
val dateModifiedColumn =
|
val mimeTypeColumn =
|
||||||
it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED)
|
it.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)
|
||||||
val mimeTypeColumn =
|
val dateTakenColumn =
|
||||||
it.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)
|
it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
|
||||||
val dateTakenColumn =
|
val displayNameColumn =
|
||||||
it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
|
it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
|
||||||
val displayNameColumn =
|
val pathColumn = it.getColumnIndexOrThrow(pathColumnName)
|
||||||
it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
|
val products = mutableListOf<Map<String, Any>>()
|
||||||
val pathColumn = it.getColumnIndexOrThrow(pathColumnName)
|
while (it.moveToNext()) {
|
||||||
val products = mutableListOf<Map<String, Any>>()
|
val id = it.getLong(idColumn)
|
||||||
while (it.moveToNext()) {
|
val dateModified = it.getLong(dateModifiedColumn)
|
||||||
val id = it.getLong(idColumn)
|
val mimeType = it.getString(mimeTypeColumn)
|
||||||
val dateModified = it.getLong(dateModifiedColumn)
|
val dateTaken = it.getLong(dateTakenColumn)
|
||||||
val mimeType = it.getString(mimeTypeColumn)
|
val displayName = it.getString(displayNameColumn)
|
||||||
val dateTaken = it.getLong(dateTakenColumn)
|
val path = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
val displayName = it.getString(displayNameColumn)
|
// RELATIVE_PATH
|
||||||
val path = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
"${it.getString(pathColumn).trimEnd('/')}/$displayName"
|
||||||
// RELATIVE_PATH
|
} else {
|
||||||
"${it.getString(pathColumn).trimEnd('/')}/$displayName"
|
// DATA
|
||||||
} else {
|
it.getString(pathColumn)
|
||||||
// DATA
|
}
|
||||||
it.getString(pathColumn)
|
val contentUri = ContentUris.withAppendedId(
|
||||||
}
|
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id
|
||||||
val contentUri = ContentUris.withAppendedId(
|
)
|
||||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id
|
products.add(buildMap {
|
||||||
)
|
put("uri", contentUri.toString())
|
||||||
products.add(buildMap {
|
put("displayName", displayName)
|
||||||
put("uri", contentUri.toString())
|
put("path", path)
|
||||||
put("displayName", displayName)
|
put("dateModified", dateModified * 1000)
|
||||||
put("path", path)
|
put("mimeType", mimeType)
|
||||||
put("dateModified", dateModified * 1000)
|
if (dateTaken != 0L) put("dateTaken", dateTaken)
|
||||||
put("mimeType", mimeType)
|
})
|
||||||
if (dateTaken != 0L) put("dateTaken", dateTaken)
|
logD(
|
||||||
})
|
TAG,
|
||||||
logD(
|
"[queryEnhancedPhotos] Found $displayName, path=$path, uri=$contentUri"
|
||||||
TAG,
|
)
|
||||||
"[queryEnhancedPhotos] Found $displayName, path=$path, uri=$contentUri"
|
}
|
||||||
)
|
products
|
||||||
}
|
}
|
||||||
products
|
logI(TAG, "[queryEnhancedPhotos] Found ${files.size} files")
|
||||||
}
|
result.success(files)
|
||||||
logI(TAG, "[queryEnhancedPhotos] Found ${files.size} files")
|
}
|
||||||
result.success(files)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteFiles(uris: List<String>, result: MethodChannel.Result) {
|
private fun deleteFiles(uris: List<String>, result: MethodChannel.Result) {
|
||||||
val urisTyped = uris.map(Uri::parse)
|
val urisTyped = uris.map(Uri::parse)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
val pi = MediaStore.createDeleteRequest(
|
val pi = MediaStore.createDeleteRequest(
|
||||||
context.contentResolver, urisTyped
|
context.contentResolver, urisTyped
|
||||||
)
|
)
|
||||||
activity!!.startIntentSenderForResult(
|
activity!!.startIntentSenderForResult(
|
||||||
pi.intentSender, K.MEDIA_STORE_DELETE_REQUEST_CODE, null, 0, 0,
|
pi.intentSender, K.MEDIA_STORE_DELETE_REQUEST_CODE, null, 0, 0,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
result.success(null)
|
result.success(null)
|
||||||
} else {
|
} else {
|
||||||
if (!PermissionUtil.hasWriteExternalStorage(context)) {
|
if (!PermissionUtil.hasWriteExternalStorage(context)) {
|
||||||
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
|
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
|
||||||
result.error("permissionError", "Permission not granted", null)
|
result.error("permissionError", "Permission not granted", null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val failed = mutableListOf<String>()
|
val failed = mutableListOf<String>()
|
||||||
for (uri in urisTyped) {
|
for (uri in urisTyped) {
|
||||||
try {
|
try {
|
||||||
context.contentResolver.delete(uri, null, null)
|
context.contentResolver.delete(uri, null, null)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
logE(TAG, "[deleteFiles] Failed while delete", e)
|
logE(TAG, "[deleteFiles] Failed while delete", e)
|
||||||
failed += uri.toString()
|
failed += uri.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.success(failed)
|
result.success(failed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inputToUri(fromFile: String): Uri {
|
private fun inputToUri(fromFile: String): Uri {
|
||||||
val testUri = Uri.parse(fromFile)
|
val testUri = Uri.parse(fromFile)
|
||||||
return if (testUri.scheme == null) {
|
return if (testUri.scheme == null) {
|
||||||
// is a file path
|
// is a file path
|
||||||
Uri.fromFile(File(fromFile))
|
Uri.fromFile(File(fromFile))
|
||||||
} else {
|
} else {
|
||||||
// is a uri
|
// is a uri
|
||||||
Uri.parse(fromFile)
|
Uri.parse(fromFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private var eventSink: EventChannel.EventSink? = null
|
private var eventSink: EventChannel.EventSink? = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,117 +11,117 @@ import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.PluginRegistry
|
import io.flutter.plugin.common.PluginRegistry
|
||||||
|
|
||||||
class NcPhotosPlugin : FlutterPlugin, ActivityAware,
|
class NcPhotosPlugin : FlutterPlugin, ActivityAware,
|
||||||
PluginRegistry.ActivityResultListener {
|
PluginRegistry.ActivityResultListener {
|
||||||
companion object {
|
companion object {
|
||||||
const val ACTION_DOWNLOAD_CANCEL = K.ACTION_DOWNLOAD_CANCEL
|
const val ACTION_DOWNLOAD_CANCEL = K.ACTION_DOWNLOAD_CANCEL
|
||||||
const val EXTRA_NOTIFICATION_ID = K.EXTRA_NOTIFICATION_ID
|
const val EXTRA_NOTIFICATION_ID = K.EXTRA_NOTIFICATION_ID
|
||||||
|
|
||||||
private const val TAG = "NcPhotosPlugin"
|
private const val TAG = "NcPhotosPlugin"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachedToEngine(
|
override fun onAttachedToEngine(
|
||||||
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
|
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
|
||||||
) {
|
) {
|
||||||
notificationChannel = MethodChannel(
|
notificationChannel = MethodChannel(
|
||||||
flutterPluginBinding.binaryMessenger,
|
flutterPluginBinding.binaryMessenger,
|
||||||
NotificationChannelHandler.CHANNEL
|
NotificationChannelHandler.CHANNEL
|
||||||
)
|
)
|
||||||
notificationChannel.setMethodCallHandler(
|
notificationChannel.setMethodCallHandler(
|
||||||
NotificationChannelHandler(
|
NotificationChannelHandler(
|
||||||
flutterPluginBinding.applicationContext
|
flutterPluginBinding.applicationContext
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
mediaStoreChannelHandler =
|
mediaStoreChannelHandler =
|
||||||
MediaStoreChannelHandler(flutterPluginBinding.applicationContext)
|
MediaStoreChannelHandler(flutterPluginBinding.applicationContext)
|
||||||
mediaStoreChannel = EventChannel(
|
mediaStoreChannel = EventChannel(
|
||||||
flutterPluginBinding.binaryMessenger,
|
flutterPluginBinding.binaryMessenger,
|
||||||
MediaStoreChannelHandler.EVENT_CHANNEL
|
MediaStoreChannelHandler.EVENT_CHANNEL
|
||||||
)
|
)
|
||||||
mediaStoreChannel.setStreamHandler(mediaStoreChannelHandler)
|
mediaStoreChannel.setStreamHandler(mediaStoreChannelHandler)
|
||||||
mediaStoreMethodChannel = MethodChannel(
|
mediaStoreMethodChannel = MethodChannel(
|
||||||
flutterPluginBinding.binaryMessenger,
|
flutterPluginBinding.binaryMessenger,
|
||||||
MediaStoreChannelHandler.METHOD_CHANNEL
|
MediaStoreChannelHandler.METHOD_CHANNEL
|
||||||
)
|
)
|
||||||
mediaStoreMethodChannel.setMethodCallHandler(mediaStoreChannelHandler)
|
mediaStoreMethodChannel.setMethodCallHandler(mediaStoreChannelHandler)
|
||||||
|
|
||||||
contentUriMethodChannel = MethodChannel(
|
contentUriMethodChannel = MethodChannel(
|
||||||
flutterPluginBinding.binaryMessenger,
|
flutterPluginBinding.binaryMessenger,
|
||||||
ContentUriChannelHandler.METHOD_CHANNEL
|
ContentUriChannelHandler.METHOD_CHANNEL
|
||||||
)
|
)
|
||||||
contentUriMethodChannel.setMethodCallHandler(
|
contentUriMethodChannel.setMethodCallHandler(
|
||||||
ContentUriChannelHandler(flutterPluginBinding.applicationContext)
|
ContentUriChannelHandler(flutterPluginBinding.applicationContext)
|
||||||
)
|
)
|
||||||
|
|
||||||
val preferenceChannelHandler =
|
val preferenceChannelHandler =
|
||||||
PreferenceChannelHandler(flutterPluginBinding.applicationContext)
|
PreferenceChannelHandler(flutterPluginBinding.applicationContext)
|
||||||
preferenceMethodChannel = MethodChannel(
|
preferenceMethodChannel = MethodChannel(
|
||||||
flutterPluginBinding.binaryMessenger,
|
flutterPluginBinding.binaryMessenger,
|
||||||
PreferenceChannelHandler.METHOD_CHANNEL
|
PreferenceChannelHandler.METHOD_CHANNEL
|
||||||
)
|
)
|
||||||
preferenceMethodChannel.setMethodCallHandler(preferenceChannelHandler)
|
preferenceMethodChannel.setMethodCallHandler(preferenceChannelHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromEngine(
|
override fun onDetachedFromEngine(
|
||||||
@NonNull binding: FlutterPlugin.FlutterPluginBinding
|
@NonNull binding: FlutterPlugin.FlutterPluginBinding
|
||||||
) {
|
) {
|
||||||
notificationChannel.setMethodCallHandler(null)
|
notificationChannel.setMethodCallHandler(null)
|
||||||
mediaStoreChannel.setStreamHandler(null)
|
mediaStoreChannel.setStreamHandler(null)
|
||||||
mediaStoreMethodChannel.setMethodCallHandler(null)
|
mediaStoreMethodChannel.setMethodCallHandler(null)
|
||||||
contentUriMethodChannel.setMethodCallHandler(null)
|
contentUriMethodChannel.setMethodCallHandler(null)
|
||||||
preferenceMethodChannel.setMethodCallHandler(null)
|
preferenceMethodChannel.setMethodCallHandler(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||||
mediaStoreChannelHandler.onAttachedToActivity(binding)
|
mediaStoreChannelHandler.onAttachedToActivity(binding)
|
||||||
pluginBinding = binding
|
pluginBinding = binding
|
||||||
binding.addActivityResultListener(this)
|
binding.addActivityResultListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReattachedToActivityForConfigChanges(
|
override fun onReattachedToActivityForConfigChanges(
|
||||||
binding: ActivityPluginBinding
|
binding: ActivityPluginBinding
|
||||||
) {
|
) {
|
||||||
mediaStoreChannelHandler.onReattachedToActivityForConfigChanges(binding)
|
mediaStoreChannelHandler.onReattachedToActivityForConfigChanges(binding)
|
||||||
pluginBinding = binding
|
pluginBinding = binding
|
||||||
binding.addActivityResultListener(this)
|
binding.addActivityResultListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivity() {
|
override fun onDetachedFromActivity() {
|
||||||
mediaStoreChannelHandler.onDetachedFromActivity()
|
mediaStoreChannelHandler.onDetachedFromActivity()
|
||||||
pluginBinding?.removeActivityResultListener(this)
|
pluginBinding?.removeActivityResultListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromActivityForConfigChanges() {
|
override fun onDetachedFromActivityForConfigChanges() {
|
||||||
mediaStoreChannelHandler.onDetachedFromActivityForConfigChanges()
|
mediaStoreChannelHandler.onDetachedFromActivityForConfigChanges()
|
||||||
pluginBinding?.removeActivityResultListener(this)
|
pluginBinding?.removeActivityResultListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(
|
override fun onActivityResult(
|
||||||
requestCode: Int, resultCode: Int, data: Intent?
|
requestCode: Int, resultCode: Int, data: Intent?
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return try {
|
return try {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
K.MEDIA_STORE_DELETE_REQUEST_CODE -> {
|
K.MEDIA_STORE_DELETE_REQUEST_CODE -> {
|
||||||
mediaStoreChannelHandler.onActivityResult(
|
mediaStoreChannelHandler.onActivityResult(
|
||||||
requestCode, resultCode, data
|
requestCode, resultCode, data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
logE(TAG, "Failed while onActivityResult, requestCode=$requestCode")
|
logE(TAG, "Failed while onActivityResult, requestCode=$requestCode")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var pluginBinding: ActivityPluginBinding? = null
|
private var pluginBinding: ActivityPluginBinding? = null
|
||||||
|
|
||||||
private lateinit var notificationChannel: MethodChannel
|
private lateinit var notificationChannel: MethodChannel
|
||||||
private lateinit var mediaStoreChannel: EventChannel
|
private lateinit var mediaStoreChannel: EventChannel
|
||||||
private lateinit var mediaStoreMethodChannel: MethodChannel
|
private lateinit var mediaStoreMethodChannel: MethodChannel
|
||||||
private lateinit var contentUriMethodChannel: MethodChannel
|
private lateinit var contentUriMethodChannel: MethodChannel
|
||||||
private lateinit var preferenceMethodChannel: MethodChannel
|
private lateinit var preferenceMethodChannel: MethodChannel
|
||||||
|
|
||||||
private lateinit var mediaStoreChannelHandler: MediaStoreChannelHandler
|
private lateinit var mediaStoreChannelHandler: MediaStoreChannelHandler
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,334 +26,323 @@ import kotlin.math.max
|
||||||
* mimeTypes: List<String>): Unit
|
* mimeTypes: List<String>): Unit
|
||||||
*/
|
*/
|
||||||
internal class NotificationChannelHandler(context: Context) :
|
internal class NotificationChannelHandler(context: Context) :
|
||||||
MethodChannel.MethodCallHandler {
|
MethodChannel.MethodCallHandler {
|
||||||
companion object {
|
companion object {
|
||||||
const val CHANNEL = "${K.LIB_ID}/notification"
|
const val CHANNEL = "${K.LIB_ID}/notification"
|
||||||
|
|
||||||
fun getNextNotificationId(): Int {
|
fun getNextNotificationId(): Int {
|
||||||
if (++notificationId >= K.DOWNLOAD_NOTIFICATION_ID_MAX) {
|
if (++notificationId >= K.DOWNLOAD_NOTIFICATION_ID_MAX) {
|
||||||
notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN
|
notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN
|
||||||
}
|
}
|
||||||
return notificationId
|
return notificationId
|
||||||
}
|
}
|
||||||
|
|
||||||
const val DOWNLOAD_CHANNEL_ID = "download"
|
const val DOWNLOAD_CHANNEL_ID = "download"
|
||||||
private var notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN
|
private var notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
createDownloadChannel(context)
|
createDownloadChannel(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"notifyDownloadSuccessful" -> {
|
"notifyDownloadSuccessful" -> {
|
||||||
try {
|
try {
|
||||||
notifyDownloadSuccessful(
|
notifyDownloadSuccessful(
|
||||||
call.argument("fileUris")!!,
|
call.argument("fileUris")!!,
|
||||||
call.argument("mimeTypes")!!,
|
call.argument("mimeTypes")!!,
|
||||||
call.argument("notificationId"),
|
call.argument("notificationId"), result
|
||||||
result
|
)
|
||||||
)
|
} catch (e: Throwable) {
|
||||||
} catch (e: Throwable) {
|
result.error("systemException", e.toString(), null)
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
"notifyDownloadProgress" -> {
|
"notifyDownloadProgress" -> {
|
||||||
try {
|
try {
|
||||||
notifyDownloadProgress(
|
notifyDownloadProgress(
|
||||||
call.argument("progress")!!,
|
call.argument("progress")!!, call.argument("max")!!,
|
||||||
call.argument("max")!!,
|
call.argument("currentItemTitle"),
|
||||||
call.argument("currentItemTitle"),
|
call.argument("notificationId"), result
|
||||||
call.argument("notificationId"),
|
)
|
||||||
result
|
} catch (e: Throwable) {
|
||||||
)
|
result.error("systemException", e.toString(), null)
|
||||||
} catch (e: Throwable) {
|
}
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"notifyLogSaveSuccessful" -> {
|
"notifyLogSaveSuccessful" -> {
|
||||||
try {
|
try {
|
||||||
notifyLogSaveSuccessful(
|
notifyLogSaveSuccessful(
|
||||||
call.argument("fileUri")!!, result
|
call.argument("fileUri")!!, result
|
||||||
)
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"dismiss" -> {
|
"dismiss" -> {
|
||||||
try {
|
try {
|
||||||
dismiss(call.argument("notificationId")!!, result)
|
dismiss(call.argument("notificationId")!!, result)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
result.error("systemException", e.toString(), null)
|
result.error("systemException", e.toString(), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyDownloadSuccessful(
|
private fun notifyDownloadSuccessful(
|
||||||
fileUris: List<String>,
|
fileUris: List<String>, mimeTypes: List<String?>, notificationId: Int?,
|
||||||
mimeTypes: List<String?>,
|
result: MethodChannel.Result
|
||||||
notificationId: Int?,
|
) {
|
||||||
result: MethodChannel.Result
|
assert(fileUris.isNotEmpty())
|
||||||
) {
|
assert(fileUris.size == mimeTypes.size)
|
||||||
assert(fileUris.isNotEmpty())
|
val uris = fileUris.map { Uri.parse(it) }
|
||||||
assert(fileUris.size == mimeTypes.size)
|
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
|
||||||
val uris = fileUris.map { Uri.parse(it) }
|
.setSmallIcon(R.drawable.baseline_download_white_18)
|
||||||
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
|
.setWhen(System.currentTimeMillis())
|
||||||
.setSmallIcon(R.drawable.baseline_download_white_18)
|
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
|
||||||
.setWhen(System.currentTimeMillis())
|
RingtoneManager.getDefaultUri(
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
|
RingtoneManager.TYPE_NOTIFICATION
|
||||||
RingtoneManager.getDefaultUri(
|
)
|
||||||
RingtoneManager.TYPE_NOTIFICATION
|
).setOnlyAlertOnce(false).setAutoCancel(true).setLocalOnly(true)
|
||||||
)
|
|
||||||
).setOnlyAlertOnce(false).setAutoCancel(true).setLocalOnly(true)
|
|
||||||
|
|
||||||
if (uris.size == 1) {
|
if (uris.size == 1) {
|
||||||
builder.setContentTitle(
|
builder.setContentTitle(
|
||||||
_context.getString(
|
_context.getString(
|
||||||
R.string.download_successful_notification_title
|
R.string.download_successful_notification_title
|
||||||
)
|
)
|
||||||
).setContentText(
|
).setContentText(
|
||||||
_context.getString(
|
_context.getString(
|
||||||
R.string.download_successful_notification_text
|
R.string.download_successful_notification_text
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val openIntent = Intent().apply {
|
val openIntent = Intent().apply {
|
||||||
action = Intent.ACTION_VIEW
|
action = Intent.ACTION_VIEW
|
||||||
setDataAndType(uris[0], mimeTypes[0])
|
setDataAndType(uris[0], mimeTypes[0])
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
}
|
}
|
||||||
val openPendingIntent = PendingIntent.getActivity(
|
val openPendingIntent = PendingIntent.getActivity(
|
||||||
_context, 0, openIntent,
|
_context, 0, openIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
|
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
|
||||||
)
|
)
|
||||||
builder.setContentIntent(openPendingIntent)
|
builder.setContentIntent(openPendingIntent)
|
||||||
|
|
||||||
// show preview if available
|
// show preview if available
|
||||||
if (mimeTypes[0]?.startsWith("image/") == true) {
|
if (mimeTypes[0]?.startsWith("image/") == true) {
|
||||||
val preview = loadNotificationImage(uris[0])
|
val preview = loadNotificationImage(uris[0])
|
||||||
if (preview != null) {
|
if (preview != null) {
|
||||||
builder.setStyle(
|
builder.setStyle(
|
||||||
NotificationCompat.BigPictureStyle()
|
NotificationCompat.BigPictureStyle()
|
||||||
.bigPicture(loadNotificationImage(uris[0]))
|
.bigPicture(loadNotificationImage(uris[0]))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
builder.setContentTitle(
|
builder.setContentTitle(
|
||||||
_context.getString(
|
_context.getString(
|
||||||
R.string.download_multiple_successful_notification_title,
|
R.string.download_multiple_successful_notification_title,
|
||||||
fileUris.size
|
fileUris.size
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val shareIntent = if (uris.size == 1) Intent().apply {
|
val shareIntent = if (uris.size == 1) Intent().apply {
|
||||||
action = Intent.ACTION_SEND
|
action = Intent.ACTION_SEND
|
||||||
putExtra(Intent.EXTRA_STREAM, uris[0])
|
putExtra(Intent.EXTRA_STREAM, uris[0])
|
||||||
type = mimeTypes[0] ?: "*/*"
|
type = mimeTypes[0] ?: "*/*"
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
} else Intent().apply {
|
} else Intent().apply {
|
||||||
action = Intent.ACTION_SEND_MULTIPLE
|
action = Intent.ACTION_SEND_MULTIPLE
|
||||||
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
|
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
|
||||||
type =
|
type = if (mimeTypes.all {
|
||||||
if (mimeTypes.all {
|
it?.startsWith(
|
||||||
it?.startsWith(
|
"image/"
|
||||||
"image/"
|
) == true
|
||||||
) == true
|
}) "image/*" else "*/*"
|
||||||
}) "image/*" else "*/*"
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
}
|
||||||
}
|
val shareChooser = Intent.createChooser(
|
||||||
val shareChooser = Intent.createChooser(
|
shareIntent, _context.getString(
|
||||||
shareIntent, _context.getString(
|
R.string.download_successful_notification_action_share_chooser
|
||||||
R.string.download_successful_notification_action_share_chooser
|
)
|
||||||
)
|
)
|
||||||
)
|
val sharePendingIntent = PendingIntent.getActivity(
|
||||||
val sharePendingIntent = PendingIntent.getActivity(
|
_context, 1, shareChooser,
|
||||||
_context, 1, shareChooser,
|
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
|
)
|
||||||
)
|
builder.addAction(
|
||||||
builder.addAction(
|
0, _context.getString(
|
||||||
0, _context.getString(
|
R.string.download_successful_notification_action_share
|
||||||
R.string.download_successful_notification_action_share
|
), sharePendingIntent
|
||||||
), sharePendingIntent
|
)
|
||||||
)
|
|
||||||
|
|
||||||
val id = notificationId ?: getNextNotificationId()
|
val id = notificationId ?: getNextNotificationId()
|
||||||
with(NotificationManagerCompat.from(_context)) {
|
with(NotificationManagerCompat.from(_context)) {
|
||||||
notify(id, builder.build())
|
notify(id, builder.build())
|
||||||
}
|
}
|
||||||
result.success(id)
|
result.success(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyDownloadProgress(
|
private fun notifyDownloadProgress(
|
||||||
progress: Int,
|
progress: Int, max: Int, currentItemTitle: String?,
|
||||||
max: Int,
|
notificationId: Int?, result: MethodChannel.Result
|
||||||
currentItemTitle: String?,
|
) {
|
||||||
notificationId: Int?,
|
val id = notificationId ?: getNextNotificationId()
|
||||||
result: MethodChannel.Result
|
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
|
||||||
) {
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
val id = notificationId ?: getNextNotificationId()
|
.setWhen(System.currentTimeMillis())
|
||||||
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
|
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
RingtoneManager.getDefaultUri(
|
||||||
.setWhen(System.currentTimeMillis())
|
RingtoneManager.TYPE_NOTIFICATION
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
|
)
|
||||||
RingtoneManager.getDefaultUri(
|
).setOnlyAlertOnce(true).setAutoCancel(false).setLocalOnly(true)
|
||||||
RingtoneManager.TYPE_NOTIFICATION
|
.setProgress(max, progress, false).setContentText("$progress/$max")
|
||||||
)
|
if (currentItemTitle == null) {
|
||||||
).setOnlyAlertOnce(true).setAutoCancel(false).setLocalOnly(true)
|
builder.setContentTitle(
|
||||||
.setProgress(max, progress, false).setContentText("$progress/$max")
|
_context.getString(
|
||||||
if (currentItemTitle == null) {
|
R.string.download_progress_notification_untitled_text
|
||||||
builder.setContentTitle(
|
)
|
||||||
_context.getString(
|
)
|
||||||
R.string.download_progress_notification_untitled_text
|
} else {
|
||||||
)
|
builder.setContentTitle(
|
||||||
)
|
_context.getString(
|
||||||
} else {
|
R.string.download_progress_notification_text,
|
||||||
builder.setContentTitle(
|
currentItemTitle
|
||||||
_context.getString(
|
)
|
||||||
R.string.download_progress_notification_text,
|
)
|
||||||
currentItemTitle
|
}
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val cancelIntent = Intent().apply {
|
val cancelIntent = Intent().apply {
|
||||||
`package` = _context.packageName
|
`package` = _context.packageName
|
||||||
action = K.ACTION_DOWNLOAD_CANCEL
|
action = K.ACTION_DOWNLOAD_CANCEL
|
||||||
putExtra(K.EXTRA_NOTIFICATION_ID, id)
|
putExtra(K.EXTRA_NOTIFICATION_ID, id)
|
||||||
}
|
}
|
||||||
val cancelPendingIntent = PendingIntent.getBroadcast(
|
val cancelPendingIntent = PendingIntent.getBroadcast(
|
||||||
_context, 0, cancelIntent,
|
_context, 0, cancelIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
|
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
|
||||||
)
|
)
|
||||||
builder.addAction(
|
builder.addAction(
|
||||||
0, _context.getString(android.R.string.cancel), cancelPendingIntent
|
0, _context.getString(android.R.string.cancel), cancelPendingIntent
|
||||||
)
|
)
|
||||||
|
|
||||||
with(NotificationManagerCompat.from(_context)) {
|
with(NotificationManagerCompat.from(_context)) {
|
||||||
notify(id, builder.build())
|
notify(id, builder.build())
|
||||||
}
|
}
|
||||||
result.success(id)
|
result.success(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyLogSaveSuccessful(
|
private fun notifyLogSaveSuccessful(
|
||||||
fileUri: String, result: MethodChannel.Result
|
fileUri: String, result: MethodChannel.Result
|
||||||
) {
|
) {
|
||||||
val uri = Uri.parse(fileUri)
|
val uri = Uri.parse(fileUri)
|
||||||
val mimeType = "text/plain"
|
val mimeType = "text/plain"
|
||||||
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
|
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.baseline_download_white_18)
|
.setSmallIcon(R.drawable.baseline_download_white_18)
|
||||||
.setWhen(System.currentTimeMillis())
|
.setWhen(System.currentTimeMillis())
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
|
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
|
||||||
RingtoneManager.getDefaultUri(
|
RingtoneManager.getDefaultUri(
|
||||||
RingtoneManager.TYPE_NOTIFICATION
|
RingtoneManager.TYPE_NOTIFICATION
|
||||||
)
|
)
|
||||||
).setAutoCancel(true).setLocalOnly(true).setTicker(
|
).setAutoCancel(true).setLocalOnly(true).setTicker(
|
||||||
_context.getString(
|
_context.getString(
|
||||||
R.string.log_save_successful_notification_title
|
R.string.log_save_successful_notification_title
|
||||||
)
|
)
|
||||||
).setContentTitle(
|
).setContentTitle(
|
||||||
_context.getString(
|
_context.getString(
|
||||||
R.string.log_save_successful_notification_title
|
R.string.log_save_successful_notification_title
|
||||||
)
|
)
|
||||||
).setContentText(
|
).setContentText(
|
||||||
_context.getString(
|
_context.getString(
|
||||||
R.string.log_save_successful_notification_text
|
R.string.log_save_successful_notification_text
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val openIntent = Intent().apply {
|
val openIntent = Intent().apply {
|
||||||
action = Intent.ACTION_VIEW
|
action = Intent.ACTION_VIEW
|
||||||
setDataAndType(uri, mimeType)
|
setDataAndType(uri, mimeType)
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
}
|
}
|
||||||
val openPendingIntent = PendingIntent.getActivity(
|
val openPendingIntent = PendingIntent.getActivity(
|
||||||
_context, 0, openIntent,
|
_context, 0, openIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
|
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
|
||||||
)
|
)
|
||||||
builder.setContentIntent(openPendingIntent)
|
builder.setContentIntent(openPendingIntent)
|
||||||
|
|
||||||
// can't add the share action here because android will share the URI as
|
// can't add the share action here because android will share the URI as
|
||||||
// plain text instead of treating it as a text file...
|
// plain text instead of treating it as a text file...
|
||||||
|
|
||||||
val id = getNextNotificationId()
|
val id = getNextNotificationId()
|
||||||
with(NotificationManagerCompat.from(_context)) {
|
with(NotificationManagerCompat.from(_context)) {
|
||||||
notify(id, builder.build())
|
notify(id, builder.build())
|
||||||
}
|
}
|
||||||
result.success(id)
|
result.success(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dismiss(notificationId: Int, result: MethodChannel.Result) {
|
private fun dismiss(notificationId: Int, result: MethodChannel.Result) {
|
||||||
with(NotificationManagerCompat.from(_context)) {
|
with(NotificationManagerCompat.from(_context)) {
|
||||||
cancel(notificationId)
|
cancel(notificationId)
|
||||||
}
|
}
|
||||||
result.success(null)
|
result.success(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadNotificationImage(fileUri: Uri): Bitmap? {
|
private fun loadNotificationImage(fileUri: Uri): Bitmap? {
|
||||||
try {
|
try {
|
||||||
val resolver = _context.applicationContext.contentResolver
|
val resolver = _context.applicationContext.contentResolver
|
||||||
resolver.openFileDescriptor(fileUri, "r").use { pfd ->
|
resolver.openFileDescriptor(fileUri, "r").use { pfd ->
|
||||||
val metaOpts = BitmapFactory.Options().apply {
|
val metaOpts = BitmapFactory.Options().apply {
|
||||||
inJustDecodeBounds = true
|
inJustDecodeBounds = true
|
||||||
}
|
}
|
||||||
BitmapFactory.decodeFileDescriptor(
|
BitmapFactory.decodeFileDescriptor(
|
||||||
pfd!!.fileDescriptor, null, metaOpts
|
pfd!!.fileDescriptor, null, metaOpts
|
||||||
)
|
)
|
||||||
val longSide = max(metaOpts.outWidth, metaOpts.outHeight)
|
val longSide = max(metaOpts.outWidth, metaOpts.outHeight)
|
||||||
val opts = BitmapFactory.Options().apply {
|
val opts = BitmapFactory.Options().apply {
|
||||||
// just a preview in the panel, useless to be in high res
|
// just a preview in the panel, useless to be in high res
|
||||||
inSampleSize = longSide / 720
|
inSampleSize = longSide / 720
|
||||||
}
|
}
|
||||||
return BitmapFactory.decodeFileDescriptor(
|
return BitmapFactory.decodeFileDescriptor(
|
||||||
pfd.fileDescriptor, null, opts
|
pfd.fileDescriptor, null, opts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
logE(
|
logE(
|
||||||
"NotificationChannelHandler::loadNotificationImage",
|
"NotificationChannelHandler::loadNotificationImage",
|
||||||
"Failed generating preview image",
|
"Failed generating preview image", e
|
||||||
e
|
)
|
||||||
)
|
return null
|
||||||
return null
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun createDownloadChannel(context: Context) {
|
private fun createDownloadChannel(context: Context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val name = context.getString(
|
val name = context.getString(
|
||||||
R.string.download_notification_channel_name
|
R.string.download_notification_channel_name
|
||||||
)
|
)
|
||||||
val descriptionStr = context.getString(
|
val descriptionStr = context.getString(
|
||||||
R.string.download_notification_channel_description
|
R.string.download_notification_channel_description
|
||||||
)
|
)
|
||||||
val channel = NotificationChannel(
|
val channel = NotificationChannel(
|
||||||
DOWNLOAD_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH
|
DOWNLOAD_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH
|
||||||
).apply {
|
).apply {
|
||||||
description = descriptionStr
|
description = descriptionStr
|
||||||
}
|
}
|
||||||
|
|
||||||
val manager =
|
val manager = context.getSystemService(
|
||||||
context.getSystemService(
|
Context.NOTIFICATION_SERVICE
|
||||||
Context.NOTIFICATION_SERVICE
|
) as NotificationManager
|
||||||
) as NotificationManager
|
manager.createNotificationChannel(channel)
|
||||||
manager.createNotificationChannel(channel)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private val _context = context
|
private val _context = context
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,76 +6,68 @@ import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
internal class PreferenceChannelHandler(context: Context) :
|
internal class PreferenceChannelHandler(context: Context) :
|
||||||
MethodChannel.MethodCallHandler {
|
MethodChannel.MethodCallHandler {
|
||||||
companion object {
|
companion object {
|
||||||
const val METHOD_CHANNEL = "${K.LIB_ID}/preference_method"
|
const val METHOD_CHANNEL = "${K.LIB_ID}/preference_method"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"setBool" -> {
|
"setBool" -> {
|
||||||
try {
|
try {
|
||||||
setBool(
|
setBool(
|
||||||
call.argument("prefName")!!,
|
call.argument("prefName")!!, call.argument("key")!!,
|
||||||
call.argument("key")!!,
|
call.argument("value")!!, result
|
||||||
call.argument("value")!!,
|
)
|
||||||
result
|
} catch (e: Throwable) {
|
||||||
)
|
result.error("systemException", e.toString(), null)
|
||||||
} catch (e: Throwable) {
|
}
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"getBool" -> {
|
"getBool" -> {
|
||||||
try {
|
try {
|
||||||
getBool(
|
getBool(
|
||||||
call.argument("prefName")!!,
|
call.argument("prefName")!!, call.argument("key")!!,
|
||||||
call.argument("key")!!,
|
call.argument("defValue"), result
|
||||||
call.argument("defValue"),
|
)
|
||||||
result
|
} catch (e: Throwable) {
|
||||||
)
|
result.error("systemException", e.toString(), null)
|
||||||
} catch (e: Throwable) {
|
}
|
||||||
result.error("systemException", e.toString(), null)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setBool(
|
private fun setBool(
|
||||||
prefName: String,
|
prefName: String, key: String, value: Boolean,
|
||||||
key: String,
|
result: MethodChannel.Result
|
||||||
value: Boolean,
|
) {
|
||||||
result: MethodChannel.Result
|
openPref(prefName).run {
|
||||||
) {
|
edit().run {
|
||||||
openPref(prefName).run {
|
putBoolean(key, value)
|
||||||
edit().run {
|
}.apply()
|
||||||
putBoolean(key, value)
|
}
|
||||||
}.apply()
|
result.success(null)
|
||||||
}
|
}
|
||||||
result.success(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getBool(
|
private fun getBool(
|
||||||
prefName: String,
|
prefName: String, key: String, defValue: Boolean?,
|
||||||
key: String,
|
result: MethodChannel.Result
|
||||||
defValue: Boolean?,
|
) {
|
||||||
result: MethodChannel.Result
|
val product = openPref(prefName).run {
|
||||||
) {
|
if (contains(key)) {
|
||||||
val product = openPref(prefName).run {
|
getBoolean(key, false)
|
||||||
if (contains(key)) {
|
} else {
|
||||||
getBoolean(key, false)
|
defValue
|
||||||
} else {
|
}
|
||||||
defValue
|
}
|
||||||
}
|
result.success(product)
|
||||||
}
|
}
|
||||||
result.success(product)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openPref(prefName: String): SharedPreferences {
|
private fun openPref(prefName: String): SharedPreferences {
|
||||||
return context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
|
return context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val context = context
|
private val context = context
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue