mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-24 02:18:50 +01:00
Merge branch 'upload-edit-results-to-server' into dev
This commit is contained in:
commit
51a2597ef4
18 changed files with 556 additions and 101 deletions
|
@ -107,14 +107,20 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler,
|
|||
logE(TAG, "Image result uri == null")
|
||||
return null
|
||||
}
|
||||
return if (resultUri.scheme?.startsWith("http") == true) {
|
||||
// remote uri
|
||||
val encodedUrl = URLEncoder.encode(resultUri.toString(), "utf-8")
|
||||
"/result-viewer?url=$encodedUrl"
|
||||
} else {
|
||||
val filename = UriUtil.resolveFilename(this, resultUri)?.let {
|
||||
URLEncoder.encode(it, Charsets.UTF_8.toString())
|
||||
}
|
||||
return StringBuilder().apply {
|
||||
StringBuilder().apply {
|
||||
append("/enhanced-photo-browser?")
|
||||
if (filename != null) append("filename=$filename")
|
||||
}.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private var _initialRoute: String? = null
|
||||
private var _isNewGMapsRenderer = false
|
||||
|
|
|
@ -121,6 +121,7 @@ class ScanAccountDirBloc
|
|||
_accountPrefUpdatedEventListener.begin();
|
||||
|
||||
_nativeFileExifUpdatedListener?.begin();
|
||||
_imageProcessorUploadSuccessListener?.begin();
|
||||
|
||||
on<ScanAccountDirBlocEvent>(_onEvent, transformer: ((events, mapper) {
|
||||
return events.asyncExpand(mapper).distinct((a, b) {
|
||||
|
@ -164,6 +165,7 @@ class ScanAccountDirBloc
|
|||
_accountPrefUpdatedEventListener.end();
|
||||
|
||||
_nativeFileExifUpdatedListener?.end();
|
||||
_imageProcessorUploadSuccessListener?.end();
|
||||
|
||||
_refreshThrottler.clear();
|
||||
return super.close();
|
||||
|
@ -333,6 +335,14 @@ class ScanAccountDirBloc
|
|||
);
|
||||
}
|
||||
|
||||
void _onImageProcessorUploadSuccessEvent(
|
||||
ImageProcessorUploadSuccessEvent ev) {
|
||||
_refreshThrottler.trigger(
|
||||
maxResponceTime: const Duration(seconds: 3),
|
||||
maxPendingCount: 10,
|
||||
);
|
||||
}
|
||||
|
||||
/// Query a small amount of files to give an illusion of quick startup
|
||||
Future<List<File>> _queryOfflineMini(ScanAccountDirBlocQueryBase ev) async {
|
||||
return await ScanDirOfflineMini(_c)(
|
||||
|
@ -540,6 +550,10 @@ class ScanAccountDirBloc
|
|||
late final _nativeFileExifUpdatedListener = platform_k.isWeb
|
||||
? null
|
||||
: NativeEventListener<FileExifUpdatedEvent>(_onNativeFileExifUpdated);
|
||||
late final _imageProcessorUploadSuccessListener = platform_k.isWeb
|
||||
? null
|
||||
: NativeEventListener<ImageProcessorUploadSuccessEvent>(
|
||||
_onImageProcessorUploadSuccessEvent);
|
||||
|
||||
late final _refreshThrottler = Throttler(
|
||||
onTriggered: (_) {
|
||||
|
|
|
@ -31,6 +31,9 @@ class NativeEventListener<T> {
|
|||
case FileExifUpdatedEvent._id:
|
||||
return FileExifUpdatedEvent.fromEvent(ev);
|
||||
|
||||
case ImageProcessorUploadSuccessEvent._id:
|
||||
return ImageProcessorUploadSuccessEvent.fromEvent(ev);
|
||||
|
||||
default:
|
||||
throw ArgumentError("Invalid event: ${ev.event}");
|
||||
}
|
||||
|
@ -63,3 +66,15 @@ class FileExifUpdatedEvent {
|
|||
|
||||
final List<int> fileIds;
|
||||
}
|
||||
|
||||
class ImageProcessorUploadSuccessEvent {
|
||||
const ImageProcessorUploadSuccessEvent();
|
||||
|
||||
factory ImageProcessorUploadSuccessEvent.fromEvent(NativeEventObject ev) {
|
||||
assert(ev.event == _id);
|
||||
assert(ev.data == null);
|
||||
return const ImageProcessorUploadSuccessEvent();
|
||||
}
|
||||
|
||||
static const _id = "ImageProcessorUploadSuccessEvent";
|
||||
}
|
||||
|
|
|
@ -403,13 +403,19 @@
|
|||
},
|
||||
"settingsShowDateInAlbumTitle": "Group photos by date",
|
||||
"settingsShowDateInAlbumDescription": "Apply only when the album is sorted by time",
|
||||
"settingsPhotoEnhancementTitle": "Photo enhancement",
|
||||
"settingsPhotoEnhancementPageTitle": "Photo enhancement settings",
|
||||
"@settingsPhotoEnhancementPageTitle": {
|
||||
"description": "Dedicated page for photo enhancement settings"
|
||||
"settingsImageEditTitle": "Editor",
|
||||
"@settingsImageEditTitle": {
|
||||
"description": "Include settings for image enhancements and the image editor"
|
||||
},
|
||||
"settingsEnhanceMaxResolutionTitle": "Max output resolution",
|
||||
"settingsImageEditDescription": "Customize image enhancements and the image editor",
|
||||
"settingsEnhanceMaxResolutionTitle2": "Image resolution for enhancements",
|
||||
"settingsEnhanceMaxResolutionDescription": "Photos larger than the selected resolution will be downscaled.\n\nHigh resolution photos require significantly more memory and time to process. Please lower this setting if the app crashed while enhancing your photos.",
|
||||
"settingsImageEditSaveResultsToServerTitle": "Save results to server",
|
||||
"@settingsImageEditSaveResultsToServerTitle": {
|
||||
"description": "Whether to save the edit/enhance results to server instead of the current device"
|
||||
},
|
||||
"settingsImageEditSaveResultsToServerTrueDescription": "Results are saved to server, fallback to device storage if failed",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription": "Results are saved to this device",
|
||||
"settingsThemeTitle": "Theme",
|
||||
"settingsThemeDescription": "Customize the appearance of the app",
|
||||
"settingsThemePageTitle": "Theme settings",
|
||||
|
|
|
@ -25,10 +25,13 @@
|
|||
"settingsAlbumPageTitle",
|
||||
"settingsShowDateInAlbumTitle",
|
||||
"settingsShowDateInAlbumDescription",
|
||||
"settingsPhotoEnhancementTitle",
|
||||
"settingsPhotoEnhancementPageTitle",
|
||||
"settingsEnhanceMaxResolutionTitle",
|
||||
"settingsImageEditTitle",
|
||||
"settingsImageEditDescription",
|
||||
"settingsEnhanceMaxResolutionTitle2",
|
||||
"settingsEnhanceMaxResolutionDescription",
|
||||
"settingsImageEditSaveResultsToServerTitle",
|
||||
"settingsImageEditSaveResultsToServerTrueDescription",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription",
|
||||
"settingsMiscellaneousTitle",
|
||||
"settingsMiscellaneousPageTitle",
|
||||
"settingsDoubleTapExitTitle",
|
||||
|
@ -190,10 +193,13 @@
|
|||
"settingsAlbumPageTitle",
|
||||
"settingsShowDateInAlbumTitle",
|
||||
"settingsShowDateInAlbumDescription",
|
||||
"settingsPhotoEnhancementTitle",
|
||||
"settingsPhotoEnhancementPageTitle",
|
||||
"settingsEnhanceMaxResolutionTitle",
|
||||
"settingsImageEditTitle",
|
||||
"settingsImageEditDescription",
|
||||
"settingsEnhanceMaxResolutionTitle2",
|
||||
"settingsEnhanceMaxResolutionDescription",
|
||||
"settingsImageEditSaveResultsToServerTitle",
|
||||
"settingsImageEditSaveResultsToServerTrueDescription",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription",
|
||||
"settingsMiscellaneousTitle",
|
||||
"settingsMiscellaneousPageTitle",
|
||||
"settingsDoubleTapExitTitle",
|
||||
|
@ -351,6 +357,12 @@
|
|||
"settingsPhotosPageTitle",
|
||||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"settingsImageEditTitle",
|
||||
"settingsImageEditDescription",
|
||||
"settingsEnhanceMaxResolutionTitle2",
|
||||
"settingsImageEditSaveResultsToServerTitle",
|
||||
"settingsImageEditSaveResultsToServerTrueDescription",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription",
|
||||
"settingsDoubleTapExitTitle",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
|
@ -406,6 +418,12 @@
|
|||
"settingsPhotosPageTitle",
|
||||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"settingsImageEditTitle",
|
||||
"settingsImageEditDescription",
|
||||
"settingsEnhanceMaxResolutionTitle2",
|
||||
"settingsImageEditSaveResultsToServerTitle",
|
||||
"settingsImageEditSaveResultsToServerTrueDescription",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription",
|
||||
"rootPickerSkipConfirmationDialogContent2",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
|
@ -429,6 +447,12 @@
|
|||
"settingsPhotosPageTitle",
|
||||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"settingsImageEditTitle",
|
||||
"settingsImageEditDescription",
|
||||
"settingsEnhanceMaxResolutionTitle2",
|
||||
"settingsImageEditSaveResultsToServerTitle",
|
||||
"settingsImageEditSaveResultsToServerTrueDescription",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription",
|
||||
"shareMethodPreviewTitle",
|
||||
"shareMethodPreviewDescription",
|
||||
"shareMethodOriginalFileTitle",
|
||||
|
@ -456,10 +480,13 @@
|
|||
"settingsPhotosPageTitle",
|
||||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"settingsPhotoEnhancementTitle",
|
||||
"settingsPhotoEnhancementPageTitle",
|
||||
"settingsEnhanceMaxResolutionTitle",
|
||||
"settingsImageEditTitle",
|
||||
"settingsImageEditDescription",
|
||||
"settingsEnhanceMaxResolutionTitle2",
|
||||
"settingsEnhanceMaxResolutionDescription",
|
||||
"settingsImageEditSaveResultsToServerTitle",
|
||||
"settingsImageEditSaveResultsToServerTrueDescription",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription",
|
||||
"settingsMiscellaneousTitle",
|
||||
"settingsMiscellaneousPageTitle",
|
||||
"settingsDoubleTapExitTitle",
|
||||
|
@ -539,10 +566,13 @@
|
|||
"settingsPhotosPageTitle",
|
||||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"settingsPhotoEnhancementTitle",
|
||||
"settingsPhotoEnhancementPageTitle",
|
||||
"settingsEnhanceMaxResolutionTitle",
|
||||
"settingsImageEditTitle",
|
||||
"settingsImageEditDescription",
|
||||
"settingsEnhanceMaxResolutionTitle2",
|
||||
"settingsEnhanceMaxResolutionDescription",
|
||||
"settingsImageEditSaveResultsToServerTitle",
|
||||
"settingsImageEditSaveResultsToServerTrueDescription",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription",
|
||||
"settingsMiscellaneousTitle",
|
||||
"settingsMiscellaneousPageTitle",
|
||||
"settingsDoubleTapExitTitle",
|
||||
|
@ -640,10 +670,13 @@
|
|||
"settingsPhotosPageTitle",
|
||||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"settingsPhotoEnhancementTitle",
|
||||
"settingsPhotoEnhancementPageTitle",
|
||||
"settingsEnhanceMaxResolutionTitle",
|
||||
"settingsImageEditTitle",
|
||||
"settingsImageEditDescription",
|
||||
"settingsEnhanceMaxResolutionTitle2",
|
||||
"settingsEnhanceMaxResolutionDescription",
|
||||
"settingsImageEditSaveResultsToServerTitle",
|
||||
"settingsImageEditSaveResultsToServerTrueDescription",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription",
|
||||
"settingsMiscellaneousTitle",
|
||||
"settingsMiscellaneousPageTitle",
|
||||
"settingsDoubleTapExitTitle",
|
||||
|
@ -720,10 +753,13 @@
|
|||
"settingsPhotosPageTitle",
|
||||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"settingsPhotoEnhancementTitle",
|
||||
"settingsPhotoEnhancementPageTitle",
|
||||
"settingsEnhanceMaxResolutionTitle",
|
||||
"settingsImageEditTitle",
|
||||
"settingsImageEditDescription",
|
||||
"settingsEnhanceMaxResolutionTitle2",
|
||||
"settingsEnhanceMaxResolutionDescription",
|
||||
"settingsImageEditSaveResultsToServerTitle",
|
||||
"settingsImageEditSaveResultsToServerTrueDescription",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription",
|
||||
"settingsMiscellaneousTitle",
|
||||
"settingsMiscellaneousPageTitle",
|
||||
"settingsDoubleTapExitTitle",
|
||||
|
@ -800,10 +836,13 @@
|
|||
"settingsPhotosPageTitle",
|
||||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"settingsPhotoEnhancementTitle",
|
||||
"settingsPhotoEnhancementPageTitle",
|
||||
"settingsEnhanceMaxResolutionTitle",
|
||||
"settingsImageEditTitle",
|
||||
"settingsImageEditDescription",
|
||||
"settingsEnhanceMaxResolutionTitle2",
|
||||
"settingsEnhanceMaxResolutionDescription",
|
||||
"settingsImageEditSaveResultsToServerTitle",
|
||||
"settingsImageEditSaveResultsToServerTrueDescription",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription",
|
||||
"settingsMiscellaneousTitle",
|
||||
"settingsMiscellaneousPageTitle",
|
||||
"settingsDoubleTapExitTitle",
|
||||
|
@ -880,10 +919,13 @@
|
|||
"settingsPhotosPageTitle",
|
||||
"settingsMemoriesRangeTitle",
|
||||
"settingsMemoriesRangeValueText",
|
||||
"settingsPhotoEnhancementTitle",
|
||||
"settingsPhotoEnhancementPageTitle",
|
||||
"settingsEnhanceMaxResolutionTitle",
|
||||
"settingsImageEditTitle",
|
||||
"settingsImageEditDescription",
|
||||
"settingsEnhanceMaxResolutionTitle2",
|
||||
"settingsEnhanceMaxResolutionDescription",
|
||||
"settingsImageEditSaveResultsToServerTitle",
|
||||
"settingsImageEditSaveResultsToServerTrueDescription",
|
||||
"settingsImageEditSaveResultsToServerFalseDescription",
|
||||
"settingsMiscellaneousTitle",
|
||||
"settingsMiscellaneousPageTitle",
|
||||
"settingsDoubleTapExitTitle",
|
||||
|
|
|
@ -249,6 +249,15 @@ class Pref {
|
|||
Future<bool> setMemoriesRange(int value) => _set<int>(PrefKey.memoriesRange,
|
||||
value, (key, value) => provider.setInt(key, value));
|
||||
|
||||
bool? isSaveEditResultToServer() =>
|
||||
provider.getBool(PrefKey.saveEditResultToServer);
|
||||
bool isSaveEditResultToServerOr([bool def = true]) =>
|
||||
isSaveEditResultToServer() ?? def;
|
||||
Future<bool> setSaveEditResultToServer(bool value) => _set<bool>(
|
||||
PrefKey.saveEditResultToServer,
|
||||
value,
|
||||
(key, value) => provider.setBool(key, value));
|
||||
|
||||
Future<bool> _set<T>(PrefKey key, T value,
|
||||
Future<bool> 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:
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import 'package:nc_photos/entity/album.dart';
|
|||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||
import 'package:nc_photos/event/event.dart';
|
||||
import 'package:nc_photos/event/native_event.dart';
|
||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/language_util.dart' as language_util;
|
||||
|
@ -78,11 +79,13 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
_initBloc();
|
||||
_web?.onInitState();
|
||||
_prefUpdatedListener.begin();
|
||||
_imageProcessorUploadSuccessListener?.begin();
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
_prefUpdatedListener.end();
|
||||
_imageProcessorUploadSuccessListener?.end();
|
||||
_web?.onDispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
@ -512,6 +515,13 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
}
|
||||
}
|
||||
|
||||
void _onImageProcessorUploadSuccessEvent(
|
||||
ImageProcessorUploadSuccessEvent ev) {
|
||||
_log.info(
|
||||
"[_onImageProcessorUploadSuccessEvent] Scheduling metadata task after next refresh");
|
||||
_hasFiredMetadataTask.value = false;
|
||||
}
|
||||
|
||||
void _tryStartMetadataTask({
|
||||
bool ignoreFired = false,
|
||||
}) {
|
||||
|
@ -714,6 +724,10 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
|
||||
late final _prefUpdatedListener =
|
||||
AppEventListener<PrefUpdatedEvent>(_onPrefUpdated);
|
||||
late final _imageProcessorUploadSuccessListener = platform_k.isWeb
|
||||
? null
|
||||
: NativeEventListener<ImageProcessorUploadSuccessEvent>(
|
||||
_onImageProcessorUploadSuccessEvent);
|
||||
|
||||
late final _Web? _web = platform_k.isWeb ? _Web(this) : null;
|
||||
|
||||
|
|
|
@ -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<ImageEditor> {
|
|||
}
|
||||
|
||||
Future<void> _onSavePressed(BuildContext context) async {
|
||||
final c = KiwiContainer().resolve<DiContainer>();
|
||||
await ImageProcessor.filter(
|
||||
"${widget.account.url}/${widget.file.path}",
|
||||
widget.file.filename,
|
||||
|
@ -267,6 +270,7 @@ class _ImageEditorState extends State<ImageEditor> {
|
|||
headers: {
|
||||
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
|
||||
},
|
||||
isSaveToServer: c.pref.isSaveEditResultToServerOr(),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import 'package:nc_photos/widget/people_browser.dart';
|
|||
import 'package:nc_photos/widget/person_browser.dart';
|
||||
import 'package:nc_photos/widget/place_browser.dart';
|
||||
import 'package:nc_photos/widget/places_browser.dart';
|
||||
import 'package:nc_photos/widget/result_viewer.dart';
|
||||
import 'package:nc_photos/widget/root_picker.dart';
|
||||
import 'package:nc_photos/widget/settings.dart';
|
||||
import 'package:nc_photos/widget/setup.dart';
|
||||
|
@ -171,6 +172,7 @@ class _MyAppState extends State<MyApp>
|
|||
route ??= _handlePeopleBrowserRoute(settings);
|
||||
route ??= _handlePlaceBrowserRoute(settings);
|
||||
route ??= _handlePlacesBrowserRoute(settings);
|
||||
route ??= _handleResultViewerRoute(settings);
|
||||
return route;
|
||||
}
|
||||
|
||||
|
@ -600,6 +602,25 @@ class _MyAppState extends State<MyApp>
|
|||
return null;
|
||||
}
|
||||
|
||||
Route<dynamic>? _handleResultViewerRoute(RouteSettings settings) {
|
||||
try {
|
||||
if (settings.name == ResultViewer.routeName &&
|
||||
settings.arguments != null) {
|
||||
final args = settings.arguments as ResultViewerArguments;
|
||||
return ResultViewer.buildRoute(args);
|
||||
} else if (settings.name?.startsWith("${ResultViewer.routeName}?") ==
|
||||
true) {
|
||||
final queries = Uri.parse(settings.name!).queryParameters;
|
||||
final args = ResultViewerArguments(queries["url"]!);
|
||||
return ResultViewer.buildRoute(args);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe("[_handleResultViewerRoute] Failed while handling route", e,
|
||||
stackTrace);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||
final _navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
|
|
129
app/lib/widget/result_viewer.dart
Normal file
129
app/lib/widget/result_viewer.dart
Normal file
|
@ -0,0 +1,129 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:kiwi/kiwi.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/app_localizations.dart';
|
||||
import 'package:nc_photos/di_container.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/pref.dart';
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
import 'package:nc_photos/theme.dart';
|
||||
import 'package:nc_photos/use_case/ls_single_file.dart';
|
||||
import 'package:nc_photos/widget/viewer.dart';
|
||||
|
||||
class ResultViewerArguments {
|
||||
const ResultViewerArguments(this.resultUrl);
|
||||
|
||||
final String resultUrl;
|
||||
}
|
||||
|
||||
/// This is an intermediate widget in charge of preparing the file to be
|
||||
/// eventually shown in [Viewer]
|
||||
class ResultViewer extends StatefulWidget {
|
||||
static const routeName = "/result-viewer";
|
||||
|
||||
const ResultViewer({
|
||||
super.key,
|
||||
required this.resultUrl,
|
||||
});
|
||||
|
||||
ResultViewer.fromArgs(ResultViewerArguments args, {Key? key})
|
||||
: this(
|
||||
key: key,
|
||||
resultUrl: args.resultUrl,
|
||||
);
|
||||
|
||||
static Route buildRoute(ResultViewerArguments args) => MaterialPageRoute(
|
||||
builder: (_) => ResultViewer.fromArgs(args),
|
||||
);
|
||||
|
||||
@override
|
||||
createState() => _ResultViewerState();
|
||||
|
||||
final String resultUrl;
|
||||
}
|
||||
|
||||
class _ResultViewerState extends State<ResultViewer> {
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
_c = KiwiContainer().resolve<DiContainer>();
|
||||
_doWork();
|
||||
}
|
||||
|
||||
@override
|
||||
build(BuildContext context) {
|
||||
if (_file == null) {
|
||||
return AppTheme(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.black,
|
||||
shadowColor: Colors.black,
|
||||
foregroundColor: Colors.white.withOpacity(.87),
|
||||
elevation: 0,
|
||||
),
|
||||
body: Container(
|
||||
color: Colors.black,
|
||||
alignment: Alignment.topCenter,
|
||||
child: const LinearProgressIndicator(),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Viewer(
|
||||
account: _account!,
|
||||
streamFiles: [_file!],
|
||||
startIndex: 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _doWork() async {
|
||||
_log.info("[_doWork] URL: ${widget.resultUrl}");
|
||||
_account = _c.pref.getCurrentAccount();
|
||||
if (_account == null) {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().errorUnauthenticated),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
if (!widget.resultUrl
|
||||
.startsWith(RegExp(_account!.url, caseSensitive: false))) {
|
||||
_log.severe("[_doWork] File url and current account does not match");
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().errorUnauthenticated),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
// +1 for the slash
|
||||
final filePath = widget.resultUrl.substring(_account!.url.length + 1);
|
||||
// query remote
|
||||
final File file;
|
||||
try {
|
||||
file = await LsSingleFile(_c)(_account!, filePath);
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe("[_doWork] Failed while LsSingleFile", e, stackTrace);
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(exception_util.toUserString(e)),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_file = file;
|
||||
});
|
||||
}
|
||||
|
||||
late final DiContainer _c;
|
||||
Account? _account;
|
||||
File? _file;
|
||||
|
||||
static final _log = Logger("widget.result_viewer._ResultViewerState");
|
||||
}
|
|
@ -177,7 +177,8 @@ class _SettingsState extends State<Settings> {
|
|||
Icons.auto_fix_high_outlined,
|
||||
color: AppTheme.getUnfocusedIconColor(context),
|
||||
),
|
||||
label: L10n.global().settingsPhotoEnhancementTitle,
|
||||
label: L10n.global().settingsImageEditTitle,
|
||||
description: L10n.global().settingsImageEditDescription,
|
||||
builder: () => const EnhancementSettings(),
|
||||
),
|
||||
_buildSubSettings(
|
||||
|
@ -1293,6 +1294,7 @@ class _EnhancementSettingsState extends State<EnhancementSettings> {
|
|||
super.initState();
|
||||
_maxWidth = Pref().getEnhanceMaxWidthOr();
|
||||
_maxHeight = Pref().getEnhanceMaxHeightOr();
|
||||
_isSaveEditResultToServer = Pref().isSaveEditResultToServerOr();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1311,13 +1313,24 @@ class _EnhancementSettingsState extends State<EnhancementSettings> {
|
|||
slivers: [
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
title: Text(L10n.global().settingsPhotoEnhancementPageTitle),
|
||||
title: Text(L10n.global().settingsImageEditTitle),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
SwitchListTile(
|
||||
title: Text(
|
||||
L10n.global().settingsImageEditSaveResultsToServerTitle),
|
||||
subtitle: Text(_isSaveEditResultToServer
|
||||
? L10n.global()
|
||||
.settingsImageEditSaveResultsToServerTrueDescription
|
||||
: L10n.global()
|
||||
.settingsImageEditSaveResultsToServerFalseDescription),
|
||||
value: _isSaveEditResultToServer,
|
||||
onChanged: _onSaveEditResultToServerChanged,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(L10n.global().settingsEnhanceMaxResolutionTitle),
|
||||
title: Text(L10n.global().settingsEnhanceMaxResolutionTitle2),
|
||||
subtitle: Text("${_maxWidth}x$_maxHeight"),
|
||||
onTap: () => _onMaxResolutionTap(context),
|
||||
),
|
||||
|
@ -1335,7 +1348,7 @@ class _EnhancementSettingsState extends State<EnhancementSettings> {
|
|||
context: context,
|
||||
builder: (_) => AppTheme(
|
||||
child: AlertDialog(
|
||||
title: Text(L10n.global().settingsEnhanceMaxResolutionTitle),
|
||||
title: Text(L10n.global().settingsEnhanceMaxResolutionTitle2),
|
||||
content: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
@ -1394,8 +1407,26 @@ class _EnhancementSettingsState extends State<EnhancementSettings> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _onSaveEditResultToServerChanged(bool value) async {
|
||||
final oldValue = _isSaveEditResultToServer;
|
||||
setState(() {
|
||||
_isSaveEditResultToServer = value;
|
||||
});
|
||||
if (!await Pref().setSaveEditResultToServer(value)) {
|
||||
_log.severe("[_onSaveEditResultToServerChanged] Failed writing pref");
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
content: Text(L10n.global().writePreferenceFailureNotification),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
setState(() {
|
||||
_isSaveEditResultToServer = oldValue;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
late int _maxWidth;
|
||||
late int _maxHeight;
|
||||
late bool _isSaveEditResultToServer;
|
||||
|
||||
static final _log = Logger("widget.settings._EnhancementSettingsState");
|
||||
}
|
||||
|
|
|
@ -602,11 +602,13 @@ class _ViewerState extends State<Viewer>
|
|||
_log.shout("[_onEnhancePressed] Video file not supported");
|
||||
return;
|
||||
}
|
||||
final c = KiwiContainer().resolve<DiContainer>();
|
||||
|
||||
_log.info("[_onEnhancePressed] Enhance file: ${file.path}");
|
||||
EnhanceHandler(
|
||||
account: widget.account,
|
||||
file: file,
|
||||
isSaveToServer: c.pref.isSaveEditResultToServerOr(),
|
||||
)(context);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ class ImageProcessorChannelHandler(context: Context) :
|
|||
call.argument("filename")!!,
|
||||
call.argument("maxWidth")!!,
|
||||
call.argument("maxHeight")!!,
|
||||
call.argument<Boolean>("isSaveToServer")!!,
|
||||
call.argument("iteration")!!,
|
||||
result
|
||||
)
|
||||
|
@ -45,6 +46,7 @@ class ImageProcessorChannelHandler(context: Context) :
|
|||
call.argument("filename")!!,
|
||||
call.argument("maxWidth")!!,
|
||||
call.argument("maxHeight")!!,
|
||||
call.argument<Boolean>("isSaveToServer")!!,
|
||||
call.argument("radius")!!,
|
||||
result
|
||||
)
|
||||
|
@ -62,6 +64,7 @@ class ImageProcessorChannelHandler(context: Context) :
|
|||
call.argument("filename")!!,
|
||||
call.argument("maxWidth")!!,
|
||||
call.argument("maxHeight")!!,
|
||||
call.argument<Boolean>("isSaveToServer")!!,
|
||||
result
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
|
@ -78,6 +81,7 @@ class ImageProcessorChannelHandler(context: Context) :
|
|||
call.argument("filename")!!,
|
||||
call.argument("maxWidth")!!,
|
||||
call.argument("maxHeight")!!,
|
||||
call.argument<Boolean>("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<Boolean>("isSaveToServer")!!,
|
||||
call.argument("filters")!!,
|
||||
result
|
||||
)
|
||||
|
@ -132,10 +137,10 @@ class ImageProcessorChannelHandler(context: Context) :
|
|||
|
||||
private fun zeroDce(
|
||||
fileUrl: String, headers: Map<String, String>?, 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<String, String>?, 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<String, String>?, 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<String, String>?, 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<String, String>?, filename: String,
|
||||
maxWidth: Int, maxHeight: Int, filters: List<Map<String, Any>>,
|
||||
result: MethodChannel.Result
|
||||
maxWidth: Int, maxHeight: Int, isSaveToServer: Boolean,
|
||||
filters: List<Map<String, Any>>, result: MethodChannel.Result
|
||||
) {
|
||||
// convert to serializable
|
||||
val l = arrayListOf<Serializable>()
|
||||
filters.mapTo(l, { HashMap(it) })
|
||||
method(
|
||||
fileUrl, headers, filename, maxWidth, maxHeight,
|
||||
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<String, String>?, 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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -372,6 +375,7 @@ class ImageProcessorService : Service() {
|
|||
|
||||
private fun notifyResult(event: MessageEvent) {
|
||||
if (event is ImageProcessorCompletedEvent) {
|
||||
NativeEventChannelHandler.fire(ImageProcessorUploadSuccessEvent())
|
||||
notificationManager.notify(
|
||||
RESULT_NOTIFICATION_ID, buildResultNotification(event.result)
|
||||
)
|
||||
|
@ -420,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
|
||||
}
|
||||
|
@ -432,9 +437,11 @@ private class ImageProcessorEnhanceCommand(
|
|||
filename: String,
|
||||
maxWidth: Int,
|
||||
maxHeight: Int,
|
||||
isSaveToServer: Boolean,
|
||||
val args: Map<String, Any?> = 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) {
|
||||
|
@ -470,10 +477,11 @@ private class ImageProcessorFilterCommand(
|
|||
filename: String,
|
||||
maxWidth: Int,
|
||||
maxHeight: Int,
|
||||
isSaveToServer: Boolean,
|
||||
val filters: List<ImageFilter>,
|
||||
) : ImageProcessorImageCommand(
|
||||
startId, ImageProcessorService.METHOD_FILTER, fileUrl, headers, filename,
|
||||
maxWidth, maxHeight
|
||||
maxWidth, maxHeight, isSaveToServer
|
||||
) {
|
||||
override fun apply(context: Context, fileUri: Uri): Bitmap {
|
||||
return ImageFilterProcessor(
|
||||
|
@ -625,8 +633,7 @@ private open class ImageProcessorCommandTask(context: Context) :
|
|||
val filter = cmd.filters.first() as Orientation
|
||||
try {
|
||||
return loselessRotate(
|
||||
filter.degree, file, cmd.filename,
|
||||
"Edited Photos"
|
||||
filter.degree, file, cmd.filename, cmd
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
logE(
|
||||
|
@ -644,14 +651,7 @@ private open class ImageProcessorCommandTask(context: Context) :
|
|||
cmd.apply(context, fileUri)
|
||||
})
|
||||
handleCancel()
|
||||
saveBitmap(
|
||||
output, cmd.filename, file,
|
||||
if (cmd.method in ImageProcessorService.EDIT_METHODS) {
|
||||
"Edited Photos"
|
||||
} else {
|
||||
"Enhanced Photos"
|
||||
}
|
||||
)
|
||||
saveBitmap(output, cmd.filename, file, cmd)
|
||||
} finally {
|
||||
file.delete()
|
||||
}
|
||||
|
@ -680,7 +680,8 @@ private open class ImageProcessorCommandTask(context: Context) :
|
|||
}
|
||||
|
||||
private fun loselessRotate(
|
||||
degree: Int, srcFile: File, outFilename: String, subDir: String
|
||||
degree: Int, srcFile: File, outFilename: String,
|
||||
cmd: ImageProcessorImageCommand
|
||||
): Uri {
|
||||
logI(TAG, "[loselessRotate] $outFilename")
|
||||
val outFile = File.createTempFile("out", null, getTempDir(context))
|
||||
|
@ -693,12 +694,8 @@ private open class ImageProcessorCommandTask(context: Context) :
|
|||
oExif.saveAttributes()
|
||||
|
||||
handleCancel()
|
||||
// move file to user accessible storage
|
||||
val uri = MediaStoreUtil.copyFileToDownload(
|
||||
context, Uri.fromFile(outFile), outFilename,
|
||||
"Photos (for Nextcloud)/$subDir"
|
||||
)
|
||||
return uri
|
||||
val persister = getPersister(cmd.isSaveToServer)
|
||||
return persister.persist(cmd, outFile)
|
||||
} finally {
|
||||
outFile.delete()
|
||||
}
|
||||
|
@ -738,10 +735,12 @@ private open class ImageProcessorCommandTask(context: Context) :
|
|||
}
|
||||
|
||||
private fun saveBitmap(
|
||||
bitmap: Bitmap, filename: String, srcFile: File, subDir: String
|
||||
bitmap: Bitmap, filename: String, srcFile: File,
|
||||
cmd: ImageProcessorImageCommand
|
||||
): Uri {
|
||||
logI(TAG, "[saveBitmap] $filename")
|
||||
val outFile = File.createTempFile("out", null, getTempDir(context))
|
||||
try {
|
||||
outFile.outputStream().use {
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, it)
|
||||
}
|
||||
|
@ -756,13 +755,11 @@ private open class ImageProcessorCommandTask(context: Context) :
|
|||
logE(TAG, "[copyExif] Failed while saving EXIF", e)
|
||||
}
|
||||
|
||||
// move file to user accessible storage
|
||||
val uri = MediaStoreUtil.copyFileToDownload(
|
||||
context, Uri.fromFile(outFile), filename,
|
||||
"Photos (for Nextcloud)/$subDir"
|
||||
)
|
||||
val persister = getPersister(cmd.isSaveToServer)
|
||||
return persister.persist(cmd, outFile)
|
||||
} finally {
|
||||
outFile.delete()
|
||||
return uri
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyExif(from: ExifInterface, to: ExifInterface) {
|
||||
|
@ -783,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
|
||||
}
|
||||
|
@ -801,3 +806,97 @@ private fun getTempDir(context: Context): File {
|
|||
}
|
||||
return f
|
||||
}
|
||||
|
||||
private interface EnhancedFilePersister {
|
||||
fun persist(cmd: ImageProcessorImageCommand, file: File): Uri
|
||||
}
|
||||
|
||||
private class EnhancedFileDevicePersister(context: Context) :
|
||||
EnhancedFilePersister {
|
||||
override fun persist(cmd: ImageProcessorImageCommand, file: File): Uri {
|
||||
val uri = MediaStoreUtil.copyFileToDownload(
|
||||
context, Uri.fromFile(file), cmd.filename,
|
||||
"Photos (for Nextcloud)/${getSubDir(cmd)}"
|
||||
)
|
||||
return uri
|
||||
}
|
||||
|
||||
private fun getSubDir(cmd: ImageProcessorImageCommand): String {
|
||||
return if (cmd.method in ImageProcessorService.EDIT_METHODS) {
|
||||
"Edited Photos"
|
||||
} else {
|
||||
"Enhanced Photos"
|
||||
}
|
||||
}
|
||||
|
||||
val context = context
|
||||
}
|
||||
|
||||
private class EnhancedFileServerPersister :
|
||||
EnhancedFilePersister {
|
||||
companion object {
|
||||
const val TAG = "EnhancedFileServerPersister"
|
||||
}
|
||||
|
||||
override fun persist(cmd: ImageProcessorImageCommand, file: File): Uri {
|
||||
val ext = cmd.fileUrl.substringAfterLast('.', "")
|
||||
val url = if (ext.contains('/')) {
|
||||
// no ext
|
||||
"${cmd.fileUrl}_${getSuffix(cmd)}.jpg"
|
||||
} else {
|
||||
"${cmd.fileUrl.substringBeforeLast('.', "")}_${getSuffix(cmd)}.jpg"
|
||||
}
|
||||
logI(TAG, "[persist] Persist file to server: $url")
|
||||
(URL(url).openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "PUT"
|
||||
instanceFollowRedirects = true
|
||||
connectTimeout = 8000
|
||||
for (entry in (cmd.headers ?: mapOf()).entries) {
|
||||
setRequestProperty(entry.key, entry.value)
|
||||
}
|
||||
}.use {
|
||||
file.inputStream()
|
||||
.use { iStream -> iStream.copyTo(it.outputStream) }
|
||||
val responseCode = it.responseCode
|
||||
if (responseCode / 100 != 2) {
|
||||
logE(TAG, "[persist] Failed uploading file: HTTP$responseCode")
|
||||
throw HttpException(
|
||||
responseCode, "Failed uploading file (HTTP$responseCode)"
|
||||
)
|
||||
}
|
||||
}
|
||||
return Uri.parse(url)
|
||||
}
|
||||
|
||||
private fun getSuffix(cmd: ImageProcessorImageCommand): String {
|
||||
val epoch = System.currentTimeMillis() / 1000
|
||||
return if (cmd.method in ImageProcessorService.EDIT_METHODS) {
|
||||
"edited_$epoch"
|
||||
} else {
|
||||
"enhanced_$epoch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class EnhancedFileServerPersisterWithFallback(context: Context) :
|
||||
EnhancedFilePersister {
|
||||
companion object {
|
||||
const val TAG = "EnhancedFileServerPersisterWithFallback"
|
||||
}
|
||||
|
||||
override fun persist(cmd: ImageProcessorImageCommand, file: File): Uri {
|
||||
try {
|
||||
return server.persist(cmd, file)
|
||||
} catch (e: Throwable) {
|
||||
logW(
|
||||
TAG,
|
||||
"[persist] Failed while persisting to server, switch to fallback",
|
||||
e
|
||||
)
|
||||
}
|
||||
return fallback.persist(cmd, file)
|
||||
}
|
||||
|
||||
private val server = EnhancedFileServerPersister()
|
||||
private val fallback = EnhancedFileDevicePersister(context)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.nkming.nc_photos.plugin
|
||||
|
||||
interface NativeEvent {
|
||||
fun getId(): String
|
||||
fun getData(): String? = null
|
||||
}
|
||||
|
||||
class ImageProcessorUploadSuccessEvent : NativeEvent {
|
||||
companion object {
|
||||
const val id = "ImageProcessorUploadSuccessEvent"
|
||||
}
|
||||
|
||||
override fun getId() = id
|
||||
}
|
|
@ -10,6 +10,20 @@ class NativeEventChannelHandler : MethodChannel.MethodCallHandler,
|
|||
const val EVENT_CHANNEL = "${K.LIB_ID}/native_event"
|
||||
const val METHOD_CHANNEL = "${K.LIB_ID}/native_event_method"
|
||||
|
||||
/**
|
||||
* Fire native events on the native side
|
||||
*/
|
||||
fun fire(eventObj: NativeEvent) {
|
||||
synchronized(eventSinks) {
|
||||
for (s in eventSinks.values) {
|
||||
s.success(buildMap {
|
||||
put("event", eventObj.getId())
|
||||
eventObj.getData()?.also { put("data", it) }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val eventSinks = mutableMapOf<Int, EventChannel.EventSink>()
|
||||
private var nextId = 0
|
||||
}
|
||||
|
@ -29,22 +43,28 @@ class NativeEventChannelHandler : MethodChannel.MethodCallHandler,
|
|||
}
|
||||
|
||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
||||
synchronized(eventSinks) {
|
||||
eventSinks[id] = events
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
synchronized(eventSinks) {
|
||||
eventSinks.remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fire(
|
||||
event: String, data: String?, result: MethodChannel.Result
|
||||
) {
|
||||
synchronized(eventSinks) {
|
||||
for (s in eventSinks.values) {
|
||||
s.success(buildMap {
|
||||
put("event", event)
|
||||
if (data != null) put("data", data)
|
||||
})
|
||||
}
|
||||
}
|
||||
result.success(null)
|
||||
}
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ class ImageProcessor {
|
|||
int maxHeight,
|
||||
int iteration, {
|
||||
Map<String, String>? headers,
|
||||
required bool isSaveToServer,
|
||||
}) =>
|
||||
_methodChannel.invokeMethod("zeroDce", <String, dynamic>{
|
||||
"fileUrl": fileUrl,
|
||||
|
@ -90,6 +91,7 @@ class ImageProcessor {
|
|||
"maxWidth": maxWidth,
|
||||
"maxHeight": maxHeight,
|
||||
"iteration": iteration,
|
||||
"isSaveToServer": isSaveToServer,
|
||||
});
|
||||
|
||||
static Future<void> deepLab3Portrait(
|
||||
|
@ -99,6 +101,7 @@ class ImageProcessor {
|
|||
int maxHeight,
|
||||
int radius, {
|
||||
Map<String, String>? headers,
|
||||
required bool isSaveToServer,
|
||||
}) =>
|
||||
_methodChannel.invokeMethod("deepLab3Portrait", <String, dynamic>{
|
||||
"fileUrl": fileUrl,
|
||||
|
@ -107,6 +110,7 @@ class ImageProcessor {
|
|||
"maxWidth": maxWidth,
|
||||
"maxHeight": maxHeight,
|
||||
"radius": radius,
|
||||
"isSaveToServer": isSaveToServer,
|
||||
});
|
||||
|
||||
static Future<void> esrgan(
|
||||
|
@ -115,6 +119,7 @@ class ImageProcessor {
|
|||
int maxWidth,
|
||||
int maxHeight, {
|
||||
Map<String, String>? headers,
|
||||
required bool isSaveToServer,
|
||||
}) =>
|
||||
_methodChannel.invokeMethod("esrgan", <String, dynamic>{
|
||||
"fileUrl": fileUrl,
|
||||
|
@ -122,6 +127,7 @@ class ImageProcessor {
|
|||
"filename": filename,
|
||||
"maxWidth": maxWidth,
|
||||
"maxHeight": maxHeight,
|
||||
"isSaveToServer": isSaveToServer,
|
||||
});
|
||||
|
||||
static Future<void> arbitraryStyleTransfer(
|
||||
|
@ -132,6 +138,7 @@ class ImageProcessor {
|
|||
String styleUri,
|
||||
double weight, {
|
||||
Map<String, String>? headers,
|
||||
required bool isSaveToServer,
|
||||
}) =>
|
||||
_methodChannel.invokeMethod("arbitraryStyleTransfer", <String, dynamic>{
|
||||
"fileUrl": fileUrl,
|
||||
|
@ -141,6 +148,7 @@ class ImageProcessor {
|
|||
"maxHeight": maxHeight,
|
||||
"styleUri": styleUri,
|
||||
"weight": weight,
|
||||
"isSaveToServer": isSaveToServer,
|
||||
});
|
||||
|
||||
static Future<void> filter(
|
||||
|
@ -150,6 +158,7 @@ class ImageProcessor {
|
|||
int maxHeight,
|
||||
List<ImageFilter> filters, {
|
||||
Map<String, String>? headers,
|
||||
required bool isSaveToServer,
|
||||
}) =>
|
||||
_methodChannel.invokeMethod("filter", <String, dynamic>{
|
||||
"fileUrl": fileUrl,
|
||||
|
@ -158,6 +167,7 @@ class ImageProcessor {
|
|||
"maxWidth": maxWidth,
|
||||
"maxHeight": maxHeight,
|
||||
"filters": filters.map((f) => f.toJson()).toList(),
|
||||
"isSaveToServer": isSaveToServer,
|
||||
});
|
||||
|
||||
static Future<Rgba8Image> filterPreview(
|
||||
|
|
Loading…
Reference in a new issue