mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-22 06:59:21 +01:00
Set album cover manually
This commit is contained in:
parent
7ae54b9d7e
commit
f7d8a2533a
7 changed files with 168 additions and 6 deletions
|
@ -17,6 +17,9 @@ abstract class AlbumCoverProvider with EquatableMixin {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case AlbumAutoCoverProvider._type:
|
case AlbumAutoCoverProvider._type:
|
||||||
return AlbumAutoCoverProvider.fromJson(content.cast<String, dynamic>());
|
return AlbumAutoCoverProvider.fromJson(content.cast<String, dynamic>());
|
||||||
|
case AlbumManualCoverProvider._type:
|
||||||
|
return AlbumManualCoverProvider.fromJson(
|
||||||
|
content.cast<String, dynamic>());
|
||||||
default:
|
default:
|
||||||
_log.shout("[fromJson] Unknown type: $type");
|
_log.shout("[fromJson] Unknown type: $type");
|
||||||
throw ArgumentError.value(type, "type");
|
throw ArgumentError.value(type, "type");
|
||||||
|
@ -27,6 +30,8 @@ abstract class AlbumCoverProvider with EquatableMixin {
|
||||||
String getType() {
|
String getType() {
|
||||||
if (this is AlbumAutoCoverProvider) {
|
if (this is AlbumAutoCoverProvider) {
|
||||||
return AlbumAutoCoverProvider._type;
|
return AlbumAutoCoverProvider._type;
|
||||||
|
} else if (this is AlbumManualCoverProvider) {
|
||||||
|
return AlbumManualCoverProvider._type;
|
||||||
} else {
|
} else {
|
||||||
throw StateError("Unknwon subtype");
|
throw StateError("Unknwon subtype");
|
||||||
}
|
}
|
||||||
|
@ -107,3 +112,42 @@ class AlbumAutoCoverProvider extends AlbumCoverProvider {
|
||||||
|
|
||||||
static const _type = "auto";
|
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";
|
||||||
|
}
|
||||||
|
|
|
@ -631,6 +631,18 @@
|
||||||
"@albumSharedLabel": {
|
"@albumSharedLabel": {
|
||||||
"description": "A small label placed next to a shared album"
|
"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": "Changelog",
|
||||||
"@changelogTitle": {
|
"@changelogTitle": {
|
||||||
|
|
|
@ -12,11 +12,17 @@
|
||||||
"deletePermanentlyTooltip",
|
"deletePermanentlyTooltip",
|
||||||
"deletePermanentlyConfirmationDialogTitle",
|
"deletePermanentlyConfirmationDialogTitle",
|
||||||
"deletePermanentlyConfirmationDialogContent",
|
"deletePermanentlyConfirmationDialogContent",
|
||||||
"albumSharedLabel"
|
"albumSharedLabel",
|
||||||
|
"setAlbumCoverProcessingNotification",
|
||||||
|
"setAlbumCoverSuccessNotification",
|
||||||
|
"setAlbumCoverFailureNotification"
|
||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
"albumSharedLabel"
|
"albumSharedLabel",
|
||||||
|
"setAlbumCoverProcessingNotification",
|
||||||
|
"setAlbumCoverSuccessNotification",
|
||||||
|
"setAlbumCoverFailureNotification"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
|
@ -32,6 +38,9 @@
|
||||||
"deletePermanentlyTooltip",
|
"deletePermanentlyTooltip",
|
||||||
"deletePermanentlyConfirmationDialogTitle",
|
"deletePermanentlyConfirmationDialogTitle",
|
||||||
"deletePermanentlyConfirmationDialogContent",
|
"deletePermanentlyConfirmationDialogContent",
|
||||||
"albumSharedLabel"
|
"albumSharedLabel",
|
||||||
|
"setAlbumCoverProcessingNotification",
|
||||||
|
"setAlbumCoverSuccessNotification",
|
||||||
|
"setAlbumCoverFailureNotification"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
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/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
@ -75,6 +76,16 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
initState() {
|
initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initAlbum();
|
_initAlbum();
|
||||||
|
|
||||||
|
_albumUpdatedListener =
|
||||||
|
AppEventListener<AlbumUpdatedEvent>(_onAlbumUpdatedEvent);
|
||||||
|
_albumUpdatedListener.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dispose() {
|
||||||
|
super.dispose();
|
||||||
|
_albumUpdatedListener.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -273,7 +284,8 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Navigator.pushNamed(context, Viewer.routeName,
|
Navigator.pushNamed(context, Viewer.routeName,
|
||||||
arguments: ViewerArguments(widget.account, _backingFiles, fileIndex));
|
arguments: ViewerArguments(widget.account, _backingFiles, fileIndex,
|
||||||
|
album: widget.album));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionAppBarSharePressed(BuildContext context) {
|
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() {
|
void _transformItems() {
|
||||||
if (_editAlbum != null) {
|
if (_editAlbum != null) {
|
||||||
// edit mode
|
// edit mode
|
||||||
|
@ -662,6 +683,8 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
final _editFormKey = GlobalKey<FormState>();
|
final _editFormKey = GlobalKey<FormState>();
|
||||||
Album? _editAlbum;
|
Album? _editAlbum;
|
||||||
|
|
||||||
|
late AppEventListener<AlbumUpdatedEvent> _albumUpdatedListener;
|
||||||
|
|
||||||
static final _log = Logger("widget.album_browser._AlbumBrowserState");
|
static final _log = Logger("widget.album_browser._AlbumBrowserState");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.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/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/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
@ -76,6 +77,16 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
initState() {
|
initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initAlbum();
|
_initAlbum();
|
||||||
|
|
||||||
|
_albumUpdatedListener =
|
||||||
|
AppEventListener<AlbumUpdatedEvent>(_onAlbumUpdatedEvent);
|
||||||
|
_albumUpdatedListener.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dispose() {
|
||||||
|
super.dispose();
|
||||||
|
_albumUpdatedListener.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -318,7 +329,8 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Navigator.pushNamed(context, Viewer.routeName,
|
Navigator.pushNamed(context, Viewer.routeName,
|
||||||
arguments: ViewerArguments(widget.account, _backingFiles, fileIndex));
|
arguments: ViewerArguments(widget.account, _backingFiles, fileIndex,
|
||||||
|
album: widget.album));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAppBarConvertBasicPressed(BuildContext context) {
|
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) {
|
void _transformItems(List<AlbumItem> items) {
|
||||||
if (_editAlbum != null) {
|
if (_editAlbum != null) {
|
||||||
// edit mode
|
// edit mode
|
||||||
|
@ -547,6 +568,8 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
final _editFormKey = GlobalKey<FormState>();
|
final _editFormKey = GlobalKey<FormState>();
|
||||||
Album? _editAlbum;
|
Album? _editAlbum;
|
||||||
|
|
||||||
|
late AppEventListener<AlbumUpdatedEvent> _albumUpdatedListener;
|
||||||
|
|
||||||
static final _log =
|
static final _log =
|
||||||
Logger("widget.dynamic_album_browser._DynamicAlbumBrowserState");
|
Logger("widget.dynamic_album_browser._DynamicAlbumBrowserState");
|
||||||
static const _menuValueConvertBasic = 0;
|
static const _menuValueConvertBasic = 0;
|
||||||
|
|
|
@ -30,11 +30,17 @@ import 'package:nc_photos/widget/viewer_bottom_app_bar.dart';
|
||||||
import 'package:nc_photos/widget/viewer_detail_pane.dart';
|
import 'package:nc_photos/widget/viewer_detail_pane.dart';
|
||||||
|
|
||||||
class ViewerArguments {
|
class ViewerArguments {
|
||||||
ViewerArguments(this.account, this.streamFiles, this.startIndex);
|
ViewerArguments(
|
||||||
|
this.account,
|
||||||
|
this.streamFiles,
|
||||||
|
this.startIndex, {
|
||||||
|
this.album,
|
||||||
|
});
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final List<File> streamFiles;
|
final List<File> streamFiles;
|
||||||
final int startIndex;
|
final int startIndex;
|
||||||
|
final Album? album;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Viewer extends StatefulWidget {
|
class Viewer extends StatefulWidget {
|
||||||
|
@ -49,6 +55,7 @@ class Viewer extends StatefulWidget {
|
||||||
required this.account,
|
required this.account,
|
||||||
required this.streamFiles,
|
required this.streamFiles,
|
||||||
required this.startIndex,
|
required this.startIndex,
|
||||||
|
this.album,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
Viewer.fromArgs(ViewerArguments args, {Key? key})
|
Viewer.fromArgs(ViewerArguments args, {Key? key})
|
||||||
|
@ -57,6 +64,7 @@ class Viewer extends StatefulWidget {
|
||||||
account: args.account,
|
account: args.account,
|
||||||
streamFiles: args.streamFiles,
|
streamFiles: args.streamFiles,
|
||||||
startIndex: args.startIndex,
|
startIndex: args.startIndex,
|
||||||
|
album: args.album,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -65,6 +73,9 @@ class Viewer extends StatefulWidget {
|
||||||
final Account account;
|
final Account account;
|
||||||
final List<File> streamFiles;
|
final List<File> streamFiles;
|
||||||
final int startIndex;
|
final int startIndex;
|
||||||
|
|
||||||
|
/// The album these files belongs to, or null
|
||||||
|
final Album? album;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ViewerState extends State<Viewer> {
|
class _ViewerState extends State<Viewer> {
|
||||||
|
@ -260,6 +271,7 @@ class _ViewerState extends State<Viewer> {
|
||||||
child: ViewerDetailPane(
|
child: ViewerDetailPane(
|
||||||
account: widget.account,
|
account: widget.account,
|
||||||
file: widget.streamFiles[index],
|
file: widget.streamFiles[index],
|
||||||
|
album: widget.album,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/double_extension.dart';
|
import 'package:nc_photos/double_extension.dart';
|
||||||
import 'package:nc_photos/entity/album.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/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
@ -36,6 +37,7 @@ class ViewerDetailPane extends StatefulWidget {
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.account,
|
required this.account,
|
||||||
required this.file,
|
required this.file,
|
||||||
|
this.album,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -43,6 +45,9 @@ class ViewerDetailPane extends StatefulWidget {
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final File file;
|
final File file;
|
||||||
|
|
||||||
|
/// The album this file belongs to, or null
|
||||||
|
final Album? album;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
|
@ -110,6 +115,12 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
if (widget.album != null)
|
||||||
|
_DetailPaneButton(
|
||||||
|
icon: Icons.photo_album_outlined,
|
||||||
|
label: "Use as album cover",
|
||||||
|
onPressed: () => _onSetAlbumCoverPressed(context),
|
||||||
|
),
|
||||||
_DetailPaneButton(
|
_DetailPaneButton(
|
||||||
icon: Icons.playlist_add_outlined,
|
icon: Icons.playlist_add_outlined,
|
||||||
label: L10n.of(context).addToAlbumTooltip,
|
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) {
|
void _onAddToAlbumPressed(BuildContext context) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
Loading…
Reference in a new issue