Set album cover manually

This commit is contained in:
Ming Ming 2021-08-14 04:18:35 +08:00
parent 7ae54b9d7e
commit f7d8a2533a
7 changed files with 168 additions and 6 deletions

View file

@ -17,6 +17,9 @@ abstract class AlbumCoverProvider with EquatableMixin {
switch (type) {
case AlbumAutoCoverProvider._type:
return AlbumAutoCoverProvider.fromJson(content.cast<String, dynamic>());
case AlbumManualCoverProvider._type:
return AlbumManualCoverProvider.fromJson(
content.cast<String, dynamic>());
default:
_log.shout("[fromJson] Unknown type: $type");
throw ArgumentError.value(type, "type");
@ -27,6 +30,8 @@ abstract class AlbumCoverProvider with EquatableMixin {
String getType() {
if (this is AlbumAutoCoverProvider) {
return AlbumAutoCoverProvider._type;
} else if (this is AlbumManualCoverProvider) {
return AlbumManualCoverProvider._type;
} else {
throw StateError("Unknwon subtype");
}
@ -107,3 +112,42 @@ class AlbumAutoCoverProvider extends AlbumCoverProvider {
static const _type = "auto";
}
/// Cover picked by user
class AlbumManualCoverProvider extends AlbumCoverProvider {
AlbumManualCoverProvider({
required this.coverFile,
});
factory AlbumManualCoverProvider.fromJson(JsonObj json) {
return AlbumManualCoverProvider(
coverFile: File.fromJson(json["coverFile"].cast<String, dynamic>()),
);
}
@override
toString() {
return "$runtimeType {"
"coverFile: '${coverFile.path}', "
"}";
}
@override
getCover(Album album) => coverFile;
@override
get props => [
coverFile,
];
@override
_toContentJson() {
return {
"coverFile": coverFile.toJson(),
};
}
final File coverFile;
static const _type = "manual";
}

View file

@ -631,6 +631,18 @@
"@albumSharedLabel": {
"description": "A small label placed next to a shared album"
},
"setAlbumCoverProcessingNotification": "Setting photo as album cover",
"@setAlbumCoverProcessingNotification": {
"description": "Setting the opened item as the album cover"
},
"setAlbumCoverSuccessNotification": "Set album cover successfully",
"@setAlbumCoverSuccessNotification": {
"description": "Set the opened item as the album cover successfully"
},
"setAlbumCoverFailureNotification": "Failed setting album cover",
"@setAlbumCoverFailureNotification": {
"description": "Cannot set the opened item as the album cover"
},
"changelogTitle": "Changelog",
"@changelogTitle": {

View file

@ -12,11 +12,17 @@
"deletePermanentlyTooltip",
"deletePermanentlyConfirmationDialogTitle",
"deletePermanentlyConfirmationDialogContent",
"albumSharedLabel"
"albumSharedLabel",
"setAlbumCoverProcessingNotification",
"setAlbumCoverSuccessNotification",
"setAlbumCoverFailureNotification"
],
"es": [
"albumSharedLabel"
"albumSharedLabel",
"setAlbumCoverProcessingNotification",
"setAlbumCoverSuccessNotification",
"setAlbumCoverFailureNotification"
],
"fr": [
@ -32,6 +38,9 @@
"deletePermanentlyTooltip",
"deletePermanentlyConfirmationDialogTitle",
"deletePermanentlyConfirmationDialogContent",
"albumSharedLabel"
"albumSharedLabel",
"setAlbumCoverProcessingNotification",
"setAlbumCoverSuccessNotification",
"setAlbumCoverFailureNotification"
]
}

View file

@ -12,6 +12,7 @@ import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/entity/album/sort_provider.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/exception_util.dart' as exception_util;
import 'package:nc_photos/iterable_extension.dart';
import 'package:nc_photos/k.dart' as k;
@ -75,6 +76,16 @@ class _AlbumBrowserState extends State<AlbumBrowser>
initState() {
super.initState();
_initAlbum();
_albumUpdatedListener =
AppEventListener<AlbumUpdatedEvent>(_onAlbumUpdatedEvent);
_albumUpdatedListener.begin();
}
@override
dispose() {
super.dispose();
_albumUpdatedListener.end();
}
@override
@ -273,7 +284,8 @@ class _AlbumBrowserState extends State<AlbumBrowser>
}
}
Navigator.pushNamed(context, Viewer.routeName,
arguments: ViewerArguments(widget.account, _backingFiles, fileIndex));
arguments: ViewerArguments(widget.account, _backingFiles, fileIndex,
album: widget.album));
}
void _onSelectionAppBarSharePressed(BuildContext context) {
@ -492,6 +504,15 @@ class _AlbumBrowserState extends State<AlbumBrowser>
});
}
void _onAlbumUpdatedEvent(AlbumUpdatedEvent ev) {
if (ev.album.albumFile!.path == _album?.albumFile?.path) {
setState(() {
_album = ev.album;
initCover(widget.account, ev.album);
});
}
}
void _transformItems() {
if (_editAlbum != null) {
// edit mode
@ -662,6 +683,8 @@ class _AlbumBrowserState extends State<AlbumBrowser>
final _editFormKey = GlobalKey<FormState>();
Album? _editAlbum;
late AppEventListener<AlbumUpdatedEvent> _albumUpdatedListener;
static final _log = Logger("widget.album_browser._AlbumBrowserState");
}

View file

@ -14,6 +14,7 @@ import 'package:nc_photos/entity/album/sort_provider.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file/data_source.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/event/event.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/iterable_extension.dart';
import 'package:nc_photos/k.dart' as k;
@ -76,6 +77,16 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
initState() {
super.initState();
_initAlbum();
_albumUpdatedListener =
AppEventListener<AlbumUpdatedEvent>(_onAlbumUpdatedEvent);
_albumUpdatedListener.begin();
}
@override
dispose() {
super.dispose();
_albumUpdatedListener.end();
}
@override
@ -318,7 +329,8 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
}
}
Navigator.pushNamed(context, Viewer.routeName,
arguments: ViewerArguments(widget.account, _backingFiles, fileIndex));
arguments: ViewerArguments(widget.account, _backingFiles, fileIndex,
album: widget.album));
}
void _onAppBarConvertBasicPressed(BuildContext context) {
@ -491,6 +503,15 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
});
}
void _onAlbumUpdatedEvent(AlbumUpdatedEvent ev) {
if (ev.album.albumFile!.path == _album?.albumFile?.path) {
setState(() {
_album = ev.album;
initCover(widget.account, ev.album);
});
}
}
void _transformItems(List<AlbumItem> items) {
if (_editAlbum != null) {
// edit mode
@ -547,6 +568,8 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
final _editFormKey = GlobalKey<FormState>();
Album? _editAlbum;
late AppEventListener<AlbumUpdatedEvent> _albumUpdatedListener;
static final _log =
Logger("widget.dynamic_album_browser._DynamicAlbumBrowserState");
static const _menuValueConvertBasic = 0;

View file

@ -30,11 +30,17 @@ import 'package:nc_photos/widget/viewer_bottom_app_bar.dart';
import 'package:nc_photos/widget/viewer_detail_pane.dart';
class ViewerArguments {
ViewerArguments(this.account, this.streamFiles, this.startIndex);
ViewerArguments(
this.account,
this.streamFiles,
this.startIndex, {
this.album,
});
final Account account;
final List<File> streamFiles;
final int startIndex;
final Album? album;
}
class Viewer extends StatefulWidget {
@ -49,6 +55,7 @@ class Viewer extends StatefulWidget {
required this.account,
required this.streamFiles,
required this.startIndex,
this.album,
}) : super(key: key);
Viewer.fromArgs(ViewerArguments args, {Key? key})
@ -57,6 +64,7 @@ class Viewer extends StatefulWidget {
account: args.account,
streamFiles: args.streamFiles,
startIndex: args.startIndex,
album: args.album,
);
@override
@ -65,6 +73,9 @@ class Viewer extends StatefulWidget {
final Account account;
final List<File> streamFiles;
final int startIndex;
/// The album these files belongs to, or null
final Album? album;
}
class _ViewerState extends State<Viewer> {
@ -260,6 +271,7 @@ class _ViewerState extends State<Viewer> {
child: ViewerDetailPane(
account: widget.account,
file: widget.streamFiles[index],
album: widget.album,
),
),
),

View file

@ -11,6 +11,7 @@ import 'package:nc_photos/account.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/double_extension.dart';
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/album/cover_provider.dart';
import 'package:nc_photos/entity/album/item.dart';
import 'package:nc_photos/entity/album/provider.dart';
import 'package:nc_photos/entity/file.dart';
@ -36,6 +37,7 @@ class ViewerDetailPane extends StatefulWidget {
Key? key,
required this.account,
required this.file,
this.album,
}) : super(key: key);
@override
@ -43,6 +45,9 @@ class ViewerDetailPane extends StatefulWidget {
final Account account;
final File file;
/// The album this file belongs to, or null
final Album? album;
}
class _ViewerDetailPaneState extends State<ViewerDetailPane> {
@ -110,6 +115,12 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
children: [
Row(
children: [
if (widget.album != null)
_DetailPaneButton(
icon: Icons.photo_album_outlined,
label: "Use as album cover",
onPressed: () => _onSetAlbumCoverPressed(context),
),
_DetailPaneButton(
icon: Icons.playlist_add_outlined,
label: L10n.of(context).addToAlbumTooltip,
@ -232,6 +243,34 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
}
}
Future<void> _onSetAlbumCoverPressed(BuildContext context) async {
assert(widget.album != null);
_log.info(
"[_onSetAlbumCoverPressed] Set '${widget.file.path}' as album cover for '${widget.album!.name}'");
await _onAction(
context,
L10n.of(context).setAlbumCoverProcessingNotification,
L10n.of(context).setAlbumCoverSuccessNotification,
() async {
final albumRepo = AlbumRepo(AlbumCachedDataSource());
try {
await UpdateAlbum(albumRepo).call(
widget.account,
widget.album!.copyWith(
coverProvider: AlbumManualCoverProvider(
coverFile: widget.file,
),
));
} catch (e, stackTrace) {
_log.shout("[_onSetAlbumCoverPressed] Failed while updating album", e,
stackTrace);
rethrow;
}
},
failureText: L10n.of(context).setAlbumCoverFailureNotification,
);
}
void _onAddToAlbumPressed(BuildContext context) {
showDialog(
context: context,