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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,16 +1,16 @@
package com.nkming.nc_photos.np_platform_image_processor package com.nkming.nc_photos.np_platform_image_processor
internal interface K { internal interface K {
companion object { companion object {
const val LIB_ID = "com.nkming.nc_photos.np_platform_image_processor" const val LIB_ID = "com.nkming.nc_photos.np_platform_image_processor"
const val IMAGE_PROCESSOR_SERVICE_NOTIFICATION_ID = 5000 const val IMAGE_PROCESSOR_SERVICE_NOTIFICATION_ID = 5000
const val IMAGE_PROCESSOR_SERVICE_RESULT_NOTIFICATION_ID = 5001 const val IMAGE_PROCESSOR_SERVICE_RESULT_NOTIFICATION_ID = 5001
const val IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID = 5002 const val IMAGE_PROCESSOR_SERVICE_RESULT_FAILED_NOTIFICATION_ID = 5002
const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT = const val ACTION_SHOW_IMAGE_PROCESSOR_RESULT =
"${LIB_ID}.ACTION_SHOW_IMAGE_PROCESSOR_RESULT" "${LIB_ID}.ACTION_SHOW_IMAGE_PROCESSOR_RESULT"
const val EXTRA_IMAGE_RESULT_URI = "${LIB_ID}.EXTRA_IMAGE_RESULT_URI" const val EXTRA_IMAGE_RESULT_URI = "${LIB_ID}.EXTRA_IMAGE_RESULT_URI"
} }
} }

View file

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

View file

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

View file

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

View file

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

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 import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class BlackPoint(val weight: Float) : ImageFilter { internal class BlackPoint(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image( override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.width, rgba8.height
rgba8.height )
)
private external fun applyNative( private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray ): ByteArray
} }

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 import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Brightness(val weight: Float) : ImageFilter { internal class Brightness(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image( override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.width, rgba8.height
rgba8.height )
)
private external fun applyNative( private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray ): ByteArray
} }

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 import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Contrast(val weight: Float) : ImageFilter { internal class Contrast(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image( override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.width, rgba8.height
rgba8.height )
)
private external fun applyNative( private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray ): ByteArray
} }

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 import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Cool(val weight: Float) : ImageFilter { internal class Cool(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image( override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.width, rgba8.height
rgba8.height )
)
private external fun applyNative( private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray ): ByteArray
} }

View file

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

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

View file

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

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

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

View file

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

View file

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

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 import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Saturation(val weight: Float) : ImageFilter { internal class Saturation(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image( override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.width, rgba8.height
rgba8.height )
)
private external fun applyNative( private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray ): ByteArray
} }

View file

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

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 import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Tint(val weight: Float) : ImageFilter { internal class Tint(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image( override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.width, rgba8.height
rgba8.height )
)
private external fun applyNative( private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray ): ByteArray
} }

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 import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class Warmth(val weight: Float) : ImageFilter { internal class Warmth(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image( override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.width, rgba8.height
rgba8.height )
)
private external fun applyNative( private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray ): ByteArray
} }

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 import com.nkming.nc_photos.np_platform_image_processor.ImageFilter
internal class WhitePoint(val weight: Float) : ImageFilter { internal class WhitePoint(val weight: Float) : ImageFilter {
override fun apply(rgba8: Rgba8Image) = Rgba8Image( override fun apply(rgba8: Rgba8Image) = Rgba8Image(
applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight), applyNative(rgba8.pixel, rgba8.width, rgba8.height, weight),
rgba8.width, rgba8.width, rgba8.height
rgba8.height )
)
private external fun applyNative( private external fun applyNative(
rgba8: ByteArray, width: Int, height: Int, weight: Float rgba8: ByteArray, width: Int, height: Int, weight: Float
): ByteArray ): ByteArray
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,16 +1,16 @@
package com.nkming.nc_photos.plugin package com.nkming.nc_photos.plugin
internal interface K { internal interface K {
companion object { companion object {
const val DOWNLOAD_NOTIFICATION_ID_MIN = 1000 const val DOWNLOAD_NOTIFICATION_ID_MIN = 1000
const val DOWNLOAD_NOTIFICATION_ID_MAX = 2000 const val DOWNLOAD_NOTIFICATION_ID_MAX = 2000
const val MEDIA_STORE_DELETE_REQUEST_CODE = 11012 const val MEDIA_STORE_DELETE_REQUEST_CODE = 11012
const val LIB_ID = "com.nkming.nc_photos.plugin" const val LIB_ID = "com.nkming.nc_photos.plugin"
const val ACTION_DOWNLOAD_CANCEL = "${LIB_ID}.ACTION_DOWNLOAD_CANCEL" const val ACTION_DOWNLOAD_CANCEL = "${LIB_ID}.ACTION_DOWNLOAD_CANCEL"
const val EXTRA_NOTIFICATION_ID = "${LIB_ID}.EXTRA_NOTIFICATION_ID" const val EXTRA_NOTIFICATION_ID = "${LIB_ID}.EXTRA_NOTIFICATION_ID"
} }
} }

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

View file

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

View file

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

View file

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