Format native code

This commit is contained in:
Ming Ming 2023-09-01 18:23:55 +08:00
parent 6809503e0b
commit 68007d5d3e
45 changed files with 2888 additions and 3107 deletions

View file

@ -21,224 +21,214 @@ fun Bitmap.aspectRatio() = width / height.toFloat()
*/
@OptIn(ExperimentalContracts::class)
inline fun <T> Bitmap.use(block: (Bitmap) -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
try {
return block(this)
} finally {
recycle()
}
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
try {
return block(this)
} finally {
recycle()
}
}
enum class BitmapResizeMethod {
FIT, FILL,
FIT, FILL,
}
interface BitmapUtil {
companion object {
fun loadImageFixed(
context: Context, uri: Uri, targetW: Int, targetH: Int
): Bitmap {
val opt = loadImageBounds(context, uri)
val subsample = calcBitmapSubsample(
opt.outWidth,
opt.outHeight,
targetW,
targetH,
BitmapResizeMethod.FILL
)
if (subsample > 1) {
logD(
TAG,
"Subsample image to fixed: $subsample ${opt.outWidth}x${opt.outHeight} -> ${targetW}x$targetH"
)
}
val outOpt = BitmapFactory.Options().apply {
inSampleSize = subsample
}
val bitmap = loadImage(context, uri, outOpt)
if (subsample > 1) {
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
}
return Bitmap.createScaledBitmap(bitmap, targetW, targetH, true)
}
companion object {
fun loadImageFixed(
context: Context, uri: Uri, targetW: Int, targetH: Int
): Bitmap {
val opt = loadImageBounds(context, uri)
val subsample = calcBitmapSubsample(
opt.outWidth, opt.outHeight, targetW, targetH,
BitmapResizeMethod.FILL
)
if (subsample > 1) {
logD(
TAG,
"Subsample image to fixed: $subsample ${opt.outWidth}x${opt.outHeight} -> ${targetW}x$targetH"
)
}
val outOpt = BitmapFactory.Options().apply {
inSampleSize = subsample
}
val bitmap = loadImage(context, uri, outOpt)
if (subsample > 1) {
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
}
return Bitmap.createScaledBitmap(bitmap, targetW, targetH, true)
}
/**
* Load a bitmap
*
* 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.,
* bitmap.w <= @c targetW and bitmap.h <= @c targetH
*
* 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.,
* bitmap.w >= @c targetW and bitmap.h >= @c targetH
*
* If bitmap is smaller than the bound and @c shouldUpscale == true, it
* will be upscaled
*
* @param context
* @param uri
* @param targetW
* @param targetH
* @param resizeMethod
* @param isAllowSwapSide
* @param shouldUpscale
* @return
*/
fun loadImage(
context: Context,
uri: Uri,
targetW: Int,
targetH: Int,
resizeMethod: BitmapResizeMethod,
isAllowSwapSide: Boolean = false,
shouldUpscale: Boolean = true,
shouldFixOrientation: Boolean = false,
): Bitmap {
val opt = loadImageBounds(context, uri)
val shouldSwapSide =
isAllowSwapSide && opt.outWidth != opt.outHeight && (opt.outWidth >= opt.outHeight) != (targetW >= targetH)
val dstW = if (shouldSwapSide) targetH else targetW
val dstH = if (shouldSwapSide) targetW else targetH
val subsample = calcBitmapSubsample(
opt.outWidth, opt.outHeight, dstW, dstH, resizeMethod
)
if (subsample > 1) {
logD(
TAG,
"Subsample image to ${resizeMethod.name}: $subsample ${opt.outWidth}x${opt.outHeight} -> ${dstW}x$dstH" + (if (shouldSwapSide) " (swapped)" else "")
)
}
val outOpt = BitmapFactory.Options().apply {
inSampleSize = subsample
}
val bitmap = loadImage(context, uri, outOpt)
if (subsample > 1) {
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
}
if (bitmap.width < dstW && bitmap.height < dstH && !shouldUpscale) {
return if (shouldFixOrientation) {
fixOrientation(context, uri, bitmap)
} else {
bitmap
}
}
val result = when (resizeMethod) {
BitmapResizeMethod.FIT -> Bitmap.createScaledBitmap(
bitmap,
minOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
minOf(dstH, (dstW / bitmap.aspectRatio()).toInt()),
true
)
/**
* Load a bitmap
*
* 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.,
* bitmap.w <= @c targetW and bitmap.h <= @c targetH
*
* 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.,
* bitmap.w >= @c targetW and bitmap.h >= @c targetH
*
* If bitmap is smaller than the bound and @c shouldUpscale == true, it
* will be upscaled
*
* @param context
* @param uri
* @param targetW
* @param targetH
* @param resizeMethod
* @param isAllowSwapSide
* @param shouldUpscale
* @return
*/
fun loadImage(
context: Context,
uri: Uri,
targetW: Int,
targetH: Int,
resizeMethod: BitmapResizeMethod,
isAllowSwapSide: Boolean = false,
shouldUpscale: Boolean = true,
shouldFixOrientation: Boolean = false,
): Bitmap {
val opt = loadImageBounds(context, uri)
val shouldSwapSide =
isAllowSwapSide && opt.outWidth != opt.outHeight && (opt.outWidth >= opt.outHeight) != (targetW >= targetH)
val dstW = if (shouldSwapSide) targetH else targetW
val dstH = if (shouldSwapSide) targetW else targetH
val subsample = calcBitmapSubsample(
opt.outWidth, opt.outHeight, dstW, dstH, resizeMethod
)
if (subsample > 1) {
logD(
TAG,
"Subsample image to ${resizeMethod.name}: $subsample ${opt.outWidth}x${opt.outHeight} -> ${dstW}x$dstH" + (if (shouldSwapSide) " (swapped)" else "")
)
}
val outOpt = BitmapFactory.Options().apply {
inSampleSize = subsample
}
val bitmap = loadImage(context, uri, outOpt)
if (subsample > 1) {
logD(TAG, "Bitmap subsampled: ${bitmap.width}x${bitmap.height}")
}
if (bitmap.width < dstW && bitmap.height < dstH && !shouldUpscale) {
return if (shouldFixOrientation) {
fixOrientation(context, uri, bitmap)
} else {
bitmap
}
}
val result = when (resizeMethod) {
BitmapResizeMethod.FIT -> Bitmap.createScaledBitmap(
bitmap, minOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
minOf(dstH, (dstW / bitmap.aspectRatio()).toInt()), true
)
BitmapResizeMethod.FILL -> Bitmap.createScaledBitmap(
bitmap,
maxOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
maxOf(dstH, (dstW / bitmap.aspectRatio()).toInt()),
true
)
}
return if (shouldFixOrientation) {
fixOrientation(context, uri, result)
} else {
result
}
}
BitmapResizeMethod.FILL -> Bitmap.createScaledBitmap(
bitmap, maxOf(dstW, (dstH * bitmap.aspectRatio()).toInt()),
maxOf(dstH, (dstW / bitmap.aspectRatio()).toInt()), true
)
}
return if (shouldFixOrientation) {
fixOrientation(context, uri, result)
} else {
result
}
}
/**
* Rotate the image to its visible orientation
*/
private fun fixOrientation(
context: Context, uri: Uri, bitmap: Bitmap
): Bitmap {
return try {
openUriInputStream(context, uri)!!.use {
val iExif = ExifInterface(it)
val orientation =
iExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
logI(
TAG,
"[fixOrientation] Input file orientation: $orientation"
)
val rotate = when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90, ExifInterface.ORIENTATION_TRANSPOSE -> 90f
/**
* Rotate the image to its visible orientation
*/
private fun fixOrientation(
context: Context, uri: Uri, bitmap: Bitmap
): Bitmap {
return try {
openUriInputStream(context, uri)!!.use {
val iExif = ExifInterface(it)
val orientation =
iExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
logI(
TAG,
"[fixOrientation] Input file orientation: $orientation"
)
val rotate = when (orientation) {
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
else -> return bitmap
}
val mat = Matrix()
mat.postRotate(rotate)
if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || orientation == ExifInterface.ORIENTATION_TRANSVERSE || orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || orientation == ExifInterface.ORIENTATION_TRANSPOSE) {
mat.postScale(-1f, 1f)
}
Bitmap.createBitmap(
bitmap, 0, 0, bitmap.width, bitmap.height, mat, true
)
}
} catch (e: Throwable) {
logE(
TAG,
"[fixOrientation] Failed fixing, assume normal orientation",
e
)
bitmap
}
}
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> 0f
else -> return bitmap
}
val mat = Matrix()
mat.postRotate(rotate)
if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || orientation == ExifInterface.ORIENTATION_TRANSVERSE || orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || orientation == ExifInterface.ORIENTATION_TRANSPOSE) {
mat.postScale(-1f, 1f)
}
Bitmap.createBitmap(
bitmap, 0, 0, bitmap.width, bitmap.height, mat, true
)
}
} catch (e: Throwable) {
logE(
TAG,
"[fixOrientation] Failed fixing, assume normal orientation",
e
)
bitmap
}
}
private fun openUriInputStream(
context: Context, uri: Uri
): InputStream? {
return if (UriUtil.isAssetUri(uri)) {
context.assets.open(UriUtil.getAssetUriPath(uri))
} else {
context.contentResolver.openInputStream(uri)
}
}
private fun openUriInputStream(
context: Context, uri: Uri
): InputStream? {
return if (UriUtil.isAssetUri(uri)) {
context.assets.open(UriUtil.getAssetUriPath(uri))
} else {
context.contentResolver.openInputStream(uri)
}
}
private fun loadImageBounds(
context: Context, uri: Uri
): BitmapFactory.Options {
openUriInputStream(context, uri)!!.use {
val opt = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeStream(it, null, opt)
return opt
}
}
private fun loadImageBounds(
context: Context, uri: Uri
): BitmapFactory.Options {
openUriInputStream(context, uri)!!.use {
val opt = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeStream(it, null, opt)
return opt
}
}
private fun loadImage(
context: Context, uri: Uri, opt: BitmapFactory.Options
): Bitmap {
openUriInputStream(context, uri)!!.use {
return BitmapFactory.decodeStream(it, null, opt)!!
}
}
private fun loadImage(
context: Context, uri: Uri, opt: BitmapFactory.Options
): Bitmap {
openUriInputStream(context, uri)!!.use {
return BitmapFactory.decodeStream(it, null, opt)!!
}
}
private fun calcBitmapSubsample(
originalW: Int,
originalH: Int,
targetW: Int,
targetH: Int,
resizeMethod: BitmapResizeMethod
): Int {
return when (resizeMethod) {
BitmapResizeMethod.FIT -> maxOf(
originalW / targetW, originalH / targetH
)
private fun calcBitmapSubsample(
originalW: Int, originalH: Int, targetW: Int, targetH: Int,
resizeMethod: BitmapResizeMethod
): Int {
return when (resizeMethod) {
BitmapResizeMethod.FIT -> maxOf(
originalW / targetW, originalH / targetH
)
BitmapResizeMethod.FILL -> minOf(
originalW / targetW, originalH / targetH
)
}
}
BitmapResizeMethod.FILL -> minOf(
originalW / targetW, originalH / targetH
)
}
}
private const val TAG = "BitmapUtil"
}
private const val TAG = "BitmapUtil"
}
}

View file

@ -113,10 +113,10 @@ interface MediaStoreUtil {
throw PermissionException("Permission not granted")
}
@Suppress("Deprecation")
val path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS
)
@Suppress("Deprecation") val path =
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS
)
val prefix = if (subDir != null) "$subDir/" else ""
var file = File(path, prefix + filename)
val baseFilename = file.nameWithoutExtension
@ -142,8 +142,8 @@ interface MediaStoreUtil {
private fun triggerMediaScan(context: Context, uri: Uri) {
val scanIntent = Intent().apply {
@Suppress("Deprecation")
action = Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
@Suppress("Deprecation") action =
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
data = uri
}
context.sendBroadcast(scanIntent)

View file

@ -8,35 +8,35 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
interface PermissionUtil {
companion object {
const val REQUEST_CODE = K.PERMISSION_REQUEST_CODE
companion object {
const val REQUEST_CODE = K.PERMISSION_REQUEST_CODE
fun request(
activity: Activity, vararg permissions: String
) {
ActivityCompat.requestPermissions(
activity, permissions, REQUEST_CODE
)
}
fun request(
activity: Activity, vararg permissions: String
) {
ActivityCompat.requestPermissions(
activity, permissions, REQUEST_CODE
)
}
fun hasReadExternalStorage(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context, Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
fun hasReadExternalStorage(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context, Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
fun requestReadExternalStorage(activity: Activity) = request(
activity, Manifest.permission.READ_EXTERNAL_STORAGE
)
fun requestReadExternalStorage(activity: Activity) = request(
activity, Manifest.permission.READ_EXTERNAL_STORAGE
)
fun hasWriteExternalStorage(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context, Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
fun hasWriteExternalStorage(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context, Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
fun requestWriteExternalStorage(activity: Activity) = request(
activity, Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
fun requestWriteExternalStorage(activity: Activity) = request(
activity, Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
}

View file

@ -7,38 +7,37 @@ import java.nio.ByteBuffer
* Container of pixel data stored in RGBA format
*/
class Rgba8Image(
val pixel: ByteArray, val width: Int, val height: Int
val pixel: ByteArray, val width: Int, val height: Int
) {
companion object {
fun fromJson(json: Map<String, Any>) = Rgba8Image(
json["pixel"] as ByteArray,
json["width"] as Int,
json["height"] as Int
)
companion object {
fun fromJson(json: Map<String, Any>) = Rgba8Image(
json["pixel"] as ByteArray, json["width"] as Int,
json["height"] as Int
)
fun fromBitmap(src: Bitmap): Rgba8Image {
assert(src.config == Bitmap.Config.ARGB_8888)
val buffer = ByteBuffer.allocate(src.width * src.height * 4).also {
src.copyPixelsToBuffer(it)
}
return Rgba8Image(buffer.array(), src.width, src.height)
}
}
fun fromBitmap(src: Bitmap): Rgba8Image {
assert(src.config == Bitmap.Config.ARGB_8888)
val buffer = ByteBuffer.allocate(src.width * src.height * 4).also {
src.copyPixelsToBuffer(it)
}
return Rgba8Image(buffer.array(), src.width, src.height)
}
}
fun toJson() = mapOf<String, Any>(
"pixel" to pixel,
"width" to width,
"height" to height,
)
fun toJson() = mapOf<String, Any>(
"pixel" to pixel,
"width" to width,
"height" to height,
)
fun toBitmap(): Bitmap {
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
.apply {
copyPixelsFromBuffer(ByteBuffer.wrap(pixel))
}
}
fun toBitmap(): Bitmap {
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
.apply {
copyPixelsFromBuffer(ByteBuffer.wrap(pixel))
}
}
init {
assert(pixel.size == width * height * 4)
}
init {
assert(pixel.size == width * height * 4)
}
}

View file

@ -7,13 +7,11 @@ import java.io.Serializable
import java.net.HttpURLConnection
fun getPendingIntentFlagImmutable(): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_IMMUTABLE else 0
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_IMMUTABLE else 0
}
fun getPendingIntentFlagMutable(): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_MUTABLE else 0
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0
}
inline fun <T> HttpURLConnection.use(block: (HttpURLConnection) -> T): T {

View file

@ -5,9 +5,9 @@ import android.net.Uri
internal interface MessageEvent
internal data class ImageProcessorCompletedEvent(
val result: Uri,
val result: Uri,
) : MessageEvent
internal data class ImageProcessorFailedEvent(
val exception: Throwable,
val exception: Throwable,
) : MessageEvent

View file

@ -1,6 +1,6 @@
package com.nkming.nc_photos.np_platform_image_processor
internal class HttpException(statusCode: Int, message: String) :
Exception(message)
Exception(message)
internal class NativeException(message: String) : Exception(message)

View file

@ -21,386 +21,282 @@ import io.flutter.plugin.common.MethodChannel
import java.io.Serializable
internal class ImageProcessorChannelHandler(context: Context) :
MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
companion object {
const val METHOD_CHANNEL = "${K.LIB_ID}/image_processor_method"
MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
companion object {
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) {
when (call.method) {
"zeroDce" -> {
try {
zeroDce(
call.argument("fileUrl")!!,
call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
call.argument("iteration")!!,
result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"zeroDce" -> {
try {
zeroDce(
call.argument("fileUrl")!!, call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
call.argument("iteration")!!, result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"deepLab3Portrait" -> {
try {
deepLab3Portrait(
call.argument("fileUrl")!!,
call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
call.argument("radius")!!,
result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"deepLab3Portrait" -> {
try {
deepLab3Portrait(
call.argument("fileUrl")!!, call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
call.argument("radius")!!, result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"esrgan" -> {
try {
esrgan(
call.argument("fileUrl")!!,
call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"esrgan" -> {
try {
esrgan(
call.argument("fileUrl")!!, call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!, result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"arbitraryStyleTransfer" -> {
try {
arbitraryStyleTransfer(
call.argument("fileUrl")!!,
call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
call.argument("styleUri")!!,
call.argument("weight")!!,
result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"arbitraryStyleTransfer" -> {
try {
arbitraryStyleTransfer(
call.argument("fileUrl")!!, call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
call.argument("styleUri")!!, call.argument("weight")!!,
result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"deepLab3ColorPop" -> {
try {
deepLab3ColorPop(
call.argument("fileUrl")!!,
call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
call.argument("weight")!!,
result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"deepLab3ColorPop" -> {
try {
deepLab3ColorPop(
call.argument("fileUrl")!!, call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
call.argument("weight")!!, result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"neurOp" -> {
try {
neurOp(
call.argument("fileUrl")!!,
call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"neurOp" -> {
try {
neurOp(
call.argument("fileUrl")!!, call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!, result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"filter" -> {
try {
filter(
call.argument("fileUrl")!!,
call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
call.argument("filters")!!,
result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"filter" -> {
try {
filter(
call.argument("fileUrl")!!, call.argument("headers"),
call.argument("filename")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument<Boolean>("isSaveToServer")!!,
call.argument("filters")!!, result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"filterPreview" -> {
try {
filterPreview(
call.argument("rgba8")!!,
call.argument("filters")!!,
result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
"filterPreview" -> {
try {
filterPreview(
call.argument("rgba8")!!, call.argument("filters")!!,
result
)
} catch (e: Throwable) {
logE(TAG, "Uncaught exception", e)
result.error("systemException", e.toString(), null)
}
}
else -> result.notImplemented()
}
}
else -> result.notImplemented()
}
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
private fun zeroDce(
fileUrl: String,
headers: Map<String, String>?,
filename: String,
maxWidth: Int,
maxHeight: Int,
isSaveToServer: Boolean,
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 zeroDce(
fileUrl: String, headers: Map<String, String>?, filename: String,
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, 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(
fileUrl: String,
headers: Map<String, String>?,
filename: String,
maxWidth: Int,
maxHeight: Int,
isSaveToServer: Boolean,
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 deepLab3Portrait(
fileUrl: String, headers: Map<String, String>?, filename: String,
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, 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(
fileUrl: String,
headers: Map<String, String>?,
filename: String,
maxWidth: Int,
maxHeight: Int,
isSaveToServer: Boolean,
result: MethodChannel.Result
) = method(
fileUrl,
headers,
filename,
maxWidth,
maxHeight,
isSaveToServer,
ImageProcessorService.METHOD_ESRGAN,
result
)
private fun esrgan(
fileUrl: String, headers: Map<String, String>?, filename: String,
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean,
result: MethodChannel.Result
) = method(
fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer,
ImageProcessorService.METHOD_ESRGAN, result
)
private fun arbitraryStyleTransfer(
fileUrl: String,
headers: Map<String, String>?,
filename: String,
maxWidth: Int,
maxHeight: Int,
isSaveToServer: Boolean,
styleUri: String,
weight: Float,
result: MethodChannel.Result
) = method(fileUrl,
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 arbitraryStyleTransfer(
fileUrl: String, headers: Map<String, String>?, filename: String,
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean,
styleUri: String, weight: Float, result: MethodChannel.Result
) = method(fileUrl, 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(
fileUrl: String,
headers: Map<String, String>?,
filename: String,
maxWidth: Int,
maxHeight: Int,
isSaveToServer: Boolean,
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 deepLab3ColorPop(
fileUrl: String, headers: Map<String, String>?, filename: String,
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, 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(
fileUrl: String,
headers: Map<String, String>?,
filename: String,
maxWidth: Int,
maxHeight: Int,
isSaveToServer: Boolean,
result: MethodChannel.Result
) = method(
fileUrl,
headers,
filename,
maxWidth,
maxHeight,
isSaveToServer,
ImageProcessorService.METHOD_NEUR_OP,
result
)
private fun neurOp(
fileUrl: String, headers: Map<String, String>?, filename: String,
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean,
result: MethodChannel.Result
) = method(
fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer,
ImageProcessorService.METHOD_NEUR_OP, result
)
private fun filter(
fileUrl: String,
headers: Map<String, String>?,
filename: String,
maxWidth: Int,
maxHeight: Int,
isSaveToServer: Boolean,
filters: List<Map<String, Any>>,
result: MethodChannel.Result
) {
// convert to serializable
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 filter(
fileUrl: String, headers: Map<String, String>?, filename: String,
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean,
filters: List<Map<String, Any>>, result: MethodChannel.Result
) {
// convert to serializable
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(
rgba8: Map<String, Any>,
filters: List<Map<String, Any>>,
result: MethodChannel.Result
) {
var img = Rgba8Image.fromJson(rgba8)
for (f in filters.map(ImageFilter::fromJson)) {
img = f.apply(img)
}
result.success(img.toJson())
}
private fun filterPreview(
rgba8: Map<String, Any>, filters: List<Map<String, Any>>,
result: MethodChannel.Result
) {
var img = Rgba8Image.fromJson(rgba8)
for (f in filters.map(ImageFilter::fromJson)) {
img = f.apply(img)
}
result.success(img.toJson())
}
private fun method(
fileUrl: String,
headers: Map<String, String>?,
filename: String,
maxWidth: Int,
maxHeight: Int,
isSaveToServer: Boolean,
method: String,
result: MethodChannel.Result,
onIntent: ((Intent) -> Unit)? = null
) {
val intent = Intent(context, ImageProcessorService::class.java).apply {
putExtra(ImageProcessorService.EXTRA_METHOD, method)
putExtra(ImageProcessorService.EXTRA_FILE_URL, fileUrl)
putExtra(ImageProcessorService.EXTRA_HEADERS,
headers?.let { HashMap(it) })
putExtra(ImageProcessorService.EXTRA_FILENAME, filename)
putExtra(ImageProcessorService.EXTRA_MAX_WIDTH, maxWidth)
putExtra(ImageProcessorService.EXTRA_MAX_HEIGHT, maxHeight)
putExtra(
ImageProcessorService.EXTRA_IS_SAVE_TO_SERVER, isSaveToServer
)
onIntent?.invoke(this)
}
ContextCompat.startForegroundService(context, intent)
result.success(null)
}
private fun method(
fileUrl: String, headers: Map<String, String>?, filename: String,
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, method: String,
result: MethodChannel.Result, onIntent: ((Intent) -> Unit)? = null
) {
val intent = Intent(context, ImageProcessorService::class.java).apply {
putExtra(ImageProcessorService.EXTRA_METHOD, method)
putExtra(ImageProcessorService.EXTRA_FILE_URL, fileUrl)
putExtra(ImageProcessorService.EXTRA_HEADERS,
headers?.let { HashMap(it) })
putExtra(ImageProcessorService.EXTRA_FILENAME, filename)
putExtra(ImageProcessorService.EXTRA_MAX_WIDTH, maxWidth)
putExtra(ImageProcessorService.EXTRA_MAX_HEIGHT, maxHeight)
putExtra(
ImageProcessorService.EXTRA_IS_SAVE_TO_SERVER, isSaveToServer
)
onIntent?.invoke(this)
}
ContextCompat.startForegroundService(context, intent)
result.success(null)
}
private val context = context
private var eventSink: EventChannel.EventSink? = null
private val context = context
private var eventSink: EventChannel.EventSink? = null
}
internal interface ImageFilter {
companion object {
fun fromJson(json: Map<String, Any>): ImageFilter {
return when (json["type"]) {
"brightness" -> Brightness((json["weight"] as Double).toFloat())
"contrast" -> Contrast((json["weight"] as Double).toFloat())
"whitePoint" -> WhitePoint((json["weight"] as Double).toFloat())
"blackPoint" -> BlackPoint((json["weight"] as Double).toFloat())
"saturation" -> Saturation((json["weight"] as Double).toFloat())
"warmth" -> Warmth((json["weight"] as Double).toFloat())
"tint" -> Tint((json["weight"] as Double).toFloat())
"orientation" -> Orientation(json["degree"] as Int)
"crop" -> Crop(
json["top"] as Double,
json["left"] as Double,
json["bottom"] as Double,
json["right"] as Double
)
companion object {
fun fromJson(json: Map<String, Any>): ImageFilter {
return when (json["type"]) {
"brightness" -> Brightness((json["weight"] as Double).toFloat())
"contrast" -> Contrast((json["weight"] as Double).toFloat())
"whitePoint" -> WhitePoint((json["weight"] as Double).toFloat())
"blackPoint" -> BlackPoint((json["weight"] as Double).toFloat())
"saturation" -> Saturation((json["weight"] as Double).toFloat())
"warmth" -> Warmth((json["weight"] as Double).toFloat())
"tint" -> Tint((json["weight"] as Double).toFloat())
"orientation" -> Orientation(json["degree"] as Int)
"crop" -> Crop(
json["top"] as Double, json["left"] as Double,
json["bottom"] as Double, json["right"] as Double
)
else -> throw IllegalArgumentException(
"Unknown type: ${json["type"]}"
)
}
}
}
else -> throw IllegalArgumentException(
"Unknown type: ${json["type"]}"
)
}
}
}
fun apply(rgba8: Rgba8Image): Rgba8Image
fun apply(rgba8: Rgba8Image): Rgba8Image
}

View file

@ -1,16 +1,16 @@
package com.nkming.nc_photos.np_platform_image_processor
internal interface K {
companion object {
const val LIB_ID = "com.nkming.nc_photos.np_platform_image_processor"
companion object {
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_RESULT_NOTIFICATION_ID = 5001
const val IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID = 5002
const val IMAGE_PROCESSOR_SERVICE_NOTIFICATION_ID = 5000
const val IMAGE_PROCESSOR_SERVICE_RESULT_NOTIFICATION_ID = 5001
const val IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID = 5002
const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT =
"${LIB_ID}.ACTION_SHOW_IMAGE_PROCESSOR_RESULT"
const val 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"
}
}

View file

@ -2,14 +2,14 @@ package com.nkming.nc_photos.np_platform_image_processor
// To be removed
internal interface NativeEvent {
fun getId(): String
fun getData(): String? = null
fun getId(): String
fun getData(): String? = null
}
internal class ImageProcessorUploadSuccessEvent : NativeEvent {
companion object {
const val id = "ImageProcessorUploadSuccessEvent"
}
companion object {
const val id = "ImageProcessorUploadSuccessEvent"
}
override fun getId() = id
override fun getId() = id
}

View file

@ -5,68 +5,68 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
internal class NativeEventChannelHandler : MethodChannel.MethodCallHandler,
EventChannel.StreamHandler {
companion object {
const val EVENT_CHANNEL = "${K.LIB_ID}/native_event"
const val METHOD_CHANNEL = "${K.LIB_ID}/native_event_method"
EventChannel.StreamHandler {
companion object {
const val EVENT_CHANNEL = "${K.LIB_ID}/native_event"
const val METHOD_CHANNEL = "${K.LIB_ID}/native_event_method"
/**
* Fire native events on the native side
*/
fun fire(eventObj: NativeEvent) {
synchronized(eventSinks) {
for (s in eventSinks.values) {
s.success(buildMap {
put("event", eventObj.getId())
eventObj.getData()?.also { put("data", it) }
})
}
}
}
/**
* Fire native events on the native side
*/
fun fire(eventObj: NativeEvent) {
synchronized(eventSinks) {
for (s in eventSinks.values) {
s.success(buildMap {
put("event", eventObj.getId())
eventObj.getData()?.also { put("data", it) }
})
}
}
}
private val eventSinks = mutableMapOf<Int, EventChannel.EventSink>()
private var nextId = 0
}
private val eventSinks = mutableMapOf<Int, EventChannel.EventSink>()
private var nextId = 0
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"fire" -> {
try {
fire(
call.argument("event")!!, call.argument("data"), result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"fire" -> {
try {
fire(
call.argument("event")!!, call.argument("data"), result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
}
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
synchronized(eventSinks) {
eventSinks[id] = events
}
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
synchronized(eventSinks) {
eventSinks[id] = events
}
}
override fun onCancel(arguments: Any?) {
synchronized(eventSinks) {
eventSinks.remove(id)
}
}
override fun onCancel(arguments: Any?) {
synchronized(eventSinks) {
eventSinks.remove(id)
}
}
private fun fire(
event: String, data: String?, result: MethodChannel.Result
) {
synchronized(eventSinks) {
for (s in eventSinks.values) {
s.success(buildMap {
put("event", event)
if (data != null) put("data", data)
})
}
}
result.success(null)
}
private fun fire(
event: String, data: String?, result: MethodChannel.Result
) {
synchronized(eventSinks) {
for (s in eventSinks.values) {
s.success(buildMap {
put("event", event)
if (data != null) put("data", data)
})
}
}
result.success(null)
}
private val id = nextId++
private val id = nextId++
}

View file

@ -6,52 +6,51 @@ import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
class NpPlatformImageProcessorPlugin : FlutterPlugin {
companion object {
init {
System.loadLibrary("np_platform_image_processor")
}
companion object {
init {
System.loadLibrary("np_platform_image_processor")
}
const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT =
K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT
const val EXTRA_IMAGE_RESULT_URI =
K.EXTRA_IMAGE_RESULT_URI
}
const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT =
K.ACTION_SHOW_IMAGE_PROCESSOR_RESULT
const val EXTRA_IMAGE_RESULT_URI = K.EXTRA_IMAGE_RESULT_URI
}
override fun onAttachedToEngine(
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
) {
imageProcessorMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
ImageProcessorChannelHandler.METHOD_CHANNEL
)
imageProcessorMethodChannel.setMethodCallHandler(
ImageProcessorChannelHandler(
flutterPluginBinding.applicationContext
)
)
override fun onAttachedToEngine(
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
) {
imageProcessorMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
ImageProcessorChannelHandler.METHOD_CHANNEL
)
imageProcessorMethodChannel.setMethodCallHandler(
ImageProcessorChannelHandler(
flutterPluginBinding.applicationContext
)
)
val nativeEventHandler = NativeEventChannelHandler()
nativeEventChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
NativeEventChannelHandler.EVENT_CHANNEL
)
nativeEventChannel.setStreamHandler(nativeEventHandler)
nativeEventMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
NativeEventChannelHandler.METHOD_CHANNEL
)
nativeEventMethodChannel.setMethodCallHandler(nativeEventHandler)
}
val nativeEventHandler = NativeEventChannelHandler()
nativeEventChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
NativeEventChannelHandler.EVENT_CHANNEL
)
nativeEventChannel.setStreamHandler(nativeEventHandler)
nativeEventMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
NativeEventChannelHandler.METHOD_CHANNEL
)
nativeEventMethodChannel.setMethodCallHandler(nativeEventHandler)
}
override fun onDetachedFromEngine(
@NonNull binding: FlutterPlugin.FlutterPluginBinding
) {
imageProcessorMethodChannel.setMethodCallHandler(null)
nativeEventChannel.setStreamHandler(null)
nativeEventMethodChannel.setMethodCallHandler(null)
}
override fun onDetachedFromEngine(
@NonNull binding: FlutterPlugin.FlutterPluginBinding
) {
imageProcessorMethodChannel.setMethodCallHandler(null)
nativeEventChannel.setStreamHandler(null)
nativeEventMethodChannel.setMethodCallHandler(null)
}
private lateinit var imageProcessorMethodChannel: MethodChannel
private lateinit var nativeEventChannel: EventChannel
private lateinit var nativeEventMethodChannel: MethodChannel
private lateinit var imageProcessorMethodChannel: MethodChannel
private lateinit var nativeEventChannel: EventChannel
private lateinit var nativeEventMethodChannel: MethodChannel
}

View file

@ -10,79 +10,62 @@ import com.nkming.nc_photos.np_android_core.logI
import com.nkming.nc_photos.np_android_core.use
internal class ArbitraryStyleTransfer(
context: Context,
maxWidth: Int,
maxHeight: Int,
styleUri: Uri,
weight: Float
context: Context, maxWidth: Int, maxHeight: Int, styleUri: Uri,
weight: Float
) {
companion object {
const val TAG = "ArbitraryStyleTransfer"
}
companion object {
const val TAG = "ArbitraryStyleTransfer"
}
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context,
imageUri,
maxWidth,
maxHeight,
BitmapResizeMethod.FIT,
isAllowSwapSide = true,
shouldUpscale = false,
shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val rgb8Style = BitmapUtil.loadImage(
context,
styleUri,
256,
256,
BitmapResizeMethod.FILL,
isAllowSwapSide = false,
shouldUpscale = true
).use {
val styleBitmap = if (it.width != 256 || it.height != 256) {
val x = (it.width - 256) / 2
val y = (it.height - 256) / 2
logI(
TAG,
"[infer] Resize and crop style image: ${it.width}x${it.height} -> 256x256 ($x, $y)"
)
// crop
Bitmap.createBitmap(it, x, y, 256, 256)
} else {
it
}
styleBitmap.use {
TfLiteHelper.bitmapToRgb8Array(styleBitmap)
}
}
val am = context.assets
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
isAllowSwapSide = true, shouldUpscale = false,
shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val rgb8Style = BitmapUtil.loadImage(
context, styleUri, 256, 256, BitmapResizeMethod.FILL,
isAllowSwapSide = false, shouldUpscale = true
).use {
val styleBitmap = if (it.width != 256 || it.height != 256) {
val x = (it.width - 256) / 2
val y = (it.height - 256) / 2
logI(
TAG,
"[infer] Resize and crop style image: ${it.width}x${it.height} -> 256x256 ($x, $y)"
)
// crop
Bitmap.createBitmap(it, x, y, 256, 256)
} else {
it
}
styleBitmap.use {
TfLiteHelper.bitmapToRgb8Array(styleBitmap)
}
}
val am = context.assets
return inferNative(
am, rgb8Image, width, height, rgb8Style, weight
).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
}
}
return inferNative(
am, rgb8Image, width, height, rgb8Style, weight
).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
}
}
private external fun inferNative(
am: AssetManager,
image: ByteArray,
width: Int,
height: Int,
style: ByteArray,
weight: Float
): ByteArray
private external fun inferNative(
am: AssetManager, image: ByteArray, width: Int, height: Int,
style: ByteArray, weight: Float
): ByteArray
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val styleUri = styleUri
private val weight = weight
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val styleUri = styleUri
private val weight = weight
}

View file

@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class BlackPoint(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width,
rgba8.height
)
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.height
)
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
}

View file

@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Brightness(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width,
rgba8.height
)
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.height
)
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
}

View file

@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Contrast(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width,
rgba8.height
)
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.height
)
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
}

View file

@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Cool(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width,
rgba8.height
)
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.height
)
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
}

View file

@ -5,27 +5,22 @@ import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
import java.lang.Integer.max
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 {
override fun apply(rgba8: Rgba8Image): Rgba8Image {
// prevent w/h == 0
val width = max((rgba8.width * (right - left)).toInt(), 1)
val height = max((rgba8.height * (bottom - top)).toInt(), 1)
val top = (rgba8.height * top).toInt()
val left = (rgba8.width * left).toInt()
val data = applyNative(
rgba8.pixel, rgba8.width, rgba8.height, top, left, width, height
)
return Rgba8Image(data, width, height)
}
override fun apply(rgba8: Rgba8Image): Rgba8Image {
// prevent w/h == 0
val width = max((rgba8.width * (right - left)).toInt(), 1)
val height = max((rgba8.height * (bottom - top)).toInt(), 1)
val top = (rgba8.height * top).toInt()
val left = (rgba8.width * left).toInt()
val data = applyNative(
rgba8.pixel, rgba8.width, rgba8.height, top, left, width, height
)
return Rgba8Image(data, width, height)
}
private external fun applyNative(
rgba8: ByteArray,
width: Int,
height: Int,
top: Int,
left: Int,
dstWidth: Int,
dstHeight: Int
): ByteArray
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, top: Int, left: Int,
dstWidth: Int, dstHeight: Int
): ByteArray
}

View file

@ -16,79 +16,66 @@ import com.nkming.nc_photos.np_android_core.use
* See: https://github.com/tensorflow/models/tree/master/research/deeplab
*/
internal class DeepLab3Portrait(
context: Context, maxWidth: Int, maxHeight: Int, radius: Int
context: Context, maxWidth: Int, maxHeight: Int, radius: Int
) {
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context,
imageUri,
maxWidth,
maxHeight,
BitmapResizeMethod.FIT,
isAllowSwapSide = true,
shouldUpscale = false,
shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val am = context.assets
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
isAllowSwapSide = true, shouldUpscale = false,
shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val am = context.assets
return inferNative(am, rgb8Image, width, height, radius).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
}
}
return inferNative(am, rgb8Image, width, height, radius).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
}
}
private external fun inferNative(
am: AssetManager, image: ByteArray, width: Int, height: Int, radius: Int
): ByteArray
private external fun inferNative(
am: AssetManager, image: ByteArray, width: Int, height: Int, radius: Int
): ByteArray
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val radius = radius
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val radius = radius
}
class DeepLab3ColorPop(
context: Context, maxWidth: Int, maxHeight: Int, weight: Float
context: Context, maxWidth: Int, maxHeight: Int, weight: Float
) {
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context,
imageUri,
maxWidth,
maxHeight,
BitmapResizeMethod.FIT,
isAllowSwapSide = true,
shouldUpscale = false,
shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val am = context.assets
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
isAllowSwapSide = true, shouldUpscale = false,
shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val am = context.assets
return inferNative(am, rgb8Image, width, height, weight).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
}
}
return inferNative(am, rgb8Image, width, height, weight).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
}
}
private external fun inferNative(
am: AssetManager,
image: ByteArray,
width: Int,
height: Int,
weight: Float
): ByteArray
private external fun inferNative(
am: AssetManager, image: ByteArray, width: Int, height: Int,
weight: Float
): ByteArray
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val weight = weight
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val weight = weight
}

View file

@ -9,35 +9,30 @@ import com.nkming.nc_photos.np_android_core.BitmapUtil
import com.nkming.nc_photos.np_android_core.use
internal class Esrgan(context: Context, maxWidth: Int, maxHeight: Int) {
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context,
imageUri,
maxWidth / 4,
maxHeight / 4,
BitmapResizeMethod.FIT,
isAllowSwapSide = true,
shouldUpscale = false,
shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val am = context.assets
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context, imageUri, maxWidth / 4, maxHeight / 4,
BitmapResizeMethod.FIT, isAllowSwapSide = true,
shouldUpscale = false, shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val am = context.assets
return inferNative(am, rgb8Image, width, height).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width * 4, height * 4)
}
}
return inferNative(am, rgb8Image, width, height).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width * 4, height * 4)
}
}
private external fun inferNative(
am: AssetManager, image: ByteArray, width: Int, height: Int
): ByteArray
private external fun inferNative(
am: AssetManager, image: ByteArray, width: Int, height: Int
): ByteArray
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
}

View file

@ -10,34 +10,29 @@ import com.nkming.nc_photos.np_android_core.use
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class ImageFilterProcessor(
context: Context, maxWidth: Int, maxHeight: Int, filters: List<ImageFilter>
context: Context, maxWidth: Int, maxHeight: Int, filters: List<ImageFilter>
) {
companion object {
const val TAG = "ImageFilterProcessor"
}
companion object {
const val TAG = "ImageFilterProcessor"
}
fun apply(imageUri: Uri): Bitmap {
var img = BitmapUtil.loadImage(
context,
imageUri,
maxWidth,
maxHeight,
BitmapResizeMethod.FIT,
isAllowSwapSide = true,
shouldUpscale = false,
shouldFixOrientation = true
).use {
Rgba8Image(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height)
}
fun apply(imageUri: Uri): Bitmap {
var img = BitmapUtil.loadImage(
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
isAllowSwapSide = true, shouldUpscale = false,
shouldFixOrientation = true
).use {
Rgba8Image(TfLiteHelper.bitmapToRgba8Array(it), it.width, it.height)
}
for (f in filters) {
img = f.apply(img)
}
return img.toBitmap()
}
for (f in filters) {
img = f.apply(img)
}
return img.toBitmap()
}
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val filters = filters
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val filters = filters
}

View file

@ -8,75 +8,75 @@ import com.nkming.nc_photos.np_android_core.logI
* that the viewer will rotate the image when displaying the image
*/
internal class LosslessRotator {
companion object {
const val TAG = "LosslessRotator"
}
companion object {
const val TAG = "LosslessRotator"
}
/**
* Set the Orientation tag in @a dstExif according to the value in
* @a srcExif
*
* @param degree Either 0, 90, 180, -90 or -180
* @param srcExif ExifInterface of the src file
* @param dstExif ExifInterface of the dst file
*/
operator fun invoke(
degree: Int, srcExif: ExifInterface, dstExif: ExifInterface
) {
assert(degree in listOf(0, 90, 180, -90, -180))
val srcOrientation =
srcExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
val dstOrientation = rotateExifOrientationValue(srcOrientation, degree)
logI(TAG, "[invoke] $degree, $srcOrientation -> $dstOrientation")
dstExif.setAttribute(
ExifInterface.TAG_ORIENTATION, dstOrientation.toString()
)
}
/**
* Set the Orientation tag in @a dstExif according to the value in
* @a srcExif
*
* @param degree Either 0, 90, 180, -90 or -180
* @param srcExif ExifInterface of the src file
* @param dstExif ExifInterface of the dst file
*/
operator fun invoke(
degree: Int, srcExif: ExifInterface, dstExif: ExifInterface
) {
assert(degree in listOf(0, 90, 180, -90, -180))
val srcOrientation =
srcExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
val dstOrientation = rotateExifOrientationValue(srcOrientation, degree)
logI(TAG, "[invoke] $degree, $srcOrientation -> $dstOrientation")
dstExif.setAttribute(
ExifInterface.TAG_ORIENTATION, dstOrientation.toString()
)
}
/**
* Return a new orientation representing the resulting value after rotating
* @a value
*
* @param value
* @param degree Either 0, 90, 180, -90 or -180
* @return
*/
private fun rotateExifOrientationValue(value: Int, degree: Int): Int {
if (degree == 0) {
return value
}
var newValue = rotateExifOrientationValue90Ccw(value)
if (degree == 90) {
return newValue
}
newValue = rotateExifOrientationValue90Ccw(newValue)
if (degree == 180 || degree == -180) {
return newValue
}
newValue = rotateExifOrientationValue90Ccw(newValue)
return newValue
}
/**
* Return a new orientation representing the resulting value after rotating
* @a value
*
* @param value
* @param degree Either 0, 90, 180, -90 or -180
* @return
*/
private fun rotateExifOrientationValue(value: Int, degree: Int): Int {
if (degree == 0) {
return value
}
var newValue = rotateExifOrientationValue90Ccw(value)
if (degree == 90) {
return newValue
}
newValue = rotateExifOrientationValue90Ccw(newValue)
if (degree == 180 || degree == -180) {
return newValue
}
newValue = rotateExifOrientationValue90Ccw(newValue)
return newValue
}
/**
* Return a new orientation representing the resulting value after rotating
* @a value for 90 degree CCW
*
* @param value
* @return
*/
private fun rotateExifOrientationValue90Ccw(value: Int): Int {
return when (value) {
0, 1 -> 8
8 -> 3
3 -> 6
6 -> 1
2 -> 7
7 -> 4
4 -> 5
5 -> 2
else -> throw IllegalArgumentException(
"Invalid EXIF Orientation value: $value"
)
}
}
/**
* Return a new orientation representing the resulting value after rotating
* @a value for 90 degree CCW
*
* @param value
* @return
*/
private fun rotateExifOrientationValue90Ccw(value: Int): Int {
return when (value) {
0, 1 -> 8
8 -> 3
3 -> 6
6 -> 1
2 -> 7
7 -> 4
4 -> 5
5 -> 2
else -> throw IllegalArgumentException(
"Invalid EXIF Orientation value: $value"
)
}
}
}

View file

@ -9,35 +9,30 @@ import com.nkming.nc_photos.np_android_core.BitmapUtil
import com.nkming.nc_photos.np_android_core.use
internal class NeurOp(context: Context, maxWidth: Int, maxHeight: Int) {
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context,
imageUri,
maxWidth,
maxHeight,
BitmapResizeMethod.FIT,
isAllowSwapSide = true,
shouldUpscale = false,
shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val am = context.assets
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
isAllowSwapSide = true, shouldUpscale = false,
shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val am = context.assets
return inferNative(am, rgb8Image, width, height).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
}
}
return inferNative(am, rgb8Image, width, height).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
}
}
private external fun inferNative(
am: AssetManager, image: ByteArray, width: Int, height: Int
): ByteArray
private external fun inferNative(
am: AssetManager, image: ByteArray, width: Int, height: Int
): ByteArray
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
}

View file

@ -5,16 +5,15 @@ import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
import kotlin.math.abs
internal class Orientation(val degree: Int) : ImageFilter {
override fun apply(rgba8: Rgba8Image): Rgba8Image {
val data = applyNative(rgba8.pixel, rgba8.width, rgba8.height, degree)
return Rgba8Image(
data,
if (abs(degree) == 90) rgba8.height else rgba8.width,
if (abs(degree) == 90) rgba8.width else rgba8.height
)
}
override fun apply(rgba8: Rgba8Image): Rgba8Image {
val data = applyNative(rgba8.pixel, rgba8.width, rgba8.height, degree)
return Rgba8Image(
data, if (abs(degree) == 90) rgba8.height else rgba8.width,
if (abs(degree) == 90) rgba8.width else rgba8.height
)
}
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, degree: Int
): ByteArray
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, degree: Int
): ByteArray
}

View file

@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Saturation(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width,
rgba8.height
)
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.height
)
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
}

View file

@ -5,80 +5,80 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
import java.nio.IntBuffer
internal interface TfLiteHelper {
companion object {
/**
* Convert an ARGB_8888 Android bitmap to a RGB8 byte array
*
* @param bitmap
* @return
*/
fun bitmapToRgb8Array(bitmap: Bitmap): ByteArray {
val buffer = IntBuffer.allocate(bitmap.width * bitmap.height)
bitmap.copyPixelsToBuffer(buffer)
val rgb8 = ByteArray(bitmap.width * bitmap.height * 3)
buffer.array().forEachIndexed { i, it ->
run {
rgb8[i * 3] = (it and 0xFF).toByte()
rgb8[i * 3 + 1] = (it shr 8 and 0xFF).toByte()
rgb8[i * 3 + 2] = (it shr 16 and 0xFF).toByte()
}
}
return rgb8
}
companion object {
/**
* Convert an ARGB_8888 Android bitmap to a RGB8 byte array
*
* @param bitmap
* @return
*/
fun bitmapToRgb8Array(bitmap: Bitmap): ByteArray {
val buffer = IntBuffer.allocate(bitmap.width * bitmap.height)
bitmap.copyPixelsToBuffer(buffer)
val rgb8 = ByteArray(bitmap.width * bitmap.height * 3)
buffer.array().forEachIndexed { i, it ->
run {
rgb8[i * 3] = (it and 0xFF).toByte()
rgb8[i * 3 + 1] = (it shr 8 and 0xFF).toByte()
rgb8[i * 3 + 2] = (it shr 16 and 0xFF).toByte()
}
}
return rgb8
}
/**
* Convert an ARGB_8888 Android bitmap to a RGBA byte array
*
* @param bitmap
* @return
*/
fun bitmapToRgba8Array(bitmap: Bitmap): ByteArray {
return Rgba8Image.fromBitmap(bitmap).pixel
}
/**
* Convert an ARGB_8888 Android bitmap to a RGBA byte array
*
* @param bitmap
* @return
*/
fun bitmapToRgba8Array(bitmap: Bitmap): ByteArray {
return Rgba8Image.fromBitmap(bitmap).pixel
}
/**
* Convert a RGB8 byte array to an ARGB_8888 Android bitmap
*
* @param rgb8
* @param width
* @param height
* @return
*/
fun rgb8ArrayToBitmap(
rgb8: ByteArray, width: Int, height: Int
): Bitmap {
val buffer = IntBuffer.allocate(width * height)
var i = 0
var pixel = 0
rgb8.forEach {
val value = it.toInt() and 0xFF
when (i++) {
0 -> {
// A
pixel = 0xFF shl 24
// R
pixel = pixel or value
}
/**
* Convert a RGB8 byte array to an ARGB_8888 Android bitmap
*
* @param rgb8
* @param width
* @param height
* @return
*/
fun rgb8ArrayToBitmap(
rgb8: ByteArray, width: Int, height: Int
): Bitmap {
val buffer = IntBuffer.allocate(width * height)
var i = 0
var pixel = 0
rgb8.forEach {
val value = it.toInt() and 0xFF
when (i++) {
0 -> {
// A
pixel = 0xFF shl 24
// R
pixel = pixel or value
}
1 -> {
// G
pixel = pixel or (value shl 8)
}
1 -> {
// G
pixel = pixel or (value shl 8)
}
2 -> {
// B
pixel = pixel or (value shl 16)
2 -> {
// B
pixel = pixel or (value shl 16)
buffer.put(pixel)
i = 0
}
}
}
buffer.rewind()
val outputBitmap =
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
outputBitmap.copyPixelsFromBuffer(buffer)
return outputBitmap
}
}
buffer.put(pixel)
i = 0
}
}
}
buffer.rewind()
val outputBitmap =
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
outputBitmap.copyPixelsFromBuffer(buffer)
return outputBitmap
}
}
}

View file

@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Tint(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width,
rgba8.height
)
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.height
)
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
}

View file

@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Warmth(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width,
rgba8.height
)
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.height
)
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
}

View file

@ -4,13 +4,12 @@ import com.nkming.nc_photos.np_android_core.Rgba8Image
import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class WhitePoint(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width,
rgba8.height
)
override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.height
)
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray
}

View file

@ -9,45 +9,34 @@ import com.nkming.nc_photos.np_android_core.BitmapUtil
import com.nkming.nc_photos.np_android_core.use
internal class ZeroDce(
context: Context,
maxWidth: Int,
maxHeight: Int,
iteration: Int
context: Context, maxWidth: Int, maxHeight: Int, iteration: Int
) {
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context,
imageUri,
maxWidth,
maxHeight,
BitmapResizeMethod.FIT,
isAllowSwapSide = true,
shouldUpscale = false,
shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val am = context.assets
fun infer(imageUri: Uri): Bitmap {
val width: Int
val height: Int
val rgb8Image = BitmapUtil.loadImage(
context, imageUri, maxWidth, maxHeight, BitmapResizeMethod.FIT,
isAllowSwapSide = true, shouldUpscale = false,
shouldFixOrientation = true
).use {
width = it.width
height = it.height
TfLiteHelper.bitmapToRgb8Array(it)
}
val am = context.assets
return inferNative(am, rgb8Image, width, height, iteration).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
}
}
return inferNative(am, rgb8Image, width, height, iteration).let {
TfLiteHelper.rgb8ArrayToBitmap(it, width, height)
}
}
private external fun inferNative(
am: AssetManager,
image: ByteArray,
width: Int,
height: Int,
iteration: Int
): ByteArray
private external fun inferNative(
am: AssetManager, image: ByteArray, width: Int, height: Int,
iteration: Int
): ByteArray
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val iteration = iteration
private val context = context
private val maxWidth = maxWidth
private val maxHeight = maxHeight
private val iteration = iteration
}

View file

@ -18,77 +18,76 @@ import io.flutter.plugin.common.MethodChannel
* fun unlock(lockId: Int): Unit
*/
class LockChannelHandler : MethodChannel.MethodCallHandler {
companion object {
const val CHANNEL = "${K.LIB_ID}/lock"
companion object {
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
*
* All dangling locks locked via this instance will automatically be
* unlocked
*/
fun dismiss() {
for (id in _lockedIds) {
if (locks[id] == true) {
logW(TAG, "[dismiss] Automatically unlocking id: $id")
locks[id] = false
}
}
_lockedIds.clear()
}
/**
* Dismiss this handler instance
*
* All dangling locks locked via this instance will automatically be
* unlocked
*/
fun dismiss() {
for (id in _lockedIds) {
if (locks[id] == true) {
logW(TAG, "[dismiss] Automatically unlocking id: $id")
locks[id] = false
}
}
_lockedIds.clear()
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"tryLock" -> {
try {
tryLock(call.argument("lockId")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"tryLock" -> {
try {
tryLock(call.argument("lockId")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"unlock" -> {
try {
unlock(call.argument("lockId")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"unlock" -> {
try {
unlock(call.argument("lockId")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
else -> {
result.notImplemented()
}
}
}
else -> {
result.notImplemented()
}
}
}
private fun tryLock(lockId: Int, result: MethodChannel.Result) {
if (locks[lockId] != true) {
locks[lockId] = true
_lockedIds.add(lockId)
result.success(true)
} else {
result.success(false)
}
}
private fun tryLock(lockId: Int, result: MethodChannel.Result) {
if (locks[lockId] != true) {
locks[lockId] = true
_lockedIds.add(lockId)
result.success(true)
} else {
result.success(false)
}
}
private fun unlock(lockId: Int, result: MethodChannel.Result) {
if (locks[lockId] == true) {
locks[lockId] = false
_lockedIds.remove(lockId)
result.success(null)
} else {
result.error(
"notLockedException",
"Cannot unlock without first locking",
null
)
}
}
private fun unlock(lockId: Int, result: MethodChannel.Result) {
if (locks[lockId] == true) {
locks[lockId] = false
_lockedIds.remove(lockId)
result.success(null)
} else {
result.error(
"notLockedException", "Cannot unlock without first locking",
null
)
}
}
private val _lockedIds = mutableListOf<Int>()
private val _lockedIds = mutableListOf<Int>()
}

View file

@ -1,7 +1,7 @@
package com.nkming.nc_photos.np_platform_log
internal interface K {
companion object {
const val LIB_ID = "com.nkming.nc_photos.np_platform_log"
}
companion object {
const val LIB_ID = "com.nkming.nc_photos.np_platform_log"
}
}

View file

@ -1,7 +1,7 @@
package com.nkming.nc_photos.np_platform_permission
internal interface K {
companion object {
const val LIB_ID = "com.nkming.nc_photos.np_platform_permission"
}
companion object {
const val LIB_ID = "com.nkming.nc_photos.np_platform_permission"
}
}

View file

@ -11,83 +11,83 @@ import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
class NpPlatformPermissionPlugin : FlutterPlugin, ActivityAware,
PluginRegistry.RequestPermissionsResultListener {
companion object {
private const val TAG = "NpPlatformPermissionPlugin"
}
PluginRegistry.RequestPermissionsResultListener {
companion object {
private const val TAG = "NpPlatformPermissionPlugin"
}
override fun onAttachedToEngine(
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
) {
permissionChannelHandler =
PermissionChannelHandler(flutterPluginBinding.applicationContext)
permissionChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
PermissionChannelHandler.EVENT_CHANNEL
)
permissionChannel.setStreamHandler(permissionChannelHandler)
permissionMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
PermissionChannelHandler.METHOD_CHANNEL
)
permissionMethodChannel.setMethodCallHandler(permissionChannelHandler)
}
override fun onAttachedToEngine(
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
) {
permissionChannelHandler =
PermissionChannelHandler(flutterPluginBinding.applicationContext)
permissionChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
PermissionChannelHandler.EVENT_CHANNEL
)
permissionChannel.setStreamHandler(permissionChannelHandler)
permissionMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
PermissionChannelHandler.METHOD_CHANNEL
)
permissionMethodChannel.setMethodCallHandler(permissionChannelHandler)
}
override fun onDetachedFromEngine(
@NonNull binding: FlutterPlugin.FlutterPluginBinding
) {
permissionChannel.setStreamHandler(null)
permissionMethodChannel.setMethodCallHandler(null)
}
override fun onDetachedFromEngine(
@NonNull binding: FlutterPlugin.FlutterPluginBinding
) {
permissionChannel.setStreamHandler(null)
permissionMethodChannel.setMethodCallHandler(null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
permissionChannelHandler.onAttachedToActivity(binding)
pluginBinding = binding
binding.addRequestPermissionsResultListener(this)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
permissionChannelHandler.onAttachedToActivity(binding)
pluginBinding = binding
binding.addRequestPermissionsResultListener(this)
}
override fun onReattachedToActivityForConfigChanges(
binding: ActivityPluginBinding
) {
permissionChannelHandler.onReattachedToActivityForConfigChanges(binding)
pluginBinding = binding
binding.addRequestPermissionsResultListener(this)
}
override fun onReattachedToActivityForConfigChanges(
binding: ActivityPluginBinding
) {
permissionChannelHandler.onReattachedToActivityForConfigChanges(binding)
pluginBinding = binding
binding.addRequestPermissionsResultListener(this)
}
override fun onDetachedFromActivity() {
permissionChannelHandler.onDetachedFromActivity()
pluginBinding?.removeRequestPermissionsResultListener(this)
}
override fun onDetachedFromActivity() {
permissionChannelHandler.onDetachedFromActivity()
pluginBinding?.removeRequestPermissionsResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {
permissionChannelHandler.onDetachedFromActivityForConfigChanges()
pluginBinding?.removeRequestPermissionsResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {
permissionChannelHandler.onDetachedFromActivityForConfigChanges()
pluginBinding?.removeRequestPermissionsResultListener(this)
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
): Boolean {
return try {
when (requestCode) {
PermissionUtil.REQUEST_CODE -> {
permissionChannelHandler.onRequestPermissionsResult(
requestCode, permissions, grantResults
)
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
): Boolean {
return try {
when (requestCode) {
PermissionUtil.REQUEST_CODE -> {
permissionChannelHandler.onRequestPermissionsResult(
requestCode, permissions, grantResults
)
}
else -> false
}
} catch (e: Throwable) {
logE(
TAG, "Failed while onActivityResult, requestCode=$requestCode"
)
false
}
}
else -> false
}
} catch (e: Throwable) {
logE(
TAG, "Failed while onActivityResult, requestCode=$requestCode"
)
false
}
}
private var pluginBinding: ActivityPluginBinding? = null
private var pluginBinding: ActivityPluginBinding? = null
private lateinit var permissionChannel: EventChannel
private lateinit var permissionMethodChannel: MethodChannel
private lateinit var permissionChannelHandler: PermissionChannelHandler
private lateinit var permissionChannel: EventChannel
private lateinit var permissionMethodChannel: MethodChannel
private lateinit var permissionChannelHandler: PermissionChannelHandler
}

View file

@ -11,104 +11,104 @@ import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
internal class PermissionChannelHandler(context: Context) :
MethodChannel.MethodCallHandler, EventChannel.StreamHandler, ActivityAware,
PluginRegistry.RequestPermissionsResultListener {
companion object {
const val EVENT_CHANNEL = "${K.LIB_ID}/permission"
const val METHOD_CHANNEL = "${K.LIB_ID}/permission_method"
MethodChannel.MethodCallHandler, EventChannel.StreamHandler, ActivityAware,
PluginRegistry.RequestPermissionsResultListener {
companion object {
const val EVENT_CHANNEL = "${K.LIB_ID}/permission"
const val METHOD_CHANNEL = "${K.LIB_ID}/permission_method"
private const val TAG = "PermissionChannelHandler"
}
private const val TAG = "PermissionChannelHandler"
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onReattachedToActivityForConfigChanges(
binding: ActivityPluginBinding
) {
activity = binding.activity
}
override fun onReattachedToActivityForConfigChanges(
binding: ActivityPluginBinding
) {
activity = binding.activity
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
): Boolean {
return if (requestCode == PermissionUtil.REQUEST_CODE) {
eventSink?.success(buildMap {
put("event", "RequestPermissionsResult")
put(
"grantResults",
permissions.zip(grantResults.toTypedArray()).toMap()
)
})
true
} else {
false
}
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray
): Boolean {
return if (requestCode == PermissionUtil.REQUEST_CODE) {
eventSink?.success(buildMap {
put("event", "RequestPermissionsResult")
put(
"grantResults",
permissions.zip(grantResults.toTypedArray()).toMap()
)
})
true
} else {
false
}
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"request" -> {
try {
request(call.argument("permissions")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"request" -> {
try {
request(call.argument("permissions")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"hasWriteExternalStorage" -> {
try {
result.success(
PermissionUtil.hasWriteExternalStorage(context)
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"hasWriteExternalStorage" -> {
try {
result.success(
PermissionUtil.hasWriteExternalStorage(context)
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"hasReadExternalStorage" -> {
try {
result.success(
PermissionUtil.hasReadExternalStorage(context)
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"hasReadExternalStorage" -> {
try {
result.success(
PermissionUtil.hasReadExternalStorage(context)
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
else -> result.notImplemented()
}
}
else -> result.notImplemented()
}
}
private fun request(
permissions: List<String>, result: MethodChannel.Result
) {
if (activity == null) {
result.error("systemException", "Activity is not ready", null)
return
}
PermissionUtil.request(activity!!, *permissions.toTypedArray())
result.success(null)
}
private fun request(
permissions: List<String>, result: MethodChannel.Result
) {
if (activity == null) {
result.error("systemException", "Activity is not ready", null)
return
}
PermissionUtil.request(activity!!, *permissions.toTypedArray())
result.success(null)
}
private val context = context
private var activity: Activity? = null
private var eventSink: EventChannel.EventSink? = null
private val context = context
private var activity: Activity? = null
private var eventSink: EventChannel.EventSink? = null
}

View file

@ -10,70 +10,58 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
internal class ImageLoaderChannelHandler(context: Context) :
MethodChannel.MethodCallHandler {
companion object {
const val METHOD_CHANNEL = "${K.LIB_ID}/image_loader_method"
}
MethodChannel.MethodCallHandler {
companion object {
const val METHOD_CHANNEL = "${K.LIB_ID}/image_loader_method"
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"loadUri" -> {
try {
loadUri(
call.argument("fileUri")!!,
call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument("resizeMethod")!!,
call.argument("isAllowSwapSide")!!,
call.argument("shouldUpscale")!!,
call.argument("shouldFixOrientation")!!,
result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"loadUri" -> {
try {
loadUri(
call.argument("fileUri")!!, call.argument("maxWidth")!!,
call.argument("maxHeight")!!,
call.argument("resizeMethod")!!,
call.argument("isAllowSwapSide")!!,
call.argument("shouldUpscale")!!,
call.argument("shouldFixOrientation")!!, result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
else -> result.notImplemented()
}
}
else -> result.notImplemented()
}
}
/**
* Load and resize an image pointed by a uri
*
* @param fileUri
* @param maxWidth
* @param maxHeight
* @param resizeMethod
* @param isAllowSwapSide
* @param shouldUpscale
* @param shouldFixOrientation
* @param result
*/
private fun loadUri(
fileUri: String,
maxWidth: Int,
maxHeight: Int,
resizeMethod: Int,
isAllowSwapSide: Boolean,
shouldUpscale: Boolean,
shouldFixOrientation: Boolean,
result: MethodChannel.Result
) {
val image = BitmapUtil.loadImage(
context,
Uri.parse(fileUri),
maxWidth,
maxHeight,
BitmapResizeMethod.values()[resizeMethod],
isAllowSwapSide,
shouldUpscale,
shouldFixOrientation
).use {
Rgba8Image.fromBitmap(it)
}
result.success(image.toJson())
}
/**
* Load and resize an image pointed by a uri
*
* @param fileUri
* @param maxWidth
* @param maxHeight
* @param resizeMethod
* @param isAllowSwapSide
* @param shouldUpscale
* @param shouldFixOrientation
* @param result
*/
private fun loadUri(
fileUri: String, maxWidth: Int, maxHeight: Int, resizeMethod: Int,
isAllowSwapSide: Boolean, shouldUpscale: Boolean,
shouldFixOrientation: Boolean, result: MethodChannel.Result
) {
val image = BitmapUtil.loadImage(
context, Uri.parse(fileUri), maxWidth, maxHeight,
BitmapResizeMethod.values()[resizeMethod], isAllowSwapSide,
shouldUpscale, shouldFixOrientation
).use {
Rgba8Image.fromBitmap(it)
}
result.success(image.toJson())
}
private val context = context
private val context = context
}

View file

@ -1,7 +1,7 @@
package com.nkming.nc_photos.np_platform_raw_image
internal interface K {
companion object {
const val LIB_ID = "com.nkming.nc_photos.np_platform_raw_image"
}
companion object {
const val LIB_ID = "com.nkming.nc_photos.np_platform_raw_image"
}
}

View file

@ -5,19 +5,23 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel
class NpPlatformRawImagePlugin : FlutterPlugin {
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val imageLoaderChannelHandler =
ImageLoaderChannelHandler(flutterPluginBinding.applicationContext)
imageLoaderMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
ImageLoaderChannelHandler.METHOD_CHANNEL
)
imageLoaderMethodChannel.setMethodCallHandler(imageLoaderChannelHandler)
}
override fun onAttachedToEngine(
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
) {
val imageLoaderChannelHandler =
ImageLoaderChannelHandler(flutterPluginBinding.applicationContext)
imageLoaderMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
ImageLoaderChannelHandler.METHOD_CHANNEL
)
imageLoaderMethodChannel.setMethodCallHandler(imageLoaderChannelHandler)
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
imageLoaderMethodChannel.setMethodCallHandler(null)
}
override fun onDetachedFromEngine(
@NonNull binding: FlutterPlugin.FlutterPluginBinding
) {
imageLoaderMethodChannel.setMethodCallHandler(null)
}
private lateinit var imageLoaderMethodChannel: MethodChannel
private lateinit var imageLoaderMethodChannel: MethodChannel
}

View file

@ -11,65 +11,65 @@ import java.io.File
import java.io.FileNotFoundException
internal class ContentUriChannelHandler(context: Context) :
MethodChannel.MethodCallHandler {
companion object {
const val METHOD_CHANNEL = "${K.LIB_ID}/content_uri_method"
MethodChannel.MethodCallHandler {
companion object {
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) {
when (call.method) {
"readUri" -> {
try {
readUri(call.argument("uri")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"readUri" -> {
try {
readUri(call.argument("uri")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"getUriForFile" -> {
try {
getUriForFile(call.argument("filePath")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"getUriForFile" -> {
try {
getUriForFile(call.argument("filePath")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
else -> result.notImplemented()
}
}
else -> result.notImplemented()
}
}
private fun readUri(uri: String, result: MethodChannel.Result) {
val uriTyped = Uri.parse(uri)
try {
val bytes = if (UriUtil.isAssetUri(uriTyped)) {
context.assets.open(UriUtil.getAssetUriPath(uriTyped)).use {
it.readBytes()
}
} else {
context.contentResolver.openInputStream(uriTyped)!!.use {
it.readBytes()
}
}
result.success(bytes)
} catch (e: FileNotFoundException) {
result.error("fileNotFoundException", e.toString(), null)
}
}
private fun readUri(uri: String, result: MethodChannel.Result) {
val uriTyped = Uri.parse(uri)
try {
val bytes = if (UriUtil.isAssetUri(uriTyped)) {
context.assets.open(UriUtil.getAssetUriPath(uriTyped)).use {
it.readBytes()
}
} else {
context.contentResolver.openInputStream(uriTyped)!!.use {
it.readBytes()
}
}
result.success(bytes)
} catch (e: FileNotFoundException) {
result.error("fileNotFoundException", e.toString(), null)
}
}
private fun getUriForFile(filePath: String, result: MethodChannel.Result) {
try {
val file = File(filePath)
val contentUri = FileProvider.getUriForFile(
context, "${context.packageName}.fileprovider", file
)
result.success(contentUri.toString())
} catch (e: IllegalArgumentException) {
logE(TAG, "[getUriForFile] Unsupported file path: $filePath")
throw e
}
}
private fun getUriForFile(filePath: String, result: MethodChannel.Result) {
try {
val file = File(filePath)
val contentUri = FileProvider.getUriForFile(
context, "${context.packageName}.fileprovider", file
)
result.success(contentUri.toString())
} catch (e: IllegalArgumentException) {
logE(TAG, "[getUriForFile] Unsupported file path: $filePath")
throw e
}
}
private val context = context
private val context = context
}

View file

@ -1,16 +1,16 @@
package com.nkming.nc_photos.plugin
internal interface K {
companion object {
const val DOWNLOAD_NOTIFICATION_ID_MIN = 1000
const val DOWNLOAD_NOTIFICATION_ID_MAX = 2000
companion object {
const val DOWNLOAD_NOTIFICATION_ID_MIN = 1000
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"
}
}

View file

@ -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.PermissionException
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.logI
import io.flutter.embedding.engine.plugins.activity.ActivityAware
@ -33,252 +32,250 @@ import java.io.File
* fun queryFiles(relativePath: String): List<Map>
*/
internal class MediaStoreChannelHandler(context: Context) :
MethodChannel.MethodCallHandler, EventChannel.StreamHandler,
ActivityAware, PluginRegistry.ActivityResultListener {
companion object {
const val EVENT_CHANNEL = "${K.LIB_ID}/media_store"
const val METHOD_CHANNEL = "${K.LIB_ID}/media_store_method"
MethodChannel.MethodCallHandler, EventChannel.StreamHandler, ActivityAware,
PluginRegistry.ActivityResultListener {
companion object {
const val EVENT_CHANNEL = "${K.LIB_ID}/media_store"
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) {
activity = binding.activity
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onReattachedToActivityForConfigChanges(
binding: ActivityPluginBinding
) {
activity = binding.activity
}
override fun onReattachedToActivityForConfigChanges(
binding: ActivityPluginBinding
) {
activity = binding.activity
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
override fun onActivityResult(
requestCode: Int, resultCode: Int, data: Intent?
): Boolean {
if (requestCode == K.MEDIA_STORE_DELETE_REQUEST_CODE) {
eventSink?.success(buildMap {
put("event", "DeleteRequestResult")
put("resultCode", resultCode)
})
return true
}
return false
}
override fun onActivityResult(
requestCode: Int, resultCode: Int, data: Intent?
): Boolean {
if (requestCode == K.MEDIA_STORE_DELETE_REQUEST_CODE) {
eventSink?.success(buildMap {
put("event", "DeleteRequestResult")
put("resultCode", resultCode)
})
return true
}
return false
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"saveFileToDownload" -> {
try {
saveFileToDownload(
call.argument("content")!!, call.argument("filename")!!,
call.argument("subDir"), result
)
} catch (e: Throwable) {
result.error("systemException", e.message, null)
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"saveFileToDownload" -> {
try {
saveFileToDownload(
call.argument("content")!!, call.argument("filename")!!,
call.argument("subDir"), result
)
} catch (e: Throwable) {
result.error("systemException", e.message, null)
}
}
"copyFileToDownload" -> {
try {
copyFileToDownload(
call.argument("fromFile")!!, call.argument("filename"),
call.argument("subDir"), result
)
} catch (e: Throwable) {
result.error("systemException", e.message, null)
}
}
"copyFileToDownload" -> {
try {
copyFileToDownload(
call.argument("fromFile")!!, call.argument("filename"),
call.argument("subDir"), result
)
} catch (e: Throwable) {
result.error("systemException", e.message, null)
}
}
"queryFiles" -> {
try {
queryFiles(call.argument("relativePath")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.message, null)
}
}
"queryFiles" -> {
try {
queryFiles(call.argument("relativePath")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.message, null)
}
}
"deleteFiles" -> {
try {
deleteFiles(call.argument("uris")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.message, null)
}
}
"deleteFiles" -> {
try {
deleteFiles(call.argument("uris")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.message, null)
}
}
else -> result.notImplemented()
}
}
else -> result.notImplemented()
}
}
private fun saveFileToDownload(
content: ByteArray, filename: String, subDir: String?,
result: MethodChannel.Result
) {
try {
val uri = MediaStoreUtil.saveFileToDownload(
context, content, filename, subDir
)
result.success(uri.toString())
} catch (e: PermissionException) {
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
result.error("permissionError", "Permission not granted", null)
}
}
private fun saveFileToDownload(
content: ByteArray, filename: String, subDir: String?,
result: MethodChannel.Result
) {
try {
val uri = MediaStoreUtil.saveFileToDownload(
context, content, filename, subDir
)
result.success(uri.toString())
} catch (e: PermissionException) {
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
result.error("permissionError", "Permission not granted", null)
}
}
private fun copyFileToDownload(
fromFile: String, filename: String?, subDir: String?,
result: MethodChannel.Result
) {
try {
val fromUri = inputToUri(fromFile)
val uri = MediaStoreUtil.copyFileToDownload(
context, fromUri, filename, subDir
)
result.success(uri.toString())
} catch (e: PermissionException) {
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
result.error("permissionError", "Permission not granted", null)
}
}
private fun copyFileToDownload(
fromFile: String, filename: String?, subDir: String?,
result: MethodChannel.Result
) {
try {
val fromUri = inputToUri(fromFile)
val uri = MediaStoreUtil.copyFileToDownload(
context, fromUri, filename, subDir
)
result.success(uri.toString())
} catch (e: PermissionException) {
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
result.error("permissionError", "Permission not granted", null)
}
}
private fun queryFiles(relativePath: String, result: MethodChannel.Result) {
if (!PermissionUtil.hasReadExternalStorage(context)) {
activity?.let { PermissionUtil.requestReadExternalStorage(it) }
result.error("permissionError", "Permission not granted", null)
return
}
private fun queryFiles(relativePath: String, result: MethodChannel.Result) {
if (!PermissionUtil.hasReadExternalStorage(context)) {
activity?.let { PermissionUtil.requestReadExternalStorage(it) }
result.error("permissionError", "Permission not granted", null)
return
}
val pathColumnName: String
val pathArg: String
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
pathColumnName = MediaStore.Images.Media.RELATIVE_PATH
pathArg = "${relativePath}/%"
} else {
@Suppress("Deprecation")
pathColumnName = MediaStore.Images.Media.DATA
pathArg = "%/${relativePath}/%"
}
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATE_MODIFIED,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DISPLAY_NAME,
pathColumnName
)
val selection = StringBuilder().apply {
append("${MediaStore.Images.Media.MIME_TYPE} LIKE ?")
append("AND $pathColumnName LIKE ?")
}.toString()
val selectionArgs = arrayOf("image/%", pathArg)
val files = context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, selection, selectionArgs, null
)!!.use {
val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val dateModifiedColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED)
val mimeTypeColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)
val dateTakenColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
val displayNameColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val pathColumn = it.getColumnIndexOrThrow(pathColumnName)
val products = mutableListOf<Map<String, Any>>()
while (it.moveToNext()) {
val id = it.getLong(idColumn)
val dateModified = it.getLong(dateModifiedColumn)
val mimeType = it.getString(mimeTypeColumn)
val dateTaken = it.getLong(dateTakenColumn)
val displayName = it.getString(displayNameColumn)
val path = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// RELATIVE_PATH
"${it.getString(pathColumn).trimEnd('/')}/$displayName"
} else {
// DATA
it.getString(pathColumn)
}
val contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id
)
products.add(buildMap {
put("uri", contentUri.toString())
put("displayName", displayName)
put("path", path)
put("dateModified", dateModified * 1000)
put("mimeType", mimeType)
if (dateTaken != 0L) put("dateTaken", dateTaken)
})
logD(
TAG,
"[queryEnhancedPhotos] Found $displayName, path=$path, uri=$contentUri"
)
}
products
}
logI(TAG, "[queryEnhancedPhotos] Found ${files.size} files")
result.success(files)
}
val pathColumnName: String
val pathArg: String
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
pathColumnName = MediaStore.Images.Media.RELATIVE_PATH
pathArg = "${relativePath}/%"
} else {
@Suppress("Deprecation") pathColumnName =
MediaStore.Images.Media.DATA
pathArg = "%/${relativePath}/%"
}
val projection = arrayOf(
MediaStore.Images.Media._ID, MediaStore.Images.Media.DATE_MODIFIED,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DISPLAY_NAME, pathColumnName
)
val selection = StringBuilder().apply {
append("${MediaStore.Images.Media.MIME_TYPE} LIKE ?")
append("AND $pathColumnName LIKE ?")
}.toString()
val selectionArgs = arrayOf("image/%", pathArg)
val files = context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection,
selectionArgs, null
)!!.use {
val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val dateModifiedColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED)
val mimeTypeColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)
val dateTakenColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
val displayNameColumn =
it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val pathColumn = it.getColumnIndexOrThrow(pathColumnName)
val products = mutableListOf<Map<String, Any>>()
while (it.moveToNext()) {
val id = it.getLong(idColumn)
val dateModified = it.getLong(dateModifiedColumn)
val mimeType = it.getString(mimeTypeColumn)
val dateTaken = it.getLong(dateTakenColumn)
val displayName = it.getString(displayNameColumn)
val path = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// RELATIVE_PATH
"${it.getString(pathColumn).trimEnd('/')}/$displayName"
} else {
// DATA
it.getString(pathColumn)
}
val contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id
)
products.add(buildMap {
put("uri", contentUri.toString())
put("displayName", displayName)
put("path", path)
put("dateModified", dateModified * 1000)
put("mimeType", mimeType)
if (dateTaken != 0L) put("dateTaken", dateTaken)
})
logD(
TAG,
"[queryEnhancedPhotos] Found $displayName, path=$path, uri=$contentUri"
)
}
products
}
logI(TAG, "[queryEnhancedPhotos] Found ${files.size} files")
result.success(files)
}
private fun deleteFiles(uris: List<String>, result: MethodChannel.Result) {
val urisTyped = uris.map(Uri::parse)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val pi = MediaStore.createDeleteRequest(
context.contentResolver, urisTyped
)
activity!!.startIntentSenderForResult(
pi.intentSender, K.MEDIA_STORE_DELETE_REQUEST_CODE, null, 0, 0,
0
)
result.success(null)
} else {
if (!PermissionUtil.hasWriteExternalStorage(context)) {
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
result.error("permissionError", "Permission not granted", null)
return
}
private fun deleteFiles(uris: List<String>, result: MethodChannel.Result) {
val urisTyped = uris.map(Uri::parse)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val pi = MediaStore.createDeleteRequest(
context.contentResolver, urisTyped
)
activity!!.startIntentSenderForResult(
pi.intentSender, K.MEDIA_STORE_DELETE_REQUEST_CODE, null, 0, 0,
0
)
result.success(null)
} else {
if (!PermissionUtil.hasWriteExternalStorage(context)) {
activity?.let { PermissionUtil.requestWriteExternalStorage(it) }
result.error("permissionError", "Permission not granted", null)
return
}
val failed = mutableListOf<String>()
for (uri in urisTyped) {
try {
context.contentResolver.delete(uri, null, null)
} catch (e: Throwable) {
logE(TAG, "[deleteFiles] Failed while delete", e)
failed += uri.toString()
}
}
result.success(failed)
}
}
val failed = mutableListOf<String>()
for (uri in urisTyped) {
try {
context.contentResolver.delete(uri, null, null)
} catch (e: Throwable) {
logE(TAG, "[deleteFiles] Failed while delete", e)
failed += uri.toString()
}
}
result.success(failed)
}
}
private fun inputToUri(fromFile: String): Uri {
val testUri = Uri.parse(fromFile)
return if (testUri.scheme == null) {
// is a file path
Uri.fromFile(File(fromFile))
} else {
// is a uri
Uri.parse(fromFile)
}
}
private fun inputToUri(fromFile: String): Uri {
val testUri = Uri.parse(fromFile)
return if (testUri.scheme == null) {
// is a file path
Uri.fromFile(File(fromFile))
} else {
// is a uri
Uri.parse(fromFile)
}
}
private val context = context
private var activity: Activity? = null
private var eventSink: EventChannel.EventSink? = null
private val context = context
private var activity: Activity? = null
private var eventSink: EventChannel.EventSink? = null
}

View file

@ -11,117 +11,117 @@ import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
class NcPhotosPlugin : FlutterPlugin, ActivityAware,
PluginRegistry.ActivityResultListener {
companion object {
const val ACTION_DOWNLOAD_CANCEL = K.ACTION_DOWNLOAD_CANCEL
const val EXTRA_NOTIFICATION_ID = K.EXTRA_NOTIFICATION_ID
PluginRegistry.ActivityResultListener {
companion object {
const val ACTION_DOWNLOAD_CANCEL = K.ACTION_DOWNLOAD_CANCEL
const val EXTRA_NOTIFICATION_ID = K.EXTRA_NOTIFICATION_ID
private const val TAG = "NcPhotosPlugin"
}
private const val TAG = "NcPhotosPlugin"
}
override fun onAttachedToEngine(
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
) {
notificationChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
NotificationChannelHandler.CHANNEL
)
notificationChannel.setMethodCallHandler(
NotificationChannelHandler(
flutterPluginBinding.applicationContext
)
)
override fun onAttachedToEngine(
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
) {
notificationChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
NotificationChannelHandler.CHANNEL
)
notificationChannel.setMethodCallHandler(
NotificationChannelHandler(
flutterPluginBinding.applicationContext
)
)
mediaStoreChannelHandler =
MediaStoreChannelHandler(flutterPluginBinding.applicationContext)
mediaStoreChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
MediaStoreChannelHandler.EVENT_CHANNEL
)
mediaStoreChannel.setStreamHandler(mediaStoreChannelHandler)
mediaStoreMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
MediaStoreChannelHandler.METHOD_CHANNEL
)
mediaStoreMethodChannel.setMethodCallHandler(mediaStoreChannelHandler)
mediaStoreChannelHandler =
MediaStoreChannelHandler(flutterPluginBinding.applicationContext)
mediaStoreChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
MediaStoreChannelHandler.EVENT_CHANNEL
)
mediaStoreChannel.setStreamHandler(mediaStoreChannelHandler)
mediaStoreMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
MediaStoreChannelHandler.METHOD_CHANNEL
)
mediaStoreMethodChannel.setMethodCallHandler(mediaStoreChannelHandler)
contentUriMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
ContentUriChannelHandler.METHOD_CHANNEL
)
contentUriMethodChannel.setMethodCallHandler(
ContentUriChannelHandler(flutterPluginBinding.applicationContext)
)
contentUriMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
ContentUriChannelHandler.METHOD_CHANNEL
)
contentUriMethodChannel.setMethodCallHandler(
ContentUriChannelHandler(flutterPluginBinding.applicationContext)
)
val preferenceChannelHandler =
PreferenceChannelHandler(flutterPluginBinding.applicationContext)
preferenceMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
PreferenceChannelHandler.METHOD_CHANNEL
)
preferenceMethodChannel.setMethodCallHandler(preferenceChannelHandler)
}
val preferenceChannelHandler =
PreferenceChannelHandler(flutterPluginBinding.applicationContext)
preferenceMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
PreferenceChannelHandler.METHOD_CHANNEL
)
preferenceMethodChannel.setMethodCallHandler(preferenceChannelHandler)
}
override fun onDetachedFromEngine(
@NonNull binding: FlutterPlugin.FlutterPluginBinding
) {
notificationChannel.setMethodCallHandler(null)
mediaStoreChannel.setStreamHandler(null)
mediaStoreMethodChannel.setMethodCallHandler(null)
contentUriMethodChannel.setMethodCallHandler(null)
preferenceMethodChannel.setMethodCallHandler(null)
}
override fun onDetachedFromEngine(
@NonNull binding: FlutterPlugin.FlutterPluginBinding
) {
notificationChannel.setMethodCallHandler(null)
mediaStoreChannel.setStreamHandler(null)
mediaStoreMethodChannel.setMethodCallHandler(null)
contentUriMethodChannel.setMethodCallHandler(null)
preferenceMethodChannel.setMethodCallHandler(null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
mediaStoreChannelHandler.onAttachedToActivity(binding)
pluginBinding = binding
binding.addActivityResultListener(this)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
mediaStoreChannelHandler.onAttachedToActivity(binding)
pluginBinding = binding
binding.addActivityResultListener(this)
}
override fun onReattachedToActivityForConfigChanges(
binding: ActivityPluginBinding
) {
mediaStoreChannelHandler.onReattachedToActivityForConfigChanges(binding)
pluginBinding = binding
binding.addActivityResultListener(this)
}
override fun onReattachedToActivityForConfigChanges(
binding: ActivityPluginBinding
) {
mediaStoreChannelHandler.onReattachedToActivityForConfigChanges(binding)
pluginBinding = binding
binding.addActivityResultListener(this)
}
override fun onDetachedFromActivity() {
mediaStoreChannelHandler.onDetachedFromActivity()
pluginBinding?.removeActivityResultListener(this)
}
override fun onDetachedFromActivity() {
mediaStoreChannelHandler.onDetachedFromActivity()
pluginBinding?.removeActivityResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {
mediaStoreChannelHandler.onDetachedFromActivityForConfigChanges()
pluginBinding?.removeActivityResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {
mediaStoreChannelHandler.onDetachedFromActivityForConfigChanges()
pluginBinding?.removeActivityResultListener(this)
}
override fun onActivityResult(
requestCode: Int, resultCode: Int, data: Intent?
): Boolean {
return try {
when (requestCode) {
K.MEDIA_STORE_DELETE_REQUEST_CODE -> {
mediaStoreChannelHandler.onActivityResult(
requestCode, resultCode, data
)
}
override fun onActivityResult(
requestCode: Int, resultCode: Int, data: Intent?
): Boolean {
return try {
when (requestCode) {
K.MEDIA_STORE_DELETE_REQUEST_CODE -> {
mediaStoreChannelHandler.onActivityResult(
requestCode, resultCode, data
)
}
else -> false
}
} catch (e: Throwable) {
logE(TAG, "Failed while onActivityResult, requestCode=$requestCode")
false
}
}
else -> false
}
} catch (e: Throwable) {
logE(TAG, "Failed while onActivityResult, requestCode=$requestCode")
false
}
}
private var pluginBinding: ActivityPluginBinding? = null
private var pluginBinding: ActivityPluginBinding? = null
private lateinit var notificationChannel: MethodChannel
private lateinit var mediaStoreChannel: EventChannel
private lateinit var mediaStoreMethodChannel: MethodChannel
private lateinit var contentUriMethodChannel: MethodChannel
private lateinit var preferenceMethodChannel: MethodChannel
private lateinit var notificationChannel: MethodChannel
private lateinit var mediaStoreChannel: EventChannel
private lateinit var mediaStoreMethodChannel: MethodChannel
private lateinit var contentUriMethodChannel: MethodChannel
private lateinit var preferenceMethodChannel: MethodChannel
private lateinit var mediaStoreChannelHandler: MediaStoreChannelHandler
private lateinit var mediaStoreChannelHandler: MediaStoreChannelHandler
}

View file

@ -26,334 +26,323 @@ import kotlin.math.max
* mimeTypes: List<String>): Unit
*/
internal class NotificationChannelHandler(context: Context) :
MethodChannel.MethodCallHandler {
companion object {
const val CHANNEL = "${K.LIB_ID}/notification"
MethodChannel.MethodCallHandler {
companion object {
const val CHANNEL = "${K.LIB_ID}/notification"
fun getNextNotificationId(): Int {
if (++notificationId >= K.DOWNLOAD_NOTIFICATION_ID_MAX) {
notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN
}
return notificationId
}
fun getNextNotificationId(): Int {
if (++notificationId >= K.DOWNLOAD_NOTIFICATION_ID_MAX) {
notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN
}
return notificationId
}
const val DOWNLOAD_CHANNEL_ID = "download"
private var notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN
}
const val DOWNLOAD_CHANNEL_ID = "download"
private var notificationId = K.DOWNLOAD_NOTIFICATION_ID_MIN
}
init {
createDownloadChannel(context)
}
init {
createDownloadChannel(context)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"notifyDownloadSuccessful" -> {
try {
notifyDownloadSuccessful(
call.argument("fileUris")!!,
call.argument("mimeTypes")!!,
call.argument("notificationId"),
result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"notifyDownloadSuccessful" -> {
try {
notifyDownloadSuccessful(
call.argument("fileUris")!!,
call.argument("mimeTypes")!!,
call.argument("notificationId"), result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"notifyDownloadProgress" -> {
try {
notifyDownloadProgress(
call.argument("progress")!!,
call.argument("max")!!,
call.argument("currentItemTitle"),
call.argument("notificationId"),
result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"notifyDownloadProgress" -> {
try {
notifyDownloadProgress(
call.argument("progress")!!, call.argument("max")!!,
call.argument("currentItemTitle"),
call.argument("notificationId"), result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"notifyLogSaveSuccessful" -> {
try {
notifyLogSaveSuccessful(
call.argument("fileUri")!!, result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"notifyLogSaveSuccessful" -> {
try {
notifyLogSaveSuccessful(
call.argument("fileUri")!!, result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"dismiss" -> {
try {
dismiss(call.argument("notificationId")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"dismiss" -> {
try {
dismiss(call.argument("notificationId")!!, result)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
else -> {
result.notImplemented()
}
}
}
else -> {
result.notImplemented()
}
}
}
private fun notifyDownloadSuccessful(
fileUris: List<String>,
mimeTypes: List<String?>,
notificationId: Int?,
result: MethodChannel.Result
) {
assert(fileUris.isNotEmpty())
assert(fileUris.size == mimeTypes.size)
val uris = fileUris.map { Uri.parse(it) }
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
.setSmallIcon(R.drawable.baseline_download_white_18)
.setWhen(System.currentTimeMillis())
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
RingtoneManager.getDefaultUri(
RingtoneManager.TYPE_NOTIFICATION
)
).setOnlyAlertOnce(false).setAutoCancel(true).setLocalOnly(true)
private fun notifyDownloadSuccessful(
fileUris: List<String>, mimeTypes: List<String?>, notificationId: Int?,
result: MethodChannel.Result
) {
assert(fileUris.isNotEmpty())
assert(fileUris.size == mimeTypes.size)
val uris = fileUris.map { Uri.parse(it) }
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
.setSmallIcon(R.drawable.baseline_download_white_18)
.setWhen(System.currentTimeMillis())
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
RingtoneManager.getDefaultUri(
RingtoneManager.TYPE_NOTIFICATION
)
).setOnlyAlertOnce(false).setAutoCancel(true).setLocalOnly(true)
if (uris.size == 1) {
builder.setContentTitle(
_context.getString(
R.string.download_successful_notification_title
)
).setContentText(
_context.getString(
R.string.download_successful_notification_text
)
)
if (uris.size == 1) {
builder.setContentTitle(
_context.getString(
R.string.download_successful_notification_title
)
).setContentText(
_context.getString(
R.string.download_successful_notification_text
)
)
val openIntent = Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(uris[0], mimeTypes[0])
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
val openPendingIntent = PendingIntent.getActivity(
_context, 0, openIntent,
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
)
builder.setContentIntent(openPendingIntent)
val openIntent = Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(uris[0], mimeTypes[0])
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
val openPendingIntent = PendingIntent.getActivity(
_context, 0, openIntent,
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
)
builder.setContentIntent(openPendingIntent)
// show preview if available
if (mimeTypes[0]?.startsWith("image/") == true) {
val preview = loadNotificationImage(uris[0])
if (preview != null) {
builder.setStyle(
NotificationCompat.BigPictureStyle()
.bigPicture(loadNotificationImage(uris[0]))
)
}
}
} else {
builder.setContentTitle(
_context.getString(
R.string.download_multiple_successful_notification_title,
fileUris.size
)
)
}
// show preview if available
if (mimeTypes[0]?.startsWith("image/") == true) {
val preview = loadNotificationImage(uris[0])
if (preview != null) {
builder.setStyle(
NotificationCompat.BigPictureStyle()
.bigPicture(loadNotificationImage(uris[0]))
)
}
}
} else {
builder.setContentTitle(
_context.getString(
R.string.download_multiple_successful_notification_title,
fileUris.size
)
)
}
val shareIntent = if (uris.size == 1) Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uris[0])
type = mimeTypes[0] ?: "*/*"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
} else Intent().apply {
action = Intent.ACTION_SEND_MULTIPLE
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
type =
if (mimeTypes.all {
it?.startsWith(
"image/"
) == true
}) "image/*" else "*/*"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
val shareChooser = Intent.createChooser(
shareIntent, _context.getString(
R.string.download_successful_notification_action_share_chooser
)
)
val sharePendingIntent = PendingIntent.getActivity(
_context, 1, shareChooser,
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
)
builder.addAction(
0, _context.getString(
R.string.download_successful_notification_action_share
), sharePendingIntent
)
val shareIntent = if (uris.size == 1) Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uris[0])
type = mimeTypes[0] ?: "*/*"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
} else Intent().apply {
action = Intent.ACTION_SEND_MULTIPLE
putParcelableArrayListExtra(Intent.EXTRA_STREAM, ArrayList(uris))
type = if (mimeTypes.all {
it?.startsWith(
"image/"
) == true
}) "image/*" else "*/*"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
val shareChooser = Intent.createChooser(
shareIntent, _context.getString(
R.string.download_successful_notification_action_share_chooser
)
)
val sharePendingIntent = PendingIntent.getActivity(
_context, 1, shareChooser,
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
)
builder.addAction(
0, _context.getString(
R.string.download_successful_notification_action_share
), sharePendingIntent
)
val id = notificationId ?: getNextNotificationId()
with(NotificationManagerCompat.from(_context)) {
notify(id, builder.build())
}
result.success(id)
}
val id = notificationId ?: getNextNotificationId()
with(NotificationManagerCompat.from(_context)) {
notify(id, builder.build())
}
result.success(id)
}
private fun notifyDownloadProgress(
progress: Int,
max: Int,
currentItemTitle: String?,
notificationId: Int?,
result: MethodChannel.Result
) {
val id = notificationId ?: getNextNotificationId()
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setWhen(System.currentTimeMillis())
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
RingtoneManager.getDefaultUri(
RingtoneManager.TYPE_NOTIFICATION
)
).setOnlyAlertOnce(true).setAutoCancel(false).setLocalOnly(true)
.setProgress(max, progress, false).setContentText("$progress/$max")
if (currentItemTitle == null) {
builder.setContentTitle(
_context.getString(
R.string.download_progress_notification_untitled_text
)
)
} else {
builder.setContentTitle(
_context.getString(
R.string.download_progress_notification_text,
currentItemTitle
)
)
}
private fun notifyDownloadProgress(
progress: Int, max: Int, currentItemTitle: String?,
notificationId: Int?, result: MethodChannel.Result
) {
val id = notificationId ?: getNextNotificationId()
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setWhen(System.currentTimeMillis())
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
RingtoneManager.getDefaultUri(
RingtoneManager.TYPE_NOTIFICATION
)
).setOnlyAlertOnce(true).setAutoCancel(false).setLocalOnly(true)
.setProgress(max, progress, false).setContentText("$progress/$max")
if (currentItemTitle == null) {
builder.setContentTitle(
_context.getString(
R.string.download_progress_notification_untitled_text
)
)
} else {
builder.setContentTitle(
_context.getString(
R.string.download_progress_notification_text,
currentItemTitle
)
)
}
val cancelIntent = Intent().apply {
`package` = _context.packageName
action = K.ACTION_DOWNLOAD_CANCEL
putExtra(K.EXTRA_NOTIFICATION_ID, id)
}
val cancelPendingIntent = PendingIntent.getBroadcast(
_context, 0, cancelIntent,
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
)
builder.addAction(
0, _context.getString(android.R.string.cancel), cancelPendingIntent
)
val cancelIntent = Intent().apply {
`package` = _context.packageName
action = K.ACTION_DOWNLOAD_CANCEL
putExtra(K.EXTRA_NOTIFICATION_ID, id)
}
val cancelPendingIntent = PendingIntent.getBroadcast(
_context, 0, cancelIntent,
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
)
builder.addAction(
0, _context.getString(android.R.string.cancel), cancelPendingIntent
)
with(NotificationManagerCompat.from(_context)) {
notify(id, builder.build())
}
result.success(id)
}
with(NotificationManagerCompat.from(_context)) {
notify(id, builder.build())
}
result.success(id)
}
private fun notifyLogSaveSuccessful(
fileUri: String, result: MethodChannel.Result
) {
val uri = Uri.parse(fileUri)
val mimeType = "text/plain"
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
.setSmallIcon(R.drawable.baseline_download_white_18)
.setWhen(System.currentTimeMillis())
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
RingtoneManager.getDefaultUri(
RingtoneManager.TYPE_NOTIFICATION
)
).setAutoCancel(true).setLocalOnly(true).setTicker(
_context.getString(
R.string.log_save_successful_notification_title
)
).setContentTitle(
_context.getString(
R.string.log_save_successful_notification_title
)
).setContentText(
_context.getString(
R.string.log_save_successful_notification_text
)
)
private fun notifyLogSaveSuccessful(
fileUri: String, result: MethodChannel.Result
) {
val uri = Uri.parse(fileUri)
val mimeType = "text/plain"
val builder = NotificationCompat.Builder(_context, DOWNLOAD_CHANNEL_ID)
.setSmallIcon(R.drawable.baseline_download_white_18)
.setWhen(System.currentTimeMillis())
.setPriority(NotificationCompat.PRIORITY_HIGH).setSound(
RingtoneManager.getDefaultUri(
RingtoneManager.TYPE_NOTIFICATION
)
).setAutoCancel(true).setLocalOnly(true).setTicker(
_context.getString(
R.string.log_save_successful_notification_title
)
).setContentTitle(
_context.getString(
R.string.log_save_successful_notification_title
)
).setContentText(
_context.getString(
R.string.log_save_successful_notification_text
)
)
val openIntent = Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(uri, mimeType)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
val openPendingIntent = PendingIntent.getActivity(
_context, 0, openIntent,
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
)
builder.setContentIntent(openPendingIntent)
val openIntent = Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(uri, mimeType)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
val openPendingIntent = PendingIntent.getActivity(
_context, 0, openIntent,
PendingIntent.FLAG_UPDATE_CURRENT or getPendingIntentFlagImmutable()
)
builder.setContentIntent(openPendingIntent)
// can't add the share action here because android will share the URI as
// plain text instead of treating it as a text file...
// can't add the share action here because android will share the URI as
// plain text instead of treating it as a text file...
val id = getNextNotificationId()
with(NotificationManagerCompat.from(_context)) {
notify(id, builder.build())
}
result.success(id)
}
val id = getNextNotificationId()
with(NotificationManagerCompat.from(_context)) {
notify(id, builder.build())
}
result.success(id)
}
private fun dismiss(notificationId: Int, result: MethodChannel.Result) {
with(NotificationManagerCompat.from(_context)) {
cancel(notificationId)
}
result.success(null)
}
private fun dismiss(notificationId: Int, result: MethodChannel.Result) {
with(NotificationManagerCompat.from(_context)) {
cancel(notificationId)
}
result.success(null)
}
private fun loadNotificationImage(fileUri: Uri): Bitmap? {
try {
val resolver = _context.applicationContext.contentResolver
resolver.openFileDescriptor(fileUri, "r").use { pfd ->
val metaOpts = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFileDescriptor(
pfd!!.fileDescriptor, null, metaOpts
)
val longSide = max(metaOpts.outWidth, metaOpts.outHeight)
val opts = BitmapFactory.Options().apply {
// just a preview in the panel, useless to be in high res
inSampleSize = longSide / 720
}
return BitmapFactory.decodeFileDescriptor(
pfd.fileDescriptor, null, opts
)
}
} catch (e: Throwable) {
logE(
"NotificationChannelHandler::loadNotificationImage",
"Failed generating preview image",
e
)
return null
}
}
private fun loadNotificationImage(fileUri: Uri): Bitmap? {
try {
val resolver = _context.applicationContext.contentResolver
resolver.openFileDescriptor(fileUri, "r").use { pfd ->
val metaOpts = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFileDescriptor(
pfd!!.fileDescriptor, null, metaOpts
)
val longSide = max(metaOpts.outWidth, metaOpts.outHeight)
val opts = BitmapFactory.Options().apply {
// just a preview in the panel, useless to be in high res
inSampleSize = longSide / 720
}
return BitmapFactory.decodeFileDescriptor(
pfd.fileDescriptor, null, opts
)
}
} catch (e: Throwable) {
logE(
"NotificationChannelHandler::loadNotificationImage",
"Failed generating preview image", e
)
return null
}
}
private fun createDownloadChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = context.getString(
R.string.download_notification_channel_name
)
val descriptionStr = context.getString(
R.string.download_notification_channel_description
)
val channel = NotificationChannel(
DOWNLOAD_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH
).apply {
description = descriptionStr
}
private fun createDownloadChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = context.getString(
R.string.download_notification_channel_name
)
val descriptionStr = context.getString(
R.string.download_notification_channel_description
)
val channel = NotificationChannel(
DOWNLOAD_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH
).apply {
description = descriptionStr
}
val manager =
context.getSystemService(
Context.NOTIFICATION_SERVICE
) as NotificationManager
manager.createNotificationChannel(channel)
}
}
val manager = context.getSystemService(
Context.NOTIFICATION_SERVICE
) as NotificationManager
manager.createNotificationChannel(channel)
}
}
private val _context = context
private val _context = context
}

View file

@ -6,76 +6,68 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
internal class PreferenceChannelHandler(context: Context) :
MethodChannel.MethodCallHandler {
companion object {
const val METHOD_CHANNEL = "${K.LIB_ID}/preference_method"
}
MethodChannel.MethodCallHandler {
companion object {
const val METHOD_CHANNEL = "${K.LIB_ID}/preference_method"
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"setBool" -> {
try {
setBool(
call.argument("prefName")!!,
call.argument("key")!!,
call.argument("value")!!,
result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"setBool" -> {
try {
setBool(
call.argument("prefName")!!, call.argument("key")!!,
call.argument("value")!!, result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"getBool" -> {
try {
getBool(
call.argument("prefName")!!,
call.argument("key")!!,
call.argument("defValue"),
result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
"getBool" -> {
try {
getBool(
call.argument("prefName")!!, call.argument("key")!!,
call.argument("defValue"), result
)
} catch (e: Throwable) {
result.error("systemException", e.toString(), null)
}
}
else -> result.notImplemented()
}
}
else -> result.notImplemented()
}
}
private fun setBool(
prefName: String,
key: String,
value: Boolean,
result: MethodChannel.Result
) {
openPref(prefName).run {
edit().run {
putBoolean(key, value)
}.apply()
}
result.success(null)
}
private fun setBool(
prefName: String, key: String, value: Boolean,
result: MethodChannel.Result
) {
openPref(prefName).run {
edit().run {
putBoolean(key, value)
}.apply()
}
result.success(null)
}
private fun getBool(
prefName: String,
key: String,
defValue: Boolean?,
result: MethodChannel.Result
) {
val product = openPref(prefName).run {
if (contains(key)) {
getBoolean(key, false)
} else {
defValue
}
}
result.success(product)
}
private fun getBool(
prefName: String, key: String, defValue: Boolean?,
result: MethodChannel.Result
) {
val product = openPref(prefName).run {
if (contains(key)) {
getBoolean(key, false)
} else {
defValue
}
}
result.success(product)
}
private fun openPref(prefName: String): SharedPreferences {
return context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
}
private fun openPref(prefName: String): SharedPreferences {
return context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
}
private val context = context
private val context = context
}