Replace enhancement dialog with a nice page

This commit is contained in:
Ming Ming 2022-09-15 18:44:38 +08:00
parent a43aa4cbbd
commit aaee53b68f
19 changed files with 635 additions and 154 deletions

BIN
app/assets/color-pop0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
app/assets/color-pop1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
app/assets/low-light0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
app/assets/low-light1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
app/assets/retouch0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
app/assets/retouch1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View file

@ -1234,6 +1234,7 @@
"@enhanceLowLightTitle": {
"description": "Enhance a photo taken in low-light environment"
},
"enhanceLowLightDescription": "Brighten your photos taken in low-light environments",
"enhanceLowLightParamBrightnessLabel": "Brightness",
"@enhanceLowLightParamBrightnessLabel": {
"description": "This parameter sets how much brighter the output will be"
@ -1250,6 +1251,7 @@
"@enhancePortraitBlurTitle": {
"description": "Blur the background of a photo"
},
"enhancePortraitBlurDescription": "Blur the background of your photos, work best with portraits",
"enhancePortraitBlurParamBlurLabel": "Blurriness",
"@enhancePortraitBlurParamBlurLabel": {
"description": "This parameter sets the radius of the blur filter"
@ -1258,6 +1260,7 @@
"@enhanceSuperResolution4xTitle": {
"description": "Upscale an image. The algorithm implemented in the app will upscale to 4x the original resolution (eg, 100x100 to 400x400)"
},
"enhanceSuperResolution4xDescription": "Enlarge your photos to 4x of its original resolution (see Help for details on how max resolution applies here)",
"enhanceStyleTransferTitle": "Style transfer",
"@enhanceStyleTransferTitle": {
"description": "Transfer the image style from a reference image to a photo"
@ -1266,10 +1269,16 @@
"@enhanceStyleTransferStyleDialogTitle": {
"description": "Pick a reference image for the style transfer algorithm"
},
"enhanceStyleTransferStyleDialogDescription": "Transfer image style from a reference image to your photos",
"enhanceStyleTransferNoStyleSelectedNotification": "Please pick a style",
"@enhanceStyleTransferNoStyleSelectedNotification": {
"description": "Show this error if users did not pick a reference image"
},
"enhanceColorPopTitle": "Color pop",
"@enhanceColorPopTitle": {
"description": "Desaturate the background of a photo"
},
"enhanceColorPopDescription": "Desaturate the background of your photos, work best with portraits",
"enhanceGenericParamWeightLabel": "Weight",
"@enhanceGenericParamWeightLabel": {
"description": "This generic parameter sets the weight of the applied effect. The effect will be more obvious when the weight is high."
@ -1278,6 +1287,7 @@
"@enhanceRetouchTitle": {
"description": "Automatically improve your photo"
},
"enhanceRetouchDescription": "Automatically retouch your photos, improve overall color and vibrance",
"doubleTapExitNotification": "Tap again to exit",
"@doubleTapExitNotification": {
"description": "If double tap to exit is enabled in settings, shown when users tap the back button"

View file

@ -110,17 +110,24 @@
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"enhanceLowLightDescription",
"enhanceLowLightParamBrightnessLabel",
"collectionEditedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle",
"enhancePortraitBlurDescription",
"enhancePortraitBlurParamBlurLabel",
"enhanceSuperResolution4xTitle",
"enhanceSuperResolution4xDescription",
"enhanceStyleTransferTitle",
"enhanceStyleTransferStyleDialogTitle",
"enhanceStyleTransferStyleDialogDescription",
"enhanceStyleTransferNoStyleSelectedNotification",
"enhanceColorPopTitle",
"enhanceColorPopDescription",
"enhanceGenericParamWeightLabel",
"enhanceRetouchTitle",
"enhanceRetouchDescription",
"doubleTapExitNotification",
"imageEditDiscardDialogTitle",
"imageEditDiscardDialogContent",
@ -292,17 +299,24 @@
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"enhanceLowLightDescription",
"enhanceLowLightParamBrightnessLabel",
"collectionEditedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle",
"enhancePortraitBlurDescription",
"enhancePortraitBlurParamBlurLabel",
"enhanceSuperResolution4xTitle",
"enhanceSuperResolution4xDescription",
"enhanceStyleTransferTitle",
"enhanceStyleTransferStyleDialogTitle",
"enhanceStyleTransferStyleDialogDescription",
"enhanceStyleTransferNoStyleSelectedNotification",
"enhanceColorPopTitle",
"enhanceColorPopDescription",
"enhanceGenericParamWeightLabel",
"enhanceRetouchTitle",
"enhanceRetouchDescription",
"doubleTapExitNotification",
"imageEditDiscardDialogTitle",
"imageEditDiscardDialogContent",
@ -371,11 +385,18 @@
"shareMethodPreviewDescription",
"shareMethodOriginalFileTitle",
"shareMethodOriginalFileDescription",
"enhanceLowLightDescription",
"collectionEditedPhotosLabel",
"enhancePortraitBlurDescription",
"enhanceSuperResolution4xDescription",
"enhanceStyleTransferStyleDialogTitle",
"enhanceStyleTransferStyleDialogDescription",
"enhanceStyleTransferNoStyleSelectedNotification",
"enhanceColorPopTitle",
"enhanceColorPopDescription",
"enhanceGenericParamWeightLabel",
"enhanceRetouchTitle",
"enhanceRetouchDescription",
"doubleTapExitNotification",
"imageEditDiscardDialogTitle",
"imageEditDiscardDialogContent",
@ -439,10 +460,17 @@
"shareMethodPreviewDescription",
"shareMethodOriginalFileTitle",
"shareMethodOriginalFileDescription",
"enhanceLowLightDescription",
"collectionEditedPhotosLabel",
"enhancePortraitBlurDescription",
"enhanceSuperResolution4xDescription",
"enhanceStyleTransferStyleDialogDescription",
"enhanceStyleTransferNoStyleSelectedNotification",
"enhanceColorPopTitle",
"enhanceColorPopDescription",
"enhanceGenericParamWeightLabel",
"enhanceRetouchTitle",
"enhanceRetouchDescription",
"imageEditToolbarColorLabel",
"imageEditToolbarTransformLabel",
"imageEditTransformOrientation",
@ -474,10 +502,17 @@
"shareMethodPreviewDescription",
"shareMethodOriginalFileTitle",
"shareMethodOriginalFileDescription",
"enhanceLowLightDescription",
"collectionEditedPhotosLabel",
"enhancePortraitBlurDescription",
"enhanceSuperResolution4xDescription",
"enhanceStyleTransferStyleDialogDescription",
"enhanceStyleTransferNoStyleSelectedNotification",
"enhanceColorPopTitle",
"enhanceColorPopDescription",
"enhanceGenericParamWeightLabel",
"enhanceRetouchTitle",
"enhanceRetouchDescription",
"imageEditToolbarColorLabel",
"imageEditToolbarTransformLabel",
"imageEditTransformOrientation",
@ -528,17 +563,24 @@
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"enhanceLowLightDescription",
"enhanceLowLightParamBrightnessLabel",
"collectionEditedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle",
"enhancePortraitBlurDescription",
"enhancePortraitBlurParamBlurLabel",
"enhanceSuperResolution4xTitle",
"enhanceSuperResolution4xDescription",
"enhanceStyleTransferTitle",
"enhanceStyleTransferStyleDialogTitle",
"enhanceStyleTransferStyleDialogDescription",
"enhanceStyleTransferNoStyleSelectedNotification",
"enhanceColorPopTitle",
"enhanceColorPopDescription",
"enhanceGenericParamWeightLabel",
"enhanceRetouchTitle",
"enhanceRetouchDescription",
"doubleTapExitNotification",
"imageEditDiscardDialogTitle",
"imageEditDiscardDialogContent",
@ -637,17 +679,24 @@
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"enhanceLowLightDescription",
"enhanceLowLightParamBrightnessLabel",
"collectionEditedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle",
"enhancePortraitBlurDescription",
"enhancePortraitBlurParamBlurLabel",
"enhanceSuperResolution4xTitle",
"enhanceSuperResolution4xDescription",
"enhanceStyleTransferTitle",
"enhanceStyleTransferStyleDialogTitle",
"enhanceStyleTransferStyleDialogDescription",
"enhanceStyleTransferNoStyleSelectedNotification",
"enhanceColorPopTitle",
"enhanceColorPopDescription",
"enhanceGenericParamWeightLabel",
"enhanceRetouchTitle",
"enhanceRetouchDescription",
"doubleTapExitNotification",
"imageEditDiscardDialogTitle",
"imageEditDiscardDialogContent",
@ -725,17 +774,24 @@
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"enhanceLowLightDescription",
"enhanceLowLightParamBrightnessLabel",
"collectionEditedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle",
"enhancePortraitBlurDescription",
"enhancePortraitBlurParamBlurLabel",
"enhanceSuperResolution4xTitle",
"enhanceSuperResolution4xDescription",
"enhanceStyleTransferTitle",
"enhanceStyleTransferStyleDialogTitle",
"enhanceStyleTransferStyleDialogDescription",
"enhanceStyleTransferNoStyleSelectedNotification",
"enhanceColorPopTitle",
"enhanceColorPopDescription",
"enhanceGenericParamWeightLabel",
"enhanceRetouchTitle",
"enhanceRetouchDescription",
"doubleTapExitNotification",
"imageEditDiscardDialogTitle",
"imageEditDiscardDialogContent",
@ -813,17 +869,24 @@
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"enhanceLowLightDescription",
"enhanceLowLightParamBrightnessLabel",
"collectionEditedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle",
"enhancePortraitBlurDescription",
"enhancePortraitBlurParamBlurLabel",
"enhanceSuperResolution4xTitle",
"enhanceSuperResolution4xDescription",
"enhanceStyleTransferTitle",
"enhanceStyleTransferStyleDialogTitle",
"enhanceStyleTransferStyleDialogDescription",
"enhanceStyleTransferNoStyleSelectedNotification",
"enhanceColorPopTitle",
"enhanceColorPopDescription",
"enhanceGenericParamWeightLabel",
"enhanceRetouchTitle",
"enhanceRetouchDescription",
"doubleTapExitNotification",
"imageEditDiscardDialogTitle",
"imageEditDiscardDialogContent",
@ -901,17 +964,24 @@
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"enhanceLowLightDescription",
"enhanceLowLightParamBrightnessLabel",
"collectionEditedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle",
"enhancePortraitBlurDescription",
"enhancePortraitBlurParamBlurLabel",
"enhanceSuperResolution4xTitle",
"enhanceSuperResolution4xDescription",
"enhanceStyleTransferTitle",
"enhanceStyleTransferStyleDialogTitle",
"enhanceStyleTransferStyleDialogDescription",
"enhanceStyleTransferNoStyleSelectedNotification",
"enhanceColorPopTitle",
"enhanceColorPopDescription",
"enhanceGenericParamWeightLabel",
"enhanceRetouchTitle",
"enhanceRetouchDescription",
"doubleTapExitNotification",
"imageEditDiscardDialogTitle",
"imageEditDiscardDialogContent",
@ -989,17 +1059,24 @@
"enhanceIntroDialogTitle",
"enhanceIntroDialogDescription",
"enhanceLowLightTitle",
"enhanceLowLightDescription",
"enhanceLowLightParamBrightnessLabel",
"collectionEditedPhotosLabel",
"deletePermanentlyLocalConfirmationDialogContent",
"enhancePortraitBlurTitle",
"enhancePortraitBlurDescription",
"enhancePortraitBlurParamBlurLabel",
"enhanceSuperResolution4xTitle",
"enhanceSuperResolution4xDescription",
"enhanceStyleTransferTitle",
"enhanceStyleTransferStyleDialogTitle",
"enhanceStyleTransferStyleDialogDescription",
"enhanceStyleTransferNoStyleSelectedNotification",
"enhanceColorPopTitle",
"enhanceColorPopDescription",
"enhanceGenericParamWeightLabel",
"enhanceRetouchTitle",
"enhanceRetouchDescription",
"doubleTapExitNotification",
"imageEditDiscardDialogTitle",
"imageEditDiscardDialogContent",

View file

@ -2,12 +2,15 @@ import 'dart:async';
import 'dart:math' as math;
import 'package:android_intent_plus/android_intent.dart';
import 'package:circular_reveal_animation/circular_reveal_animation.dart';
import 'package:collection/collection.dart';
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/api/api.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/entity/file_util.dart' as file_util;
import 'package:nc_photos/help_utils.dart';
@ -17,7 +20,6 @@ import 'package:nc_photos/mobile/android/content_uri_image_provider.dart';
import 'package:nc_photos/mobile/android/k.dart' as android;
import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/platform/k.dart' as platform_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/url_launcher_util.dart';
@ -28,125 +30,268 @@ import 'package:nc_photos/widget/settings.dart';
import 'package:nc_photos/widget/stateful_slider.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
class EnhanceHandler {
const EnhanceHandler({
class ImageEnhancerArguments {
const ImageEnhancerArguments(this.account, this.file, this.isSaveToServer);
final Account account;
final File file;
final bool isSaveToServer;
}
class ImageEnhancer extends StatefulWidget {
static const routeName = "/image-enhancer";
static Route buildRoute(ImageEnhancerArguments args) => MaterialPageRoute(
builder: (context) => ImageEnhancer.fromArgs(args),
);
static bool isSupportedFormat(File file) =>
file_util.isSupportedImageFormat(file) && file.contentType != "image/gif";
const ImageEnhancer({
super.key,
required this.account,
required this.file,
required this.isSaveToServer,
});
static bool isSupportedFormat(File file) =>
file_util.isSupportedImageFormat(file) && file.contentType != "image/gif";
ImageEnhancer.fromArgs(ImageEnhancerArguments args, {Key? key})
: this(
key: key,
account: args.account,
file: args.file,
isSaveToServer: args.isSaveToServer,
);
Future<void> call(BuildContext context) async {
if (!Pref().hasShownEnhanceInfoOr()) {
await _showInfo(context);
}
if (!Pref().hasShownSaveEditResultDialogOr()) {
await _showSaveEditResultDialog(context);
}
@override
createState() => _ImageEnhancerState();
if (!await const PermissionHandler().ensureStorageWritePermission()) {
return;
}
final Account account;
final File file;
final bool isSaveToServer;
}
final selected = await _pickAlgorithm(context);
if (selected == null) {
// user canceled
return;
}
_log.info("[call] Selected: ${selected.name}");
final args = await _getArgs(context, selected);
class _ImageEnhancerState extends State<ImageEnhancer> {
@override
initState() {
super.initState();
_c = KiwiContainer().resolve<DiContainer>();
WidgetsBinding.instance.addPostFrameCallback((_) {
_showInitialDialogs();
});
}
@override
build(BuildContext context) => AppTheme(
child: Scaffold(
body: Builder(
builder: _buildContent,
),
),
);
Widget _buildContent(BuildContext context) {
return ColoredBox(
color: Colors.black,
child: Column(
children: [
_buildAppBar(context),
Expanded(
child: PageView.builder(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
itemCount: _options.length,
itemBuilder: (context, i) => Padding(
padding: const EdgeInsets.all(48),
child: _options[i].showcaseBuilder(context),
),
),
),
SizedBox(
height: 36,
child: ListView.builder(
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width / 2 - 80),
scrollDirection: Axis.horizontal,
itemCount: _options.length,
itemBuilder: _buildItem,
),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 24),
height: 72,
alignment: AlignmentDirectional.centerStart,
child: Text(_selectedOption.description),
),
],
),
);
}
Widget _buildAppBar(BuildContext context) => AppBar(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
foregroundColor: Colors.white.withOpacity(.87),
title: Text(L10n.global().enhanceTooltip),
actions: [
TextButton(
child: Text(
L10n.global().applyButtonLabel,
style: const TextStyle(
color: Colors.white,
),
),
onPressed: () => _onSavePressed(context),
),
IconButton(
icon: const Icon(Icons.help_outline),
tooltip: L10n.global().helpTooltip,
onPressed: () {
launch(_selectedOption.link);
},
),
],
);
Widget _buildItem(BuildContext context, int index) {
final opt = _options[index];
return _ListChild(
title: opt.title,
isSelected: identical(_selectedOption, opt),
onTap: () {
setState(() {
_selectedOption = opt;
_pageController.animateToPage(
index,
duration: k.animationDurationNormal,
curve: Curves.easeInOut,
);
});
},
);
}
Future<void> _onSavePressed(BuildContext context) async {
final args = await _getArgs(context, _selectedOption.algorithm);
if (args == null) {
// user canceled
return;
}
switch (selected) {
switch (_selectedOption.algorithm) {
case _Algorithm.zeroDce:
await ImageProcessor.zeroDce(
"${account.url}/${file.path}",
file.filename,
Pref().getEnhanceMaxWidthOr(),
Pref().getEnhanceMaxHeightOr(),
"${widget.account.url}/${widget.file.path}",
widget.file.filename,
_c.pref.getEnhanceMaxWidthOr(),
_c.pref.getEnhanceMaxHeightOr(),
args["iteration"] ?? 8,
headers: {
"Authorization": Api.getAuthorizationHeaderValue(account),
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
},
isSaveToServer: isSaveToServer,
isSaveToServer: widget.isSaveToServer,
);
break;
case _Algorithm.deepLab3Portrait:
await ImageProcessor.deepLab3Portrait(
"${account.url}/${file.path}",
file.filename,
Pref().getEnhanceMaxWidthOr(),
Pref().getEnhanceMaxHeightOr(),
"${widget.account.url}/${widget.file.path}",
widget.file.filename,
_c.pref.getEnhanceMaxWidthOr(),
_c.pref.getEnhanceMaxHeightOr(),
args["radius"] ?? 16,
headers: {
"Authorization": Api.getAuthorizationHeaderValue(account),
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
},
isSaveToServer: isSaveToServer,
isSaveToServer: widget.isSaveToServer,
);
break;
case _Algorithm.esrgan:
await ImageProcessor.esrgan(
"${account.url}/${file.path}",
file.filename,
Pref().getEnhanceMaxWidthOr(),
Pref().getEnhanceMaxHeightOr(),
"${widget.account.url}/${widget.file.path}",
widget.file.filename,
_c.pref.getEnhanceMaxWidthOr(),
_c.pref.getEnhanceMaxHeightOr(),
headers: {
"Authorization": Api.getAuthorizationHeaderValue(account),
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
},
isSaveToServer: isSaveToServer,
isSaveToServer: widget.isSaveToServer,
);
break;
case _Algorithm.arbitraryStyleTransfer:
await ImageProcessor.arbitraryStyleTransfer(
"${account.url}/${file.path}",
file.filename,
"${widget.account.url}/${widget.file.path}",
widget.file.filename,
math.min(
Pref().getEnhanceMaxWidthOr(), isAtLeast5GbRam() ? 1600 : 1280),
_c.pref.getEnhanceMaxWidthOr(), _isAtLeast5GbRam() ? 1600 : 1280),
math.min(
Pref().getEnhanceMaxHeightOr(), isAtLeast5GbRam() ? 1200 : 960),
_c.pref.getEnhanceMaxHeightOr(), _isAtLeast5GbRam() ? 1200 : 960),
args["styleUri"],
args["weight"],
headers: {
"Authorization": Api.getAuthorizationHeaderValue(account),
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
},
isSaveToServer: isSaveToServer,
isSaveToServer: widget.isSaveToServer,
);
break;
case _Algorithm.deepLab3ColorPop:
await ImageProcessor.deepLab3ColorPop(
"${account.url}/${file.path}",
file.filename,
Pref().getEnhanceMaxWidthOr(),
Pref().getEnhanceMaxHeightOr(),
"${widget.account.url}/${widget.file.path}",
widget.file.filename,
_c.pref.getEnhanceMaxWidthOr(),
_c.pref.getEnhanceMaxHeightOr(),
args["weight"],
headers: {
"Authorization": Api.getAuthorizationHeaderValue(account),
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
},
isSaveToServer: isSaveToServer,
isSaveToServer: widget.isSaveToServer,
);
break;
case _Algorithm.neurOp:
await ImageProcessor.neurOp(
"${account.url}/${file.path}",
file.filename,
Pref().getEnhanceMaxWidthOr(),
Pref().getEnhanceMaxHeightOr(),
"${widget.account.url}/${widget.file.path}",
widget.file.filename,
_c.pref.getEnhanceMaxWidthOr(),
_c.pref.getEnhanceMaxHeightOr(),
headers: {
"Authorization": Api.getAuthorizationHeaderValue(account),
"Authorization": Api.getAuthorizationHeaderValue(widget.account),
},
isSaveToServer: isSaveToServer,
isSaveToServer: widget.isSaveToServer,
);
break;
}
Navigator.of(context).pop();
}
Future<void> _showInitialDialogs() async {
if (!_c.pref.hasShownEnhanceInfoOr()) {
await _showInfo(context);
}
if (!mounted) {
return;
}
final value = await _ensurePermission();
if (!mounted || !value) {
return;
}
if (!_c.pref.hasShownSaveEditResultDialogOr()) {
await _showSaveEditResultDialog(context);
}
}
Future<bool> _ensurePermission() async {
if (!await const PermissionHandler().ensureStorageWritePermission()) {
if (mounted) {
Navigator.of(context).pop();
}
return false;
} else {
return true;
}
}
Future<void> _showInfo(BuildContext context) async {
@ -177,7 +322,7 @@ class EnhanceHandler {
],
),
);
unawaited(Pref().setHasShownEnhanceInfo(true));
unawaited(_c.pref.setHasShownEnhanceInfo(true));
}
Future<void> _showSaveEditResultDialog(BuildContext context) async {
@ -189,79 +334,6 @@ class EnhanceHandler {
);
}
Future<_Algorithm?> _pickAlgorithm(BuildContext context) =>
showDialog<_Algorithm>(
context: context,
builder: (context) => SimpleDialog(
children: _getOptions()
.map((o) => SimpleDialogOption(
padding: const EdgeInsets.all(0),
child: ListTile(
title: Text(o.title),
subtitle: o.subtitle?.run((t) => Text(t)),
trailing: o.link != null
? SizedBox(
height: double.maxFinite,
child: TextButton(
child: Text(L10n.global().detailsTooltip),
onPressed: () {
launch(o.link!);
},
),
)
: null,
onTap: () {
Navigator.of(context).pop(o.algorithm);
},
),
))
.toList(),
),
);
List<_Option> _getOptions() => [
if (platform_k.isAndroid)
_Option(
title: L10n.global().enhanceRetouchTitle,
link: enhanceRetouchUrl,
algorithm: _Algorithm.neurOp,
),
if (platform_k.isAndroid)
_Option(
title: L10n.global().enhanceColorPopTitle,
subtitle: "DeepLap v3",
link: enhanceDeepLabColorPopUrl,
algorithm: _Algorithm.deepLab3ColorPop,
),
if (platform_k.isAndroid)
_Option(
title: L10n.global().enhanceLowLightTitle,
subtitle: "Zero-DCE",
link: enhanceZeroDceUrl,
algorithm: _Algorithm.zeroDce,
),
if (platform_k.isAndroid)
_Option(
title: L10n.global().enhancePortraitBlurTitle,
subtitle: "DeepLap v3",
link: enhanceDeepLabPortraitBlurUrl,
algorithm: _Algorithm.deepLab3Portrait,
),
if (platform_k.isAndroid)
_Option(
title: L10n.global().enhanceSuperResolution4xTitle,
subtitle: "ESRGAN",
link: enhanceEsrganUrl,
algorithm: _Algorithm.esrgan,
),
if (platform_k.isAndroid && isAtLeast4GbRam())
_Option(
title: L10n.global().enhanceStyleTransferTitle,
link: enhanceStyleTransferUrl,
algorithm: _Algorithm.arbitraryStyleTransfer,
),
];
Future<Map<String, dynamic>?> _getArgs(
BuildContext context, _Algorithm selected) async {
switch (selected) {
@ -458,20 +530,68 @@ class EnhanceHandler {
return weight?.run((it) => {"weight": it});
}
bool isAtLeast4GbRam() {
bool _isAtLeast4GbRam() {
// We can't compare with 4096 directly as some RAM are preserved
return AndroidInfo().totalMemMb > 3584;
}
bool isAtLeast5GbRam() {
bool _isAtLeast5GbRam() {
return AndroidInfo().totalMemMb > 4608;
}
final Account account;
final File file;
final bool isSaveToServer;
late final _options = [
if (platform_k.isAndroid) ...[
_Option(
title: L10n.global().enhanceRetouchTitle,
description: L10n.global().enhanceRetouchDescription,
link: enhanceRetouchUrl,
showcaseBuilder: (_) => const _RetouchShowcase(),
algorithm: _Algorithm.neurOp,
),
_Option(
title: L10n.global().enhanceColorPopTitle,
description: L10n.global().enhanceColorPopDescription,
link: enhanceDeepLabColorPopUrl,
showcaseBuilder: (_) => const _ColorPopShowcase(),
algorithm: _Algorithm.deepLab3ColorPop,
),
_Option(
title: L10n.global().enhanceLowLightTitle,
description: L10n.global().enhanceLowLightDescription,
link: enhanceZeroDceUrl,
showcaseBuilder: (_) => const _LowLightShowcase(),
algorithm: _Algorithm.zeroDce,
),
_Option(
title: L10n.global().enhancePortraitBlurTitle,
description: L10n.global().enhancePortraitBlurDescription,
link: enhanceDeepLabPortraitBlurUrl,
showcaseBuilder: (_) => const _PortraitBlurShowcase(),
algorithm: _Algorithm.deepLab3Portrait,
),
_Option(
title: L10n.global().enhanceSuperResolution4xTitle,
description: L10n.global().enhanceSuperResolution4xDescription,
link: enhanceEsrganUrl,
showcaseBuilder: (_) => const _SuperResolutionShowcase(),
algorithm: _Algorithm.esrgan,
),
if (_isAtLeast4GbRam())
_Option(
title: L10n.global().enhanceStyleTransferTitle,
description: L10n.global().enhanceStyleTransferStyleDialogDescription,
link: enhanceStyleTransferUrl,
showcaseBuilder: (_) => const _StyleTransferShowcase(),
algorithm: _Algorithm.arbitraryStyleTransfer,
),
],
];
static final _log = Logger("widget.handler.enhance_handler.EnhanceHandler");
late final DiContainer _c;
late var _selectedOption = _options[0];
late final _pageController = PageController(keepPage: false);
static final _log = Logger("widget.image_enhancer._ImageEnhancerState");
}
enum _Algorithm {
@ -486,17 +606,270 @@ enum _Algorithm {
class _Option {
const _Option({
required this.title,
this.subtitle,
this.link,
required this.description,
required this.link,
required this.showcaseBuilder,
required this.algorithm,
});
final String title;
final String? subtitle;
final String? link;
final String description;
final String link;
final Widget Function(BuildContext context) showcaseBuilder;
final _Algorithm algorithm;
}
class _ListChild extends StatelessWidget {
const _ListChild({
required this.title,
required this.isSelected,
required this.onTap,
});
@override
build(BuildContext context) {
return ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(24)),
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: onTap,
child: Container(
color: isSelected ? Colors.white24 : null,
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
title,
style: TextStyle(
color:
isSelected ? Colors.white : AppTheme.unfocusedIconColorDark,
),
),
),
),
),
);
}
final String title;
final bool isSelected;
final VoidCallback? onTap;
}
mixin _ShowcaseStateMixin<T extends StatefulWidget>
on State<T>, TickerProviderStateMixin<T> {
@override
initState() {
super.initState();
Future.delayed(const Duration(milliseconds: 250)).then((_) {
if (mounted) {
animController.forward();
}
});
}
@override
dispose() {
animController.dispose();
super.dispose();
}
late final animController = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
late final Animation<double> anim = CurvedAnimation(
parent: animController,
curve: Curves.easeIn,
);
}
class _RetouchShowcase extends StatefulWidget {
const _RetouchShowcase();
@override
createState() => _RetouchShowcaseState();
}
class _RetouchShowcaseState extends State<_RetouchShowcase>
with TickerProviderStateMixin, _ShowcaseStateMixin {
@override
build(BuildContext context) => Stack(
fit: StackFit.expand,
children: [
Image.asset(
"assets/retouch0.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
CircularRevealAnimation(
animation: anim,
centerAlignment: Alignment.bottomCenter,
child: Image.asset(
"assets/retouch1.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
),
],
);
}
class _ColorPopShowcase extends StatefulWidget {
const _ColorPopShowcase();
@override
createState() => _ColorPopShowcaseState();
}
class _ColorPopShowcaseState extends State<_ColorPopShowcase>
with TickerProviderStateMixin, _ShowcaseStateMixin {
@override
build(BuildContext context) => Stack(
fit: StackFit.expand,
children: [
Image.asset(
"assets/color-pop0.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
CircularRevealAnimation(
animation: anim,
centerAlignment: Alignment.bottomCenter,
child: Image.asset(
"assets/color-pop1.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
),
],
);
}
class _LowLightShowcase extends StatefulWidget {
const _LowLightShowcase();
@override
createState() => _LowLightShowcaseState();
}
class _LowLightShowcaseState extends State<_LowLightShowcase>
with TickerProviderStateMixin, _ShowcaseStateMixin {
@override
build(BuildContext context) => Stack(
fit: StackFit.expand,
children: [
Image.asset(
"assets/low-light0.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
CircularRevealAnimation(
animation: anim,
centerAlignment: Alignment.bottomCenter,
child: Image.asset(
"assets/low-light1.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
),
],
);
}
class _PortraitBlurShowcase extends StatefulWidget {
const _PortraitBlurShowcase();
@override
createState() => _PortraitBlurShowcaseState();
}
class _PortraitBlurShowcaseState extends State<_PortraitBlurShowcase>
with TickerProviderStateMixin, _ShowcaseStateMixin {
@override
build(BuildContext context) => Stack(
fit: StackFit.expand,
children: [
Image.asset(
"assets/portrait-blur0.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
CircularRevealAnimation(
animation: anim,
centerAlignment: Alignment.bottomCenter,
child: Image.asset(
"assets/portrait-blur1.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
),
],
);
}
class _SuperResolutionShowcase extends StatefulWidget {
const _SuperResolutionShowcase();
@override
createState() => _SuperResolutionShowcaseState();
}
class _SuperResolutionShowcaseState extends State<_SuperResolutionShowcase>
with TickerProviderStateMixin, _ShowcaseStateMixin {
@override
build(BuildContext context) => Stack(
fit: StackFit.expand,
children: [
Image.asset(
"assets/super-resolution0.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
CircularRevealAnimation(
animation: anim,
centerAlignment: Alignment.bottomCenter,
child: Image.asset(
"assets/super-resolution1.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
),
],
);
}
class _StyleTransferShowcase extends StatefulWidget {
const _StyleTransferShowcase();
@override
createState() => _StyleTransferShowcaseState();
}
class _StyleTransferShowcaseState extends State<_StyleTransferShowcase>
with TickerProviderStateMixin, _ShowcaseStateMixin {
@override
build(BuildContext context) => Stack(
fit: StackFit.expand,
children: [
Image.asset(
"assets/style-transfer0.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
CircularRevealAnimation(
animation: anim,
centerAlignment: Alignment.bottomCenter,
child: Image.asset(
"assets/style-transfer1.jpg",
fit: BoxFit.contain,
gaplessPlayback: true,
),
),
],
);
}
class _StylePickerResult {
const _StylePickerResult(this.styleUri, this.weight);
@ -602,8 +975,9 @@ class _StylePickerState extends State<_StylePicker> {
TextButton(
onPressed: () {
if (_selected == null) {
SnackBarManager().showSnackBar(const SnackBar(
content: Text("Please pick a style"),
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global()
.enhanceStyleTransferNoStyleSelectedNotification),
duration: k.snackBarDurationNormal,
));
} else {
@ -676,6 +1050,5 @@ class _StylePickerState extends State<_StylePicker> {
"file:///android_asset/tf/arbitrary-style-transfer/6.jpg",
];
static final _log =
Logger("widget.handler.enhance_handler._StylePickerState");
static final _log = Logger("widget.image_enhancer._StylePickerState");
}

View file

@ -20,6 +20,7 @@ import 'package:nc_photos/widget/dynamic_album_browser.dart';
import 'package:nc_photos/widget/enhanced_photo_browser.dart';
import 'package:nc_photos/widget/home.dart';
import 'package:nc_photos/widget/image_editor.dart';
import 'package:nc_photos/widget/image_enhancer.dart';
import 'package:nc_photos/widget/local_file_viewer.dart';
import 'package:nc_photos/widget/people_browser.dart';
import 'package:nc_photos/widget/person_browser.dart';
@ -173,6 +174,7 @@ class _MyAppState extends State<MyApp>
route ??= _handlePlaceBrowserRoute(settings);
route ??= _handlePlacesBrowserRoute(settings);
route ??= _handleResultViewerRoute(settings);
route ??= _handleImageEnhancerRoute(settings);
return route;
}
@ -621,6 +623,19 @@ class _MyAppState extends State<MyApp>
return null;
}
Route<dynamic>? _handleImageEnhancerRoute(RouteSettings settings) {
try {
if (settings.name == ImageEnhancer.routeName &&
settings.arguments != null) {
final args = settings.arguments as ImageEnhancerArguments;
return ImageEnhancer.buildRoute(args);
}
} catch (e) {
_log.severe("[_handleImageEnhancerRoute] Failed while handling route", e);
}
return null;
}
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
final _navigatorKey = GlobalKey<NavigatorState>();

View file

@ -24,10 +24,10 @@ import 'package:nc_photos/theme.dart';
import 'package:nc_photos/use_case/update_property.dart';
import 'package:nc_photos/widget/animated_visibility.dart';
import 'package:nc_photos/widget/disposable.dart';
import 'package:nc_photos/widget/handler/enhance_handler.dart';
import 'package:nc_photos/widget/handler/remove_selection_handler.dart';
import 'package:nc_photos/widget/horizontal_page_viewer.dart';
import 'package:nc_photos/widget/image_editor.dart';
import 'package:nc_photos/widget/image_enhancer.dart';
import 'package:nc_photos/widget/image_viewer.dart';
import 'package:nc_photos/widget/slideshow_dialog.dart';
import 'package:nc_photos/widget/slideshow_viewer.dart';
@ -217,7 +217,7 @@ class _ViewerState extends State<Viewer>
onPressed: () => _onSharePressed(context),
),
if (features.isSupportEnhancement &&
EnhanceHandler.isSupportedFormat(file)) ...[
ImageEnhancer.isSupportedFormat(file)) ...[
IconButton(
icon: Icon(
Icons.tune_outlined,
@ -605,11 +605,9 @@ class _ViewerState extends State<Viewer>
final c = KiwiContainer().resolve<DiContainer>();
_log.info("[_onEnhancePressed] Enhance file: ${file.path}");
EnhanceHandler(
account: widget.account,
file: file,
isSaveToServer: c.pref.isSaveEditResultToServerOr(),
)(context);
Navigator.of(context).pushNamed(ImageEnhancer.routeName,
arguments: ImageEnhancerArguments(
widget.account, file, c.pref.isSaveEditResultToServerOr()));
}
void _onDownloadPressed() {

View file

@ -213,6 +213,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
circular_reveal_animation:
dependency: "direct main"
description:
name: circular_reveal_animation
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
cli_util:
dependency: transitive
description:

View file

@ -37,6 +37,7 @@ dependencies:
bloc: ^8.0.0
bloc_concurrency: ^0.2.0
cached_network_image: ^3.2.1
circular_reveal_animation: ^2.0.1
collection: ^1.15.0
connectivity_plus: ^2.0.2
devicelocale: ^0.5.0