diff --git a/app/lib/pref.dart b/app/lib/pref.dart index 14cfe451..19d25bb4 100644 --- a/app/lib/pref.dart +++ b/app/lib/pref.dart @@ -249,6 +249,15 @@ class Pref { Future setMemoriesRange(int value) => _set(PrefKey.memoriesRange, value, (key, value) => provider.setInt(key, value)); + bool? isSaveEditResultToServer() => + provider.getBool(PrefKey.saveEditResultToServer); + bool isSaveEditResultToServerOr([bool def = true]) => + isSaveEditResultToServer() ?? def; + Future setSaveEditResultToServer(bool value) => _set( + PrefKey.saveEditResultToServer, + value, + (key, value) => provider.setBool(key, value)); + Future _set(PrefKey key, T value, Future Function(PrefKey key, T value) setFn) async { if (await setFn(key, value)) { @@ -561,6 +570,7 @@ enum PrefKey { shouldProcessExifWifiOnly, doubleTapExit, memoriesRange, + saveEditResultToServer, // account pref isEnableFaceRecognitionApp, @@ -632,6 +642,8 @@ extension on PrefKey { return "doubleTapExit"; case PrefKey.memoriesRange: return "memoriesRange"; + case PrefKey.saveEditResultToServer: + return "saveEditResultToServer"; // account pref case PrefKey.isEnableFaceRecognitionApp: diff --git a/app/lib/widget/handler/enhance_handler.dart b/app/lib/widget/handler/enhance_handler.dart index e5b4a9ba..706ed2de 100644 --- a/app/lib/widget/handler/enhance_handler.dart +++ b/app/lib/widget/handler/enhance_handler.dart @@ -31,6 +31,7 @@ class EnhanceHandler { const EnhanceHandler({ required this.account, required this.file, + required this.isSaveToServer, }); static bool isSupportedFormat(File file) => @@ -67,6 +68,7 @@ class EnhanceHandler { headers: { "Authorization": Api.getAuthorizationHeaderValue(account), }, + isSaveToServer: isSaveToServer, ); break; @@ -80,6 +82,7 @@ class EnhanceHandler { headers: { "Authorization": Api.getAuthorizationHeaderValue(account), }, + isSaveToServer: isSaveToServer, ); break; @@ -92,6 +95,7 @@ class EnhanceHandler { headers: { "Authorization": Api.getAuthorizationHeaderValue(account), }, + isSaveToServer: isSaveToServer, ); break; @@ -108,6 +112,7 @@ class EnhanceHandler { headers: { "Authorization": Api.getAuthorizationHeaderValue(account), }, + isSaveToServer: isSaveToServer, ); break; } @@ -353,6 +358,7 @@ class EnhanceHandler { final Account account; final File file; + final bool isSaveToServer; static final _log = Logger("widget.handler.enhance_handler.EnhanceHandler"); } diff --git a/app/lib/widget/image_editor.dart b/app/lib/widget/image_editor.dart index 2dfbe447..17135233 100644 --- a/app/lib/widget/image_editor.dart +++ b/app/lib/widget/image_editor.dart @@ -2,11 +2,13 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:kiwi/kiwi.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/api.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/cache_manager_util.dart'; +import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/help_utils.dart' as help_util; import 'package:nc_photos/k.dart' as k; @@ -258,6 +260,7 @@ class _ImageEditorState extends State { } Future _onSavePressed(BuildContext context) async { + final c = KiwiContainer().resolve(); await ImageProcessor.filter( "${widget.account.url}/${widget.file.path}", widget.file.filename, @@ -267,6 +270,7 @@ class _ImageEditorState extends State { headers: { "Authorization": Api.getAuthorizationHeaderValue(widget.account), }, + isSaveToServer: c.pref.isSaveEditResultToServerOr(), ); Navigator.of(context).pop(); } diff --git a/app/lib/widget/viewer.dart b/app/lib/widget/viewer.dart index 5756692e..3efec664 100644 --- a/app/lib/widget/viewer.dart +++ b/app/lib/widget/viewer.dart @@ -602,11 +602,13 @@ class _ViewerState extends State _log.shout("[_onEnhancePressed] Video file not supported"); return; } + final c = KiwiContainer().resolve(); _log.info("[_onEnhancePressed] Enhance file: ${file.path}"); EnhanceHandler( account: widget.account, file: file, + isSaveToServer: c.pref.isSaveEditResultToServerOr(), )(context); } diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorChannelHandler.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorChannelHandler.kt index fa6b04ec..d6fd252a 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorChannelHandler.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorChannelHandler.kt @@ -28,6 +28,7 @@ class ImageProcessorChannelHandler(context: Context) : call.argument("filename")!!, call.argument("maxWidth")!!, call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, call.argument("iteration")!!, result ) @@ -45,6 +46,7 @@ class ImageProcessorChannelHandler(context: Context) : call.argument("filename")!!, call.argument("maxWidth")!!, call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, call.argument("radius")!!, result ) @@ -62,6 +64,7 @@ class ImageProcessorChannelHandler(context: Context) : call.argument("filename")!!, call.argument("maxWidth")!!, call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, result ) } catch (e: Throwable) { @@ -78,6 +81,7 @@ class ImageProcessorChannelHandler(context: Context) : call.argument("filename")!!, call.argument("maxWidth")!!, call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, call.argument("styleUri")!!, call.argument("weight")!!, result @@ -96,6 +100,7 @@ class ImageProcessorChannelHandler(context: Context) : call.argument("filename")!!, call.argument("maxWidth")!!, call.argument("maxHeight")!!, + call.argument("isSaveToServer")!!, call.argument("filters")!!, result ) @@ -132,10 +137,10 @@ class ImageProcessorChannelHandler(context: Context) : private fun zeroDce( fileUrl: String, headers: Map?, filename: String, - maxWidth: Int, maxHeight: Int, iteration: Int, + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, iteration: Int, result: MethodChannel.Result ) = method( - fileUrl, headers, filename, maxWidth, maxHeight, + fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, ImageProcessorService.METHOD_ZERO_DCE, result, onIntent = { it.putExtra(ImageProcessorService.EXTRA_ITERATION, iteration) } @@ -143,9 +148,10 @@ class ImageProcessorChannelHandler(context: Context) : private fun deepLab3Portrait( fileUrl: String, headers: Map?, filename: String, - maxWidth: Int, maxHeight: Int, radius: Int, result: MethodChannel.Result + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, radius: Int, + result: MethodChannel.Result ) = method( - fileUrl, headers, filename, maxWidth, maxHeight, + fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, ImageProcessorService.METHOD_DEEP_LAP_PORTRAIT, result, onIntent = { it.putExtra(ImageProcessorService.EXTRA_RADIUS, radius) } @@ -153,18 +159,19 @@ class ImageProcessorChannelHandler(context: Context) : private fun esrgan( fileUrl: String, headers: Map?, filename: String, - maxWidth: Int, maxHeight: Int, result: MethodChannel.Result + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, + result: MethodChannel.Result ) = method( - fileUrl, headers, filename, maxWidth, maxHeight, + fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, ImageProcessorService.METHOD_ESRGAN, result ) private fun arbitraryStyleTransfer( fileUrl: String, headers: Map?, filename: String, - maxWidth: Int, maxHeight: Int, styleUri: String, weight: Float, - result: MethodChannel.Result + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, + styleUri: String, weight: Float, result: MethodChannel.Result ) = method( - fileUrl, headers, filename, maxWidth, maxHeight, + fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, ImageProcessorService.METHOD_ARBITRARY_STYLE_TRANSFER, result, onIntent = { it.putExtra( @@ -176,14 +183,14 @@ class ImageProcessorChannelHandler(context: Context) : private fun filter( fileUrl: String, headers: Map?, filename: String, - maxWidth: Int, maxHeight: Int, filters: List>, - result: MethodChannel.Result + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, + filters: List>, result: MethodChannel.Result ) { // convert to serializable val l = arrayListOf() filters.mapTo(l, { HashMap(it) }) method( - fileUrl, headers, filename, maxWidth, maxHeight, + fileUrl, headers, filename, maxWidth, maxHeight, isSaveToServer, ImageProcessorService.METHOD_FILTER, result, onIntent = { it.putExtra(ImageProcessorService.EXTRA_FILTERS, l) @@ -204,7 +211,7 @@ class ImageProcessorChannelHandler(context: Context) : private fun method( fileUrl: String, headers: Map?, filename: String, - maxWidth: Int, maxHeight: Int, method: String, + maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean, method: String, result: MethodChannel.Result, onIntent: ((Intent) -> Unit)? = null ) { val intent = Intent(context, ImageProcessorService::class.java).apply { @@ -216,6 +223,9 @@ class ImageProcessorChannelHandler(context: Context) : putExtra(ImageProcessorService.EXTRA_FILENAME, filename) putExtra(ImageProcessorService.EXTRA_MAX_WIDTH, maxWidth) putExtra(ImageProcessorService.EXTRA_MAX_HEIGHT, maxHeight) + putExtra( + ImageProcessorService.EXTRA_IS_SAVE_TO_SERVER, isSaveToServer + ) onIntent?.invoke(this) } ContextCompat.startForegroundService(context, intent) diff --git a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt index 59c1c1d3..d04dbb22 100644 --- a/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt +++ b/plugin/android/src/main/kotlin/com/nkming/nc_photos/plugin/ImageProcessorService.kt @@ -37,6 +37,7 @@ class ImageProcessorService : Service() { const val EXTRA_FILENAME = "filename" const val EXTRA_MAX_WIDTH = "maxWidth" const val EXTRA_MAX_HEIGHT = "maxHeight" + const val EXTRA_IS_SAVE_TO_SERVER = "isSaveToServer" const val EXTRA_RADIUS = "radius" const val EXTRA_ITERATION = "iteration" const val EXTRA_STYLE_URI = "styleUri" @@ -134,7 +135,7 @@ class ImageProcessorService : Service() { // there are commands running in the bg addCommand( ImageProcessorEnhanceCommand( - startId, "null", "", null, "", 0, 0 + startId, "null", "", null, "", 0, 0, false ) ) } @@ -184,10 +185,11 @@ class ImageProcessorService : Service() { val filename = extras.getString(EXTRA_FILENAME)!! val maxWidth = extras.getInt(EXTRA_MAX_WIDTH) val maxHeight = extras.getInt(EXTRA_MAX_HEIGHT) + val isSaveToServer = extras.getBoolean(EXTRA_IS_SAVE_TO_SERVER) addCommand( ImageProcessorFilterCommand( startId, fileUrl, headers, filename, maxWidth, - maxHeight, filters + maxHeight, isSaveToServer, filters ) ) } @@ -211,10 +213,11 @@ class ImageProcessorService : Service() { val filename = extras.getString(EXTRA_FILENAME)!! val maxWidth = extras.getInt(EXTRA_MAX_WIDTH) val maxHeight = extras.getInt(EXTRA_MAX_HEIGHT) + val isSaveToServer = extras.getBoolean(EXTRA_IS_SAVE_TO_SERVER) addCommand( ImageProcessorEnhanceCommand( startId, method, fileUrl, headers, filename, maxWidth, - maxHeight, args = args + maxHeight, isSaveToServer, args = args ) ) } @@ -421,6 +424,7 @@ private abstract class ImageProcessorImageCommand( val filename: String, val maxWidth: Int, val maxHeight: Int, + val isSaveToServer: Boolean, ) : ImageProcessorCommand { abstract fun apply(context: Context, fileUri: Uri): Bitmap } @@ -433,9 +437,11 @@ private class ImageProcessorEnhanceCommand( filename: String, maxWidth: Int, maxHeight: Int, + isSaveToServer: Boolean, val args: Map = mapOf(), ) : ImageProcessorImageCommand( - startId, method, fileUrl, headers, filename, maxWidth, maxHeight + startId, method, fileUrl, headers, filename, maxWidth, maxHeight, + isSaveToServer ) { override fun apply(context: Context, fileUri: Uri): Bitmap { return when (method) { @@ -471,10 +477,11 @@ private class ImageProcessorFilterCommand( filename: String, maxWidth: Int, maxHeight: Int, + isSaveToServer: Boolean, val filters: List, ) : ImageProcessorImageCommand( startId, ImageProcessorService.METHOD_FILTER, fileUrl, headers, filename, - maxWidth, maxHeight + maxWidth, maxHeight, isSaveToServer ) { override fun apply(context: Context, fileUri: Uri): Bitmap { return ImageFilterProcessor( @@ -687,7 +694,7 @@ private open class ImageProcessorCommandTask(context: Context) : oExif.saveAttributes() handleCancel() - val persister = EnhancedFileServerPersisterWithFallback(context) + val persister = getPersister(cmd.isSaveToServer) return persister.persist(cmd, outFile) } finally { outFile.delete() @@ -748,7 +755,7 @@ private open class ImageProcessorCommandTask(context: Context) : logE(TAG, "[copyExif] Failed while saving EXIF", e) } - val persister = EnhancedFileServerPersisterWithFallback(context) + val persister = getPersister(cmd.isSaveToServer) return persister.persist(cmd, outFile) } finally { outFile.delete() @@ -773,6 +780,14 @@ private open class ImageProcessorCommandTask(context: Context) : } } + private fun getPersister(isSaveToServer: Boolean): EnhancedFilePersister { + return if (isSaveToServer) { + EnhancedFileServerPersisterWithFallback(context) + } else { + EnhancedFileDevicePersister(context) + } + } + @SuppressLint("StaticFieldLeak") private val context = context } diff --git a/plugin/lib/src/image_processor.dart b/plugin/lib/src/image_processor.dart index 289a220e..e6821a86 100644 --- a/plugin/lib/src/image_processor.dart +++ b/plugin/lib/src/image_processor.dart @@ -82,6 +82,7 @@ class ImageProcessor { int maxHeight, int iteration, { Map? headers, + required bool isSaveToServer, }) => _methodChannel.invokeMethod("zeroDce", { "fileUrl": fileUrl, @@ -90,6 +91,7 @@ class ImageProcessor { "maxWidth": maxWidth, "maxHeight": maxHeight, "iteration": iteration, + "isSaveToServer": isSaveToServer, }); static Future deepLab3Portrait( @@ -99,6 +101,7 @@ class ImageProcessor { int maxHeight, int radius, { Map? headers, + required bool isSaveToServer, }) => _methodChannel.invokeMethod("deepLab3Portrait", { "fileUrl": fileUrl, @@ -107,6 +110,7 @@ class ImageProcessor { "maxWidth": maxWidth, "maxHeight": maxHeight, "radius": radius, + "isSaveToServer": isSaveToServer, }); static Future esrgan( @@ -115,6 +119,7 @@ class ImageProcessor { int maxWidth, int maxHeight, { Map? headers, + required bool isSaveToServer, }) => _methodChannel.invokeMethod("esrgan", { "fileUrl": fileUrl, @@ -122,6 +127,7 @@ class ImageProcessor { "filename": filename, "maxWidth": maxWidth, "maxHeight": maxHeight, + "isSaveToServer": isSaveToServer, }); static Future arbitraryStyleTransfer( @@ -132,6 +138,7 @@ class ImageProcessor { String styleUri, double weight, { Map? headers, + required bool isSaveToServer, }) => _methodChannel.invokeMethod("arbitraryStyleTransfer", { "fileUrl": fileUrl, @@ -141,6 +148,7 @@ class ImageProcessor { "maxHeight": maxHeight, "styleUri": styleUri, "weight": weight, + "isSaveToServer": isSaveToServer, }); static Future filter( @@ -150,6 +158,7 @@ class ImageProcessor { int maxHeight, List filters, { Map? headers, + required bool isSaveToServer, }) => _methodChannel.invokeMethod("filter", { "fileUrl": fileUrl, @@ -158,6 +167,7 @@ class ImageProcessor { "maxWidth": maxWidth, "maxHeight": maxHeight, "filters": filters.map((f) => f.toJson()).toList(), + "isSaveToServer": isSaveToServer, }); static Future filterPreview(