From 53b51b77b14b509b7d416b930ee3f43ed8e9fa96 Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Sat, 29 Apr 2023 01:21:53 +0800 Subject: [PATCH] Migrate Memory to use collection browser --- app/lib/entity/album/cover_provider.dart | 38 -- app/lib/entity/album/cover_provider.g.dart | 7 - app/lib/entity/album/provider.dart | 25 -- app/lib/entity/album/provider.g.dart | 7 - app/lib/entity/collection.dart | 31 +- app/lib/entity/collection/adapter.dart | 4 + app/lib/entity/collection/adapter/memory.dart | 59 +++ .../collection/content_provider/album.dart | 16 +- .../content_provider/location_group.dart | 16 +- .../collection/content_provider/memory.dart | 68 +++ .../collection/content_provider/memory.g.dart | 14 + .../collection/content_provider/nc_album.dart | 16 +- .../collection/content_provider/person.dart | 14 +- .../collection/content_provider/tag.dart | 15 +- app/lib/entity/nc_album.dart | 14 +- app/lib/use_case/populate_album.dart | 24 -- app/lib/widget/album_browser_util.dart | 4 - .../builder/photo_list_item_builder.dart | 10 +- app/lib/widget/collection_browser/bloc.dart | 24 +- app/lib/widget/home_photos.dart | 44 +- app/lib/widget/my_app.dart | 16 - app/lib/widget/photo_list_util.dart | 41 +- app/lib/widget/photo_list_util.g.dart | 4 +- app/lib/widget/smart_album_browser.dart | 403 ------------------ app/lib/widget/smart_album_browser.g.dart | 26 -- app/test/widget/photo_list_util_test.dart | 192 ++++----- 26 files changed, 392 insertions(+), 740 deletions(-) create mode 100644 app/lib/entity/collection/adapter/memory.dart create mode 100644 app/lib/entity/collection/content_provider/memory.dart create mode 100644 app/lib/entity/collection/content_provider/memory.g.dart delete mode 100644 app/lib/widget/smart_album_browser.dart delete mode 100644 app/lib/widget/smart_album_browser.g.dart diff --git a/app/lib/entity/album/cover_provider.dart b/app/lib/entity/album/cover_provider.dart index fd660278..20a718df 100644 --- a/app/lib/entity/album/cover_provider.dart +++ b/app/lib/entity/album/cover_provider.dart @@ -26,9 +26,6 @@ abstract class AlbumCoverProvider with EquatableMixin { case AlbumManualCoverProvider._type: return AlbumManualCoverProvider.fromJson( content.cast()); - case AlbumMemoryCoverProvider._type: - return AlbumMemoryCoverProvider.fromJson( - content.cast()); default: _log.shout("[fromJson] Unknown type: $type"); throw ArgumentError.value(type, "type"); @@ -154,38 +151,3 @@ class AlbumManualCoverProvider extends AlbumCoverProvider { static const _type = "manual"; } - -/// Cover selected when building a Memory album -@toString -class AlbumMemoryCoverProvider extends AlbumCoverProvider { - AlbumMemoryCoverProvider({ - required this.coverFile, - }); - - factory AlbumMemoryCoverProvider.fromJson(JsonObj json) { - return AlbumMemoryCoverProvider( - coverFile: - FileDescriptor.fromJson(json["coverFile"].cast()), - ); - } - - @override - String toString() => _$toString(); - - @override - getCover(Album album) => coverFile; - - @override - get props => [ - coverFile, - ]; - - @override - _toContentJson() => { - "coverFile": coverFile.toFdJson(), - }; - - final FileDescriptor coverFile; - - static const _type = "memory"; -} diff --git a/app/lib/entity/album/cover_provider.g.dart b/app/lib/entity/album/cover_provider.g.dart index d958ec48..0521f48f 100644 --- a/app/lib/entity/album/cover_provider.g.dart +++ b/app/lib/entity/album/cover_provider.g.dart @@ -30,10 +30,3 @@ extension _$AlbumManualCoverProviderToString on AlbumManualCoverProvider { return "AlbumManualCoverProvider {coverFile: ${coverFile.fdPath}}"; } } - -extension _$AlbumMemoryCoverProviderToString on AlbumMemoryCoverProvider { - String _$toString() { - // ignore: unnecessary_string_interpolations - return "AlbumMemoryCoverProvider {coverFile: ${coverFile.fdPath}}"; - } -} diff --git a/app/lib/entity/album/provider.dart b/app/lib/entity/album/provider.dart index 18ab269b..1d47a5d0 100644 --- a/app/lib/entity/album/provider.dart +++ b/app/lib/entity/album/provider.dart @@ -284,28 +284,3 @@ abstract class AlbumSmartProvider extends AlbumProviderBase { throw UnimplementedError(); } } - -/// Memory album is created based on dates -@ToString(extraParams: r"{bool isDeep = false}") -class AlbumMemoryProvider extends AlbumSmartProvider { - AlbumMemoryProvider({ - required this.year, - required this.month, - required this.day, - }) : super(latestItemTime: DateTime(year, month, day)); - - @override - String toString({bool isDeep = false}) => _$toString(isDeep: isDeep); - - @override - get props => [ - ...super.props, - year, - month, - day, - ]; - - final int year; - final int month; - final int day; -} diff --git a/app/lib/entity/album/provider.g.dart b/app/lib/entity/album/provider.g.dart index 3c4a0f2d..1fe71e9b 100644 --- a/app/lib/entity/album/provider.g.dart +++ b/app/lib/entity/album/provider.g.dart @@ -37,10 +37,3 @@ extension _$AlbumTagProviderToString on AlbumTagProvider { return "AlbumTagProvider {latestItemTime: $latestItemTime, tags: ${tags.map((t) => t.displayName).toReadableString()}}"; } } - -extension _$AlbumMemoryProviderToString on AlbumMemoryProvider { - String _$toString({bool isDeep = false}) { - // ignore: unnecessary_string_interpolations - return "AlbumMemoryProvider {latestItemTime: $latestItemTime, year: $year, month: $month, day: $day}"; - } -} diff --git a/app/lib/entity/collection.dart b/app/lib/entity/collection.dart index ddec018e..a9bdca0f 100644 --- a/app/lib/entity/collection.dart +++ b/app/lib/entity/collection.dart @@ -1,4 +1,5 @@ import 'package:copy_with/copy_with.dart'; +import 'package:equatable/equatable.dart'; import 'package:nc_photos/entity/collection_item/sorter.dart'; import 'package:nc_photos/entity/collection_item/util.dart'; import 'package:to_string/to_string.dart'; @@ -8,7 +9,7 @@ part 'collection.g.dart'; /// Describe a group of items @genCopyWith @toString -class Collection { +class Collection with EquatableMixin { const Collection({ required this.name, required this.contentProvider, @@ -40,11 +41,25 @@ class Collection { CollectionItemSort get itemSort => contentProvider.itemSort; /// See [CollectionContentProvider.getCoverUrl] - String? getCoverUrl(int width, int height) => - contentProvider.getCoverUrl(width, height); + String? getCoverUrl( + int width, + int height, { + bool? isKeepAspectRatio, + }) => + contentProvider.getCoverUrl( + width, + height, + isKeepAspectRatio: isKeepAspectRatio, + ); CollectionSorter getSorter() => CollectionSorter.fromSortType(itemSort); + @override + List get props => [ + name, + contentProvider, + ]; + final String name; final CollectionContentProvider contentProvider; } @@ -65,7 +80,7 @@ enum CollectionCapability { } /// Provide the actual content of a collection -abstract class CollectionContentProvider { +abstract class CollectionContentProvider with EquatableMixin { const CollectionContentProvider(); /// Unique FourCC of this provider type @@ -95,5 +110,11 @@ abstract class CollectionContentProvider { /// /// The [width] and [height] are provided as a hint only, implementations are /// free to ignore them if it's not supported - String? getCoverUrl(int width, int height); + /// + /// [isKeepAspectRatio] is only a hint and implementations may ignore it + String? getCoverUrl( + int width, + int height, { + bool? isKeepAspectRatio, + }); } diff --git a/app/lib/entity/collection/adapter.dart b/app/lib/entity/collection/adapter.dart index 87c89d70..db703281 100644 --- a/app/lib/entity/collection/adapter.dart +++ b/app/lib/entity/collection/adapter.dart @@ -4,11 +4,13 @@ import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/collection.dart'; import 'package:nc_photos/entity/collection/adapter/album.dart'; import 'package:nc_photos/entity/collection/adapter/location_group.dart'; +import 'package:nc_photos/entity/collection/adapter/memory.dart'; import 'package:nc_photos/entity/collection/adapter/nc_album.dart'; import 'package:nc_photos/entity/collection/adapter/person.dart'; import 'package:nc_photos/entity/collection/adapter/tag.dart'; import 'package:nc_photos/entity/collection/content_provider/album.dart'; import 'package:nc_photos/entity/collection/content_provider/location_group.dart'; +import 'package:nc_photos/entity/collection/content_provider/memory.dart'; import 'package:nc_photos/entity/collection/content_provider/nc_album.dart'; import 'package:nc_photos/entity/collection/content_provider/person.dart'; import 'package:nc_photos/entity/collection/content_provider/tag.dart'; @@ -29,6 +31,8 @@ abstract class CollectionAdapter { return CollectionAlbumAdapter(c, account, collection); case CollectionLocationGroupProvider: return CollectionLocationGroupAdapter(c, account, collection); + case CollectionMemoryProvider: + return CollectionMemoryAdapter(c, account, collection); case CollectionNcAlbumProvider: return CollectionNcAlbumAdapter(c, account, collection); case CollectionPersonProvider: diff --git a/app/lib/entity/collection/adapter/memory.dart b/app/lib/entity/collection/adapter/memory.dart new file mode 100644 index 00000000..53a16a4d --- /dev/null +++ b/app/lib/entity/collection/adapter/memory.dart @@ -0,0 +1,59 @@ +import 'package:nc_photos/account.dart'; +import 'package:nc_photos/di_container.dart'; +import 'package:nc_photos/entity/collection.dart'; +import 'package:nc_photos/entity/collection/adapter.dart'; +import 'package:nc_photos/entity/collection/adapter/read_only_adapter.dart'; +import 'package:nc_photos/entity/collection/content_provider/memory.dart'; +import 'package:nc_photos/entity/collection_item.dart'; +import 'package:nc_photos/entity/collection_item/basic_item.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/use_case/list_location_file.dart'; + +class CollectionMemoryAdapter + with CollectionReadOnlyAdapter + implements CollectionAdapter { + CollectionMemoryAdapter(this._c, this.account, this.collection) + : assert(require(_c)), + _provider = collection.contentProvider as CollectionMemoryProvider; + + static bool require(DiContainer c) => ListLocationFile.require(c); + + @override + Stream> listItem() async* { + final date = DateTime(_provider.year, _provider.month, _provider.day); + final dayRange = _c.pref.getMemoriesRangeOr(); + final from = date.subtract(Duration(days: dayRange)); + final to = date.add(Duration(days: dayRange + 1)); + final files = await FileSqliteDbDataSource(_c).listByDate( + account, from.millisecondsSinceEpoch, to.millisecondsSinceEpoch); + yield files + .where((f) => file_util.isSupportedFormat(f)) + .map((f) => BasicCollectionFileItem(f)) + .toList(); + } + + @override + Future adaptToNewItem(CollectionItem original) async { + if (original is CollectionFileItem) { + return BasicCollectionFileItem(original.file); + } else { + throw UnsupportedError("Unsupported type: ${original.runtimeType}"); + } + } + + @override + Future remove() { + throw UnsupportedError("Operation not supported"); + } + + @override + bool isPermitted(CollectionCapability capability) => + _provider.capabilities.contains(capability); + + final DiContainer _c; + final Account account; + final Collection collection; + + final CollectionMemoryProvider _provider; +} diff --git a/app/lib/entity/collection/content_provider/album.dart b/app/lib/entity/collection/content_provider/album.dart index 0578dd70..9914fa90 100644 --- a/app/lib/entity/collection/content_provider/album.dart +++ b/app/lib/entity/collection/content_provider/album.dart @@ -1,4 +1,5 @@ import 'package:copy_with/copy_with.dart'; +import 'package:equatable/equatable.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/entity/album.dart'; @@ -12,7 +13,9 @@ part 'album.g.dart'; /// Album provided by our app @genCopyWith @toString -class CollectionAlbumProvider implements CollectionContentProvider { +class CollectionAlbumProvider + with EquatableMixin + implements CollectionContentProvider { const CollectionAlbumProvider({ required this.account, required this.album, @@ -64,7 +67,11 @@ class CollectionAlbumProvider implements CollectionContentProvider { CollectionItemSort get itemSort => album.sortProvider.toCollectionItemSort(); @override - String? getCoverUrl(int width, int height) { + String? getCoverUrl( + int width, + int height, { + bool? isKeepAspectRatio, + }) { final fd = album.coverProvider.getCover(album); if (fd == null) { return null; @@ -74,11 +81,14 @@ class CollectionAlbumProvider implements CollectionContentProvider { fd.fdId, width: width, height: height, - isKeepAspectRatio: false, + isKeepAspectRatio: isKeepAspectRatio ?? false, ); } } + @override + List get props => [account, album]; + final Account account; final Album album; } diff --git a/app/lib/entity/collection/content_provider/location_group.dart b/app/lib/entity/collection/content_provider/location_group.dart index ff96e1a0..6316eb99 100644 --- a/app/lib/entity/collection/content_provider/location_group.dart +++ b/app/lib/entity/collection/content_provider/location_group.dart @@ -1,10 +1,13 @@ +import 'package:equatable/equatable.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/entity/collection.dart'; import 'package:nc_photos/entity/collection_item/util.dart'; import 'package:nc_photos/use_case/list_location_group.dart'; -class CollectionLocationGroupProvider implements CollectionContentProvider { +class CollectionLocationGroupProvider + with EquatableMixin + implements CollectionContentProvider { const CollectionLocationGroupProvider({ required this.account, required this.location, @@ -29,16 +32,23 @@ class CollectionLocationGroupProvider implements CollectionContentProvider { CollectionItemSort get itemSort => CollectionItemSort.dateDescending; @override - String? getCoverUrl(int width, int height) { + String? getCoverUrl( + int width, + int height, { + bool? isKeepAspectRatio, + }) { return api_util.getFilePreviewUrlByFileId( account, location.latestFileId, width: width, height: height, - isKeepAspectRatio: false, + isKeepAspectRatio: isKeepAspectRatio ?? false, ); } + @override + List get props => [account, location]; + final Account account; final LocationGroup location; } diff --git a/app/lib/entity/collection/content_provider/memory.dart b/app/lib/entity/collection/content_provider/memory.dart new file mode 100644 index 00000000..c8ed13c5 --- /dev/null +++ b/app/lib/entity/collection/content_provider/memory.dart @@ -0,0 +1,68 @@ +import 'package:equatable/equatable.dart'; +import 'package:nc_photos/account.dart'; +import 'package:nc_photos/api/api_util.dart' as api_util; +import 'package:nc_photos/entity/collection.dart'; +import 'package:nc_photos/entity/collection_item/util.dart'; +import 'package:nc_photos/entity/file_descriptor.dart'; +import 'package:nc_photos/object_extension.dart'; +import 'package:to_string/to_string.dart'; + +part 'memory.g.dart'; + +@toString +class CollectionMemoryProvider + with EquatableMixin + implements CollectionContentProvider { + const CollectionMemoryProvider({ + required this.account, + required this.year, + required this.month, + required this.day, + this.cover, + }); + + @override + String get fourCc => "MEMY"; + + @override + String get id => "$year-$month-$day"; + + @override + int? get count => null; + + @override + DateTime get lastModified => DateTime(year, month, day); + + @override + List get capabilities => []; + + @override + CollectionItemSort get itemSort => CollectionItemSort.dateDescending; + + @override + String? getCoverUrl( + int width, + int height, { + bool? isKeepAspectRatio, + }) { + return cover?.run((cover) => api_util.getFilePreviewUrl( + account, + cover, + width: width, + height: height, + isKeepAspectRatio: isKeepAspectRatio ?? false, + )); + } + + @override + String toString() => _$toString(); + + @override + List get props => [account, year, month, day, cover]; + + final Account account; + final int year; + final int month; + final int day; + final FileDescriptor? cover; +} diff --git a/app/lib/entity/collection/content_provider/memory.g.dart b/app/lib/entity/collection/content_provider/memory.g.dart new file mode 100644 index 00000000..df768f45 --- /dev/null +++ b/app/lib/entity/collection/content_provider/memory.g.dart @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'memory.dart'; + +// ************************************************************************** +// ToStringGenerator +// ************************************************************************** + +extension _$CollectionMemoryProviderToString on CollectionMemoryProvider { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "CollectionMemoryProvider {account: $account, year: $year, month: $month, day: $day, cover: ${cover == null ? null : "${cover!.fdPath}"}}"; + } +} diff --git a/app/lib/entity/collection/content_provider/nc_album.dart b/app/lib/entity/collection/content_provider/nc_album.dart index 5d537cea..cd22d7fb 100644 --- a/app/lib/entity/collection/content_provider/nc_album.dart +++ b/app/lib/entity/collection/content_provider/nc_album.dart @@ -1,5 +1,6 @@ import 'package:clock/clock.dart'; import 'package:copy_with/copy_with.dart'; +import 'package:equatable/equatable.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/entity/collection.dart'; @@ -12,7 +13,9 @@ part 'nc_album.g.dart'; /// Album provided by our app @genCopyWith @toString -class CollectionNcAlbumProvider implements CollectionContentProvider { +class CollectionNcAlbumProvider + with EquatableMixin + implements CollectionContentProvider { const CollectionNcAlbumProvider({ required this.account, required this.album, @@ -43,7 +46,11 @@ class CollectionNcAlbumProvider implements CollectionContentProvider { CollectionItemSort get itemSort => CollectionItemSort.dateDescending; @override - String? getCoverUrl(int width, int height) { + String? getCoverUrl( + int width, + int height, { + bool? isKeepAspectRatio, + }) { if (album.lastPhoto == null) { return null; } else { @@ -52,11 +59,14 @@ class CollectionNcAlbumProvider implements CollectionContentProvider { album.lastPhoto!, width: width, height: height, - isKeepAspectRatio: false, + isKeepAspectRatio: isKeepAspectRatio ?? false, ); } } + @override + List get props => [account, album]; + final Account account; final NcAlbum album; } diff --git a/app/lib/entity/collection/content_provider/person.dart b/app/lib/entity/collection/content_provider/person.dart index ef3ce1cd..16e7efae 100644 --- a/app/lib/entity/collection/content_provider/person.dart +++ b/app/lib/entity/collection/content_provider/person.dart @@ -1,13 +1,16 @@ import 'dart:math' as math; import 'package:clock/clock.dart'; +import 'package:equatable/equatable.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/entity/collection.dart'; import 'package:nc_photos/entity/collection_item/util.dart'; import 'package:nc_photos/entity/person.dart'; -class CollectionPersonProvider implements CollectionContentProvider { +class CollectionPersonProvider + with EquatableMixin + implements CollectionContentProvider { const CollectionPersonProvider({ required this.account, required this.person, @@ -32,11 +35,18 @@ class CollectionPersonProvider implements CollectionContentProvider { CollectionItemSort get itemSort => CollectionItemSort.dateDescending; @override - String? getCoverUrl(int width, int height) { + String? getCoverUrl( + int width, + int height, { + bool? isKeepAspectRatio, + }) { return api_util.getFacePreviewUrl(account, person.thumbFaceId, size: math.max(width, height)); } + @override + List get props => [account, person]; + final Account account; final Person person; } diff --git a/app/lib/entity/collection/content_provider/tag.dart b/app/lib/entity/collection/content_provider/tag.dart index ab4b415e..23bc98f8 100644 --- a/app/lib/entity/collection/content_provider/tag.dart +++ b/app/lib/entity/collection/content_provider/tag.dart @@ -1,10 +1,13 @@ import 'package:clock/clock.dart'; +import 'package:equatable/equatable.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/entity/collection.dart'; import 'package:nc_photos/entity/collection_item/util.dart'; import 'package:nc_photos/entity/tag.dart'; -class CollectionTagProvider implements CollectionContentProvider { +class CollectionTagProvider + with EquatableMixin + implements CollectionContentProvider { CollectionTagProvider({ required this.account, required this.tags, @@ -29,7 +32,15 @@ class CollectionTagProvider implements CollectionContentProvider { CollectionItemSort get itemSort => CollectionItemSort.dateDescending; @override - String? getCoverUrl(int width, int height) => null; + String? getCoverUrl( + int width, + int height, { + bool? isKeepAspectRatio, + }) => + null; + + @override + List get props => [account, tags]; final Account account; final List tags; diff --git a/app/lib/entity/nc_album.dart b/app/lib/entity/nc_album.dart index 82812b06..9250c967 100644 --- a/app/lib/entity/nc_album.dart +++ b/app/lib/entity/nc_album.dart @@ -1,4 +1,5 @@ import 'package:copy_with/copy_with.dart'; +import 'package:equatable/equatable.dart'; import 'package:nc_photos/account.dart'; import 'package:np_common/string_extension.dart'; import 'package:to_string/to_string.dart'; @@ -8,7 +9,7 @@ part 'nc_album.g.dart'; /// Server-side album since Nextcloud 25 @toString @genCopyWith -class NcAlbum { +class NcAlbum with EquatableMixin { NcAlbum({ required String path, required this.lastPhoto, @@ -37,6 +38,17 @@ class NcAlbum { @override String toString() => _$toString(); + @override + List get props => [ + path, + lastPhoto, + nbItems, + location, + dateStart, + dateEnd, + collaborators, + ]; + final String path; /// File ID of the last photo diff --git a/app/lib/use_case/populate_album.dart b/app/lib/use_case/populate_album.dart index 6d2d5b24..6b020a49 100644 --- a/app/lib/use_case/populate_album.dart +++ b/app/lib/use_case/populate_album.dart @@ -8,8 +8,6 @@ import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/item.dart'; import 'package:nc_photos/entity/album/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/exception_event.dart'; import 'package:nc_photos/use_case/list_tagged_file.dart'; import 'package:nc_photos/use_case/scan_dir.dart'; @@ -32,8 +30,6 @@ class PopulateAlbum { return _populateDirAlbum(account, album); } else if (album.provider is AlbumTagProvider) { return _populateTagAlbum(account, album); - } else if (album.provider is AlbumMemoryProvider) { - return _populateMemoryAlbum(account, album); } else { throw ArgumentError( "Unknown album provider: ${album.provider.runtimeType}"); @@ -80,25 +76,5 @@ class PopulateAlbum { return products; } - Future> _populateMemoryAlbum( - Account account, Album album) async { - assert(album.provider is AlbumMemoryProvider); - final provider = album.provider as AlbumMemoryProvider; - final date = DateTime(provider.year, provider.month, provider.day); - final dayRange = _c.pref.getMemoriesRangeOr(); - final from = date.subtract(Duration(days: dayRange)); - final to = date.add(Duration(days: dayRange + 1)); - final files = await FileSqliteDbDataSource(_c).listByDate( - account, from.millisecondsSinceEpoch, to.millisecondsSinceEpoch); - return files - .where((f) => file_util.isSupportedFormat(f)) - .map((f) => AlbumFileItem( - addedBy: account.userId, - addedAt: clock.now(), - file: f, - )) - .toList(); - } - final DiContainer _c; } diff --git a/app/lib/widget/album_browser_util.dart b/app/lib/widget/album_browser_util.dart index 8ecb12f4..11bd502d 100644 --- a/app/lib/widget/album_browser_util.dart +++ b/app/lib/widget/album_browser_util.dart @@ -3,16 +3,12 @@ import 'package:nc_photos/account.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/widget/album_browser.dart'; -import 'package:nc_photos/widget/smart_album_browser.dart'; /// Push the corresponding browser route for this album void push(BuildContext context, Account account, Album album) { if (album.provider is AlbumStaticProvider) { Navigator.of(context).pushNamed(AlbumBrowser.routeName, arguments: AlbumBrowserArguments(account, album)); - } else if (album.provider is AlbumSmartProvider) { - Navigator.of(context).pushNamed(SmartAlbumBrowser.routeName, - arguments: SmartAlbumBrowserArguments(account, album)); } } diff --git a/app/lib/widget/builder/photo_list_item_builder.dart b/app/lib/widget/builder/photo_list_item_builder.dart index 738a4eac..0dcf9ebd 100644 --- a/app/lib/widget/builder/photo_list_item_builder.dart +++ b/app/lib/widget/builder/photo_list_item_builder.dart @@ -5,7 +5,7 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_init.dart' as app_init; import 'package:nc_photos/app_localizations.dart'; -import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/collection.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/object_extension.dart'; @@ -49,12 +49,12 @@ class PhotoListItemBuilderResult { const PhotoListItemBuilderResult( this.backingFiles, this.listItems, { - this.smartAlbums = const [], + this.smartCollections = const [], }); final List backingFiles; final List listItems; - final List smartAlbums; + final List smartCollections; } typedef PhotoListItemSorter = int Function(FileDescriptor, FileDescriptor); @@ -137,7 +137,7 @@ class _PhotoListItemBuilder { Account account, List files) { final today = clock.now(); final memoryAlbumHelper = smartAlbumConfig != null - ? MemoryAlbumHelper( + ? MemoryCollectionHelper(account, today: today, dayRange: smartAlbumConfig!.memoriesDayRange) : null; final listItems = []; @@ -155,7 +155,7 @@ class _PhotoListItemBuilder { return PhotoListItemBuilderResult( files, listItems, - smartAlbums: smartAlbums ?? [], + smartCollections: smartAlbums ?? [], ); } diff --git a/app/lib/widget/collection_browser/bloc.dart b/app/lib/widget/collection_browser/bloc.dart index 8a8ad72c..a5f0ffdc 100644 --- a/app/lib/widget/collection_browser/bloc.dart +++ b/app/lib/widget/collection_browser/bloc.dart @@ -8,6 +8,8 @@ class _Bloc extends Bloc<_Event, _State> implements BlocTag { required this.collectionsController, required Collection collection, }) : _c = container, + _isAdHocCollection = !collectionsController.stream.value.data + .any((e) => e.collection.compareIdentity(collection)), super(_State.init( collection: collection, coverUrl: _getCoverUrl(collection), @@ -45,14 +47,16 @@ class _Bloc extends Bloc<_Event, _State> implements BlocTag { on<_SetError>(_onSetError); on<_SetMessage>(_onSetMessage); - _collectionControllerSubscription = - collectionsController.stream.listen((event) { - final c = event.data - .firstWhere((d) => state.collection.compareIdentity(d.collection)); - if (!identical(c, state.collection)) { - add(_UpdateCollection(c.collection)); - } - }); + if (!_isAdHocCollection) { + _collectionControllerSubscription = + collectionsController.stream.listen((event) { + final c = event.data + .firstWhere((d) => state.collection.compareIdentity(d.collection)); + if (!identical(c, state.collection)) { + add(_UpdateCollection(c.collection)); + } + }); + } _itemsControllerSubscription = itemsController.stream.listen( (_) {}, onError: (e, stackTrace) { @@ -448,6 +452,10 @@ class _Bloc extends Bloc<_Event, _State> implements BlocTag { final CollectionsController collectionsController; late final CollectionItemsController itemsController; + /// Specify if the supplied [collection] is an "inline" one, which means it's + /// not returned from the collection controller but rather created temporarily + final bool _isAdHocCollection; + StreamSubscription? _collectionControllerSubscription; StreamSubscription? _itemsControllerSubscription; } diff --git a/app/lib/widget/home_photos.dart b/app/lib/widget/home_photos.dart index 1b644e07..aa6294d8 100644 --- a/app/lib/widget/home_photos.dart +++ b/app/lib/widget/home_photos.dart @@ -18,7 +18,7 @@ import 'package:nc_photos/bloc/scan_account_dir.dart'; import 'package:nc_photos/compute_queue.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/download_handler.dart'; -import 'package:nc_photos/entity/album.dart'; +import 'package:nc_photos/entity/collection.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/sqlite/database.dart' as sql; import 'package:nc_photos/event/event.dart'; @@ -38,19 +38,18 @@ import 'package:nc_photos/theme.dart'; import 'package:nc_photos/throttler.dart'; import 'package:nc_photos/use_case/startup_sync.dart'; import 'package:nc_photos/widget/builder/photo_list_item_builder.dart'; +import 'package:nc_photos/widget/collection_browser.dart'; import 'package:nc_photos/widget/handler/add_selection_to_collection_handler.dart'; import 'package:nc_photos/widget/handler/archive_selection_handler.dart'; import 'package:nc_photos/widget/handler/double_tap_exit_handler.dart'; import 'package:nc_photos/widget/handler/remove_selection_handler.dart'; import 'package:nc_photos/widget/home_app_bar.dart'; -import 'package:nc_photos/widget/network_thumbnail.dart'; import 'package:nc_photos/widget/page_visibility_mixin.dart'; import 'package:nc_photos/widget/photo_list_item.dart'; import 'package:nc_photos/widget/photo_list_util.dart' as photo_list_util; import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart'; import 'package:nc_photos/widget/selection_app_bar.dart'; import 'package:nc_photos/widget/settings.dart'; -import 'package:nc_photos/widget/smart_album_browser.dart'; import 'package:nc_photos/widget/viewer.dart'; import 'package:nc_photos/widget/zoom_menu_button.dart'; import 'package:np_codegen/np_codegen.dart'; @@ -192,7 +191,7 @@ class _HomePhotosState extends State _web?.buildContent(context), if (AccountPref.of(widget.account) .isEnableMemoryAlbumOr(true) && - _smartAlbums.isNotEmpty) + _smartCollections.isNotEmpty) _buildSmartAlbumList(context), BlocBuilder( bloc: _bloc, @@ -352,9 +351,9 @@ class _HomePhotosState extends State Widget _buildSmartAlbumList(BuildContext context) { return SliverToBoxAdapter( - child: _SmartAlbumList( + child: _SmartCollectionList( account: widget.account, - albums: _smartAlbums, + collections: _smartCollections, ), ); } @@ -613,7 +612,7 @@ class _HomePhotosState extends State setState(() { _backingFiles = result.backingFiles; itemStreamListItems = result.listItems; - _smartAlbums = result.smartAlbums; + _smartCollections = result.smartCollections; if (isPostSuccess) { _isScrollbarVisible = true; @@ -680,7 +679,7 @@ class _HomePhotosState extends State final metadataTaskHeaderExtent = _web?.getHeaderHeight() ?? 0; final smartAlbumListHeight = AccountPref.of(widget.account).isEnableMemoryAlbumOr(true) && - _smartAlbums.isNotEmpty + _smartCollections.isNotEmpty ? _SmartAlbumItem.height : 0; // scroll extent = list height - widget viewport height @@ -745,7 +744,7 @@ class _HomePhotosState extends State late final _queryProgressBloc = ProgressBloc(); var _backingFiles = []; - var _smartAlbums = []; + var _smartCollections = []; final _buildItemQueue = ComputeQueue(); @@ -1006,10 +1005,10 @@ class _MetadataTaskLoadingIcon extends AnimatedWidget { Animation get _progress => listenable as Animation; } -class _SmartAlbumList extends StatelessWidget { - const _SmartAlbumList({ +class _SmartCollectionList extends StatelessWidget { + const _SmartCollectionList({ required this.account, - required this.albums, + required this.collections, }); @override @@ -1019,19 +1018,20 @@ class _SmartAlbumList extends StatelessWidget { child: ListView.separated( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 8), - itemCount: albums.length, + itemCount: collections.length, itemBuilder: (context, index) { - final a = albums[index]; - final coverFile = a.coverProvider.getCover(a); + final c = collections[index]; return _SmartAlbumItem( account: account, - previewUrl: coverFile == null - ? null - : NetworkRectThumbnail.imageUrlForFile(account, coverFile), - label: a.name, + previewUrl: c.getCoverUrl( + k.photoThumbSize, + k.photoThumbSize, + isKeepAspectRatio: true, + ), + label: c.name, onTap: () { - Navigator.of(context).pushNamed(SmartAlbumBrowser.routeName, - arguments: SmartAlbumBrowserArguments(account, a)); + Navigator.of(context).pushNamed(CollectionBrowser.routeName, + arguments: CollectionBrowserArguments(c)); }, ); }, @@ -1041,7 +1041,7 @@ class _SmartAlbumList extends StatelessWidget { } final Account account; - final List albums; + final List collections; } class _SmartAlbumItem extends StatelessWidget { diff --git a/app/lib/widget/my_app.dart b/app/lib/widget/my_app.dart index 83ba0a8b..51d86e01 100644 --- a/app/lib/widget/my_app.dart +++ b/app/lib/widget/my_app.dart @@ -39,7 +39,6 @@ import 'package:nc_photos/widget/shared_file_viewer.dart'; import 'package:nc_photos/widget/sharing_browser.dart'; import 'package:nc_photos/widget/sign_in.dart'; import 'package:nc_photos/widget/slideshow_viewer.dart'; -import 'package:nc_photos/widget/smart_album_browser.dart'; import 'package:nc_photos/widget/splash.dart'; import 'package:nc_photos/widget/trashbin_browser.dart'; import 'package:nc_photos/widget/trashbin_viewer.dart'; @@ -195,7 +194,6 @@ class _WrappedAppState extends State<_WrappedApp> route ??= _handleAlbumShareOutlierBrowserRoute(settings); route ??= _handleAccountSettingsRoute(settings); route ??= _handleShareFolderPickerRoute(settings); - route ??= _handleSmartAlbumBrowserRoute(settings); route ??= _handleEnhancedPhotoBrowserRoute(settings); route ??= _handleLocalFileViewerRoute(settings); route ??= _handleEnhancementSettingsRoute(settings); @@ -455,20 +453,6 @@ class _WrappedAppState extends State<_WrappedApp> return null; } - Route? _handleSmartAlbumBrowserRoute(RouteSettings settings) { - try { - if (settings.name == SmartAlbumBrowser.routeName && - settings.arguments != null) { - final args = settings.arguments as SmartAlbumBrowserArguments; - return SmartAlbumBrowser.buildRoute(args); - } - } catch (e) { - _log.severe( - "[_handleSmartAlbumBrowserRoute] Failed while handling route", e); - } - return null; - } - Route? _handleEnhancedPhotoBrowserRoute(RouteSettings settings) { try { if (settings.name == EnhancedPhotoBrowser.routeName && diff --git a/app/lib/widget/photo_list_util.dart b/app/lib/widget/photo_list_util.dart index 0bbf2b69..bf71a50c 100644 --- a/app/lib/widget/photo_list_util.dart +++ b/app/lib/widget/photo_list_util.dart @@ -3,11 +3,10 @@ import 'dart:math' as math; import 'package:clock/clock.dart'; import 'package:collection/collection.dart'; import 'package:logging/logging.dart'; +import 'package:nc_photos/account.dart'; import 'package:nc_photos/date_time_extension.dart'; -import 'package:nc_photos/entity/album.dart'; -import 'package:nc_photos/entity/album/cover_provider.dart'; -import 'package:nc_photos/entity/album/provider.dart'; -import 'package:nc_photos/entity/album/sort_provider.dart'; +import 'package:nc_photos/entity/collection.dart'; +import 'package:nc_photos/entity/collection/content_provider/memory.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:np_codegen/np_codegen.dart'; @@ -34,12 +33,13 @@ class DateGroupHelper { DateTime? _currentDate; } -/// Build memory album from files +/// Build memory collection from files /// /// Feb 29 is treated as Mar 1 on non leap years @npLog -class MemoryAlbumHelper { - MemoryAlbumHelper({ +class MemoryCollectionHelper { + MemoryCollectionHelper( + this.account, { DateTime? today, required int dayRange, }) : today = (today?.toLocal() ?? clock.now()).toMidnight(), @@ -65,16 +65,18 @@ class MemoryAlbumHelper { /// /// [nameBuilder] is a function that return the name of the album for a /// particular year - List build(String Function(int year) nameBuilder) { + List build(String Function(int year) nameBuilder) { return _data.entries .sorted((a, b) => b.key.compareTo(a.key)) - .map((e) => Album( + .map((e) => Collection( name: nameBuilder(e.key), - provider: AlbumMemoryProvider( - year: e.key, month: today.month, day: today.day), - coverProvider: - AlbumMemoryCoverProvider(coverFile: e.value.coverFile), - sortProvider: const AlbumTimeSortProvider(isAscending: false), + contentProvider: CollectionMemoryProvider( + account: account, + year: e.key, + month: today.month, + day: today.day, + cover: e.value.coverFile, + ), )) .toList(); } @@ -83,9 +85,9 @@ class MemoryAlbumHelper { final item = _data[year]; final date = today.copyWith(year: year); if (item == null) { - _data[year] = _MemoryAlbumHelperItem(date, f); + _data[year] = _MemoryCollectionHelperItem(date, f); } else { - final coverDiff = _MemoryAlbumHelperItem.getCoverDiff(date, f); + final coverDiff = _MemoryCollectionHelperItem.getCoverDiff(date, f); if (coverDiff < item.coverDiff) { item.coverFile = f; item.coverDiff = coverDiff; @@ -93,9 +95,10 @@ class MemoryAlbumHelper { } } + final Account account; final DateTime today; final int dayRange; - final _data = {}; + final _data = {}; } int getThumbSize(int zoomLevel) { @@ -115,8 +118,8 @@ int getThumbSize(int zoomLevel) { } } -class _MemoryAlbumHelperItem { - _MemoryAlbumHelperItem(this.date, this.coverFile) +class _MemoryCollectionHelperItem { + _MemoryCollectionHelperItem(this.date, this.coverFile) : coverDiff = getCoverDiff(date, coverFile); static Duration getCoverDiff(DateTime date, FileDescriptor f) => diff --git a/app/lib/widget/photo_list_util.g.dart b/app/lib/widget/photo_list_util.g.dart index 79bfa43a..3607365c 100644 --- a/app/lib/widget/photo_list_util.g.dart +++ b/app/lib/widget/photo_list_util.g.dart @@ -6,9 +6,9 @@ part of 'photo_list_util.dart'; // NpLogGenerator // ************************************************************************** -extension _$MemoryAlbumHelperNpLog on MemoryAlbumHelper { +extension _$MemoryCollectionHelperNpLog on MemoryCollectionHelper { // ignore: unused_element Logger get _log => log; - static final log = Logger("widget.photo_list_util.MemoryAlbumHelper"); + static final log = Logger("widget.photo_list_util.MemoryCollectionHelper"); } diff --git a/app/lib/widget/smart_album_browser.dart b/app/lib/widget/smart_album_browser.dart deleted file mode 100644 index 4ddf18be..00000000 --- a/app/lib/widget/smart_album_browser.dart +++ /dev/null @@ -1,403 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.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/download_handler.dart'; -import 'package:nc_photos/entity/album.dart'; -import 'package:nc_photos/entity/album/item.dart'; -import 'package:nc_photos/entity/album/provider.dart'; -import 'package:nc_photos/entity/file.dart'; -import 'package:nc_photos/entity/file_util.dart' as file_util; -import 'package:nc_photos/flutter_util.dart' as flutter_util; -import 'package:nc_photos/object_extension.dart'; -import 'package:nc_photos/share_handler.dart'; -import 'package:nc_photos/use_case/preprocess_album.dart'; -import 'package:nc_photos/widget/album_browser_mixin.dart'; -import 'package:nc_photos/widget/handler/add_selection_to_collection_handler.dart'; -import 'package:nc_photos/widget/network_thumbnail.dart'; -import 'package:nc_photos/widget/photo_list_item.dart'; -import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart'; -import 'package:nc_photos/widget/viewer.dart'; -import 'package:np_codegen/np_codegen.dart'; -import 'package:to_string/to_string.dart'; - -part 'smart_album_browser.g.dart'; - -class SmartAlbumBrowserArguments { - const SmartAlbumBrowserArguments(this.account, this.album); - - final Account account; - final Album album; -} - -class SmartAlbumBrowser extends StatefulWidget { - static const routeName = "/smart-album-browser"; - - static Route buildRoute(SmartAlbumBrowserArguments args) => MaterialPageRoute( - builder: (context) => SmartAlbumBrowser.fromArgs(args), - ); - - const SmartAlbumBrowser({ - Key? key, - required this.account, - required this.album, - }) : super(key: key); - - SmartAlbumBrowser.fromArgs(SmartAlbumBrowserArguments args, {Key? key}) - : this( - key: key, - account: args.account, - album: args.album, - ); - - @override - createState() => _SmartAlbumBrowserState(); - - final Account account; - final Album album; -} - -@npLog -class _SmartAlbumBrowserState extends State - with - SelectableItemStreamListMixin, - AlbumBrowserMixin { - _SmartAlbumBrowserState() { - final c = KiwiContainer().resolve(); - assert(PreProcessAlbum.require(c)); - _c = c; - } - - @override - initState() { - super.initState(); - _initAlbum(); - } - - @override - build(BuildContext context) { - return Scaffold( - body: Builder( - builder: (context) => _buildContent(context), - ), - ); - } - - @override - onItemTap(SelectableItem item, int index) { - item.as<_ListItem>()?.onTap?.call(); - } - - @override - @protected - get canEdit => false; - - Future _initAlbum() async { - assert(widget.album.provider is AlbumSmartProvider); - _log.info("[_initAlbum] ${widget.album}"); - final items = await PreProcessAlbum(_c)(widget.account, widget.album); - if (mounted) { - setState(() { - _album = widget.album; - _transformItems(items); - initCover(widget.account, widget.album); - }); - } - } - - Widget _buildContent(BuildContext context) { - if (_album == null) { - return CustomScrollView( - slivers: [ - buildNormalAppBar(context, widget.account, widget.album), - const SliverToBoxAdapter( - child: LinearProgressIndicator(), - ), - ], - ); - } else { - return buildItemStreamListOuter( - context, - child: CustomScrollView( - slivers: [ - _buildAppBar(context), - buildItemStreamList( - maxCrossAxisExtent: thumbSize.toDouble(), - ), - ], - ), - ); - } - } - - Widget _buildAppBar(BuildContext context) { - if (isSelectionMode) { - return _buildSelectionAppBar(context); - } else { - return _buildNormalAppBar(context); - } - } - - Widget _buildNormalAppBar(BuildContext context) { - final menuItems = >[ - PopupMenuItem( - value: _menuValueDownload, - child: Text(L10n.global().downloadTooltip), - ), - ]; - - return buildNormalAppBar( - context, - widget.account, - _album!, - menuItemBuilder: (_) => menuItems, - onSelectedMenuItem: (option) { - switch (option) { - case _menuValueDownload: - _onDownloadPressed(); - break; - default: - _log.shout("[_buildNormalAppBar] Unknown value: $option"); - break; - } - }, - ); - } - - Widget _buildSelectionAppBar(BuildContext context) { - return buildSelectionAppBar(context, [ - IconButton( - icon: const Icon(Icons.share), - tooltip: L10n.global().shareTooltip, - onPressed: () { - _onSelectionSharePressed(context); - }, - ), - IconButton( - icon: const Icon(Icons.add), - tooltip: L10n.global().addToAlbumTooltip, - onPressed: () => _onSelectionAddPressed(context), - ), - PopupMenuButton<_SelectionMenuOption>( - tooltip: MaterialLocalizations.of(context).moreButtonTooltip, - itemBuilder: (context) => [ - PopupMenuItem( - value: _SelectionMenuOption.download, - child: Text(L10n.global().downloadTooltip), - ), - ], - onSelected: (option) => _onSelectionMenuSelected(context, option), - ), - ]); - } - - void _onItemTap(int index) { - // convert item index to file index - var fileIndex = index; - for (int i = 0; i < index; ++i) { - if (_sortedItems[i] is! AlbumFileItem || - !file_util - .isSupportedFormat((_sortedItems[i] as AlbumFileItem).file)) { - --fileIndex; - } - } - Navigator.pushNamed(context, Viewer.routeName, - arguments: ViewerArguments(widget.account, _backingFiles, fileIndex)); - } - - void _onDownloadPressed() { - final c = KiwiContainer().resolve(); - DownloadHandler(c).downloadFiles( - widget.account, - _sortedItems.whereType().map((e) => e.file).toList(), - parentDir: _album!.name, - ); - } - - void _onSelectionMenuSelected( - BuildContext context, _SelectionMenuOption option) { - switch (option) { - case _SelectionMenuOption.download: - _onSelectionDownloadPressed(); - break; - default: - _log.shout("[_onSelectionMenuSelected] Unknown option: $option"); - break; - } - } - - void _onSelectionSharePressed(BuildContext context) { - final c = KiwiContainer().resolve(); - final selected = selectedListItems - .whereType<_FileListItem>() - .map((e) => e.file) - .toList(); - ShareHandler( - c, - context: context, - clearSelection: () { - setState(() { - clearSelectedItems(); - }); - }, - ).shareFiles(widget.account, selected); - } - - Future _onSelectionAddPressed(BuildContext context) async { - return const AddSelectionToCollectionHandler()( - context: context, - selection: selectedListItems - .whereType<_FileListItem>() - .map((e) => e.file) - .toList(), - clearSelection: () { - if (mounted) { - setState(() { - clearSelectedItems(); - }); - } - }, - ); - } - - void _onSelectionDownloadPressed() { - final c = KiwiContainer().resolve(); - final selected = selectedListItems - .whereType<_FileListItem>() - .map((e) => e.file) - .toList(); - DownloadHandler(c).downloadFiles(widget.account, selected); - setState(() { - clearSelectedItems(); - }); - } - - void _transformItems(List items) { - // items come sorted for smart album - _sortedItems = _album!.sortProvider.sort(items); - _backingFiles = _sortedItems - .whereType() - .map((i) => i.file) - .where((f) => file_util.isSupportedFormat(f)) - .toList(); - - itemStreamListItems = () sync* { - for (int i = 0; i < _sortedItems.length; ++i) { - final item = _sortedItems[i]; - if (item is AlbumFileItem) { - final previewUrl = - NetworkRectThumbnail.imageUrlForFile(widget.account, item.file); - if (file_util.isSupportedImageFormat(item.file)) { - yield _ImageListItem( - index: i, - file: item.file, - account: widget.account, - previewUrl: previewUrl, - onTap: () => _onItemTap(i), - ); - } else if (file_util.isSupportedVideoFormat(item.file)) { - yield _VideoListItem( - index: i, - file: item.file, - account: widget.account, - previewUrl: previewUrl, - onTap: () => _onItemTap(i), - ); - } - } - } - }() - .toList(); - } - - late final DiContainer _c; - - Album? _album; - var _sortedItems = []; - var _backingFiles = []; - - static const _menuValueDownload = 1; -} - -enum _SelectionMenuOption { - download, -} - -@toString -abstract class _ListItem implements SelectableItem { - const _ListItem({ - required this.index, - this.onTap, - }); - - @override - get isTappable => onTap != null; - - @override - get isSelectable => true; - - @override - get staggeredTile => const StaggeredTile.count(1, 1); - - @override - String toString() => _$toString(); - - final int index; - - @ignore - final VoidCallback? onTap; -} - -abstract class _FileListItem extends _ListItem { - _FileListItem({ - required super.index, - required this.file, - super.onTap, - }); - - final File file; -} - -class _ImageListItem extends _FileListItem { - _ImageListItem({ - required super.index, - required super.file, - required this.account, - required this.previewUrl, - super.onTap, - }); - - @override - buildWidget(BuildContext context) => PhotoListImage( - account: account, - previewUrl: previewUrl, - isGif: file.contentType == "image/gif", - heroKey: flutter_util.getImageHeroTag(file), - ); - - final Account account; - final String previewUrl; -} - -class _VideoListItem extends _FileListItem { - _VideoListItem({ - required super.index, - required super.file, - required this.account, - required this.previewUrl, - super.onTap, - }); - - @override - buildWidget(BuildContext context) => PhotoListVideo( - account: account, - previewUrl: previewUrl, - ); - - final Account account; - final String previewUrl; -} diff --git a/app/lib/widget/smart_album_browser.g.dart b/app/lib/widget/smart_album_browser.g.dart deleted file mode 100644 index bf5026a3..00000000 --- a/app/lib/widget/smart_album_browser.g.dart +++ /dev/null @@ -1,26 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'smart_album_browser.dart'; - -// ************************************************************************** -// NpLogGenerator -// ************************************************************************** - -extension _$_SmartAlbumBrowserStateNpLog on _SmartAlbumBrowserState { - // ignore: unused_element - Logger get _log => log; - - static final log = - Logger("widget.smart_album_browser._SmartAlbumBrowserState"); -} - -// ************************************************************************** -// ToStringGenerator -// ************************************************************************** - -extension _$_ListItemToString on _ListItem { - String _$toString() { - // ignore: unnecessary_string_interpolations - return "${objectRuntimeType(this, "_ListItem")} {index: $index}"; - } -} diff --git a/app/test/widget/photo_list_util_test.dart b/app/test/widget/photo_list_util_test.dart index bea7e9b3..f339e593 100644 --- a/app/test/widget/photo_list_util_test.dart +++ b/app/test/widget/photo_list_util_test.dart @@ -1,15 +1,12 @@ -import 'package:nc_photos/entity/album.dart'; -import 'package:nc_photos/entity/album/cover_provider.dart'; -import 'package:nc_photos/entity/album/provider.dart'; -import 'package:nc_photos/entity/album/sort_provider.dart'; -import 'package:nc_photos/or_null.dart'; +import 'package:nc_photos/entity/collection.dart'; +import 'package:nc_photos/entity/collection/content_provider/memory.dart'; import 'package:nc_photos/widget/photo_list_util.dart'; import 'package:test/test.dart'; import '../test_util.dart' as util; void main() { - group("MemoryAlbumHelper", () { + group("MemoryCollectionHelper", () { test("same year", _sameYear); test("next year", _nextYear); group("prev year", () { @@ -54,8 +51,9 @@ void main() { /// File: 2021-02-01 /// Expect: empty void _sameYear() { + final account = util.buildAccount(); final today = DateTime(2021, 2, 3); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2021, 2, 3)); obj.addFile(file); @@ -69,8 +67,9 @@ void _sameYear() { /// File: 2022-02-03 /// Expect: empty void _nextYear() { + final account = util.buildAccount(); final today = DateTime(2021, 2, 3); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2022, 2, 3)); obj.addFile(file); @@ -83,24 +82,19 @@ void _nextYear() { /// File: 2020-02-03 /// Expect: [2020] void _prevYear() { + final account = util.buildAccount(); final today = DateTime(2021, 2, 3); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 2, 3)); obj.addFile(file); expect( - obj - .build(_nameBuilder) - .map((a) => a.copyWith(lastUpdated: OrNull(DateTime(2021)))) - .toList(), + obj.build(_nameBuilder).toList(), [ - Album( + Collection( name: "2020", - provider: - AlbumMemoryProvider(year: 2020, month: today.month, day: today.day), - coverProvider: AlbumMemoryCoverProvider(coverFile: file), - sortProvider: const AlbumTimeSortProvider(isAscending: false), - lastUpdated: DateTime(2021), + contentProvider: CollectionMemoryProvider( + account: account, year: 2020, month: 2, day: 3, cover: file), ), ], ); @@ -112,8 +106,9 @@ void _prevYear() { /// File: 2020-01-31 /// Expect: empty void _prevYear3DaysBefore() { + final account = util.buildAccount(); final today = DateTime(2021, 2, 3); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 1, 31)); obj.addFile(file); @@ -126,24 +121,19 @@ void _prevYear3DaysBefore() { /// File: 2020-02-01 /// Expect: [2020] void _prevYear2DaysBefore() { + final account = util.buildAccount(); final today = DateTime(2021, 2, 3); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 2, 1)); obj.addFile(file); expect( - obj - .build(_nameBuilder) - .map((a) => a.copyWith(lastUpdated: OrNull(DateTime(2021)))) - .toList(), + obj.build(_nameBuilder).toList(), [ - Album( + Collection( name: "2020", - provider: - AlbumMemoryProvider(year: 2020, month: today.month, day: today.day), - coverProvider: AlbumMemoryCoverProvider(coverFile: file), - sortProvider: const AlbumTimeSortProvider(isAscending: false), - lastUpdated: DateTime(2021), + contentProvider: CollectionMemoryProvider( + account: account, year: 2020, month: 2, day: 3, cover: file), ), ], ); @@ -155,8 +145,9 @@ void _prevYear2DaysBefore() { /// File: 2020-02-06 /// Expect: empty void _prevYear3DaysAfter() { + final account = util.buildAccount(); final today = DateTime(2021, 2, 3); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 2, 6)); obj.addFile(file); @@ -169,24 +160,19 @@ void _prevYear3DaysAfter() { /// File: 2020-02-05 /// Expect: [2020] void _prevYear2DaysAfter() { + final account = util.buildAccount(); final today = DateTime(2021, 2, 3); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 2, 5)); obj.addFile(file); expect( - obj - .build(_nameBuilder) - .map((a) => a.copyWith(lastUpdated: OrNull(DateTime(2021)))) - .toList(), + obj.build(_nameBuilder).toList(), [ - Album( + Collection( name: "2020", - provider: - AlbumMemoryProvider(year: 2020, month: today.month, day: today.day), - coverProvider: AlbumMemoryCoverProvider(coverFile: file), - sortProvider: const AlbumTimeSortProvider(isAscending: false), - lastUpdated: DateTime(2021), + contentProvider: CollectionMemoryProvider( + account: account, year: 2020, month: 2, day: 3, cover: file), ), ], ); @@ -198,8 +184,9 @@ void _prevYear2DaysAfter() { /// File: 2019-02-26 /// Expect: empty void _onFeb29AddFeb26() { + final account = util.buildAccount(); final today = DateTime(2020, 2, 29); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2019, 2, 26)); obj.addFile(file); @@ -212,24 +199,19 @@ void _onFeb29AddFeb26() { /// File: 2019-02-27 /// Expect: [2019] void _onFeb29AddFeb27() { + final account = util.buildAccount(); final today = DateTime(2020, 2, 29); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2019, 2, 27)); obj.addFile(file); expect( - obj - .build(_nameBuilder) - .map((a) => a.copyWith(lastUpdated: OrNull(DateTime(2021)))) - .toList(), + obj.build(_nameBuilder).toList(), [ - Album( + Collection( name: "2019", - provider: - AlbumMemoryProvider(year: 2019, month: today.month, day: today.day), - coverProvider: AlbumMemoryCoverProvider(coverFile: file), - sortProvider: const AlbumTimeSortProvider(isAscending: false), - lastUpdated: DateTime(2021), + contentProvider: CollectionMemoryProvider( + account: account, year: 2019, month: 2, day: 29, cover: file), ), ], ); @@ -241,8 +223,9 @@ void _onFeb29AddFeb27() { /// File: 2019-03-04 /// Expect: empty void _onFeb29AddMar4() { + final account = util.buildAccount(); final today = DateTime(2020, 2, 29); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2019, 3, 4)); obj.addFile(file); @@ -255,24 +238,19 @@ void _onFeb29AddMar4() { /// File: 2019-03-03 /// Expect: [2019] void _onFeb29AddMar3() { + final account = util.buildAccount(); final today = DateTime(2020, 2, 29); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2019, 3, 3)); obj.addFile(file); expect( - obj - .build(_nameBuilder) - .map((a) => a.copyWith(lastUpdated: OrNull(DateTime(2021)))) - .toList(), + obj.build(_nameBuilder).toList(), [ - Album( + Collection( name: "2019", - provider: - AlbumMemoryProvider(year: 2019, month: today.month, day: today.day), - coverProvider: AlbumMemoryCoverProvider(coverFile: file), - sortProvider: const AlbumTimeSortProvider(isAscending: false), - lastUpdated: DateTime(2021), + contentProvider: CollectionMemoryProvider( + account: account, year: 2019, month: 2, day: 29, cover: file), ), ], ); @@ -284,8 +262,9 @@ void _onFeb29AddMar3() { /// File: 2016-03-03 /// Expect: empty void _onFeb29AddMar3LeapYear() { + final account = util.buildAccount(); final today = DateTime(2020, 2, 29); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2016, 3, 3)); obj.addFile(file); @@ -298,24 +277,19 @@ void _onFeb29AddMar3LeapYear() { /// File: 2016-03-02 /// Expect: [2016] void _onFeb29AddMar2LeapYear() { + final account = util.buildAccount(); final today = DateTime(2020, 2, 29); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2016, 3, 2)); obj.addFile(file); expect( - obj - .build(_nameBuilder) - .map((a) => a.copyWith(lastUpdated: OrNull(DateTime(2021)))) - .toList(), + obj.build(_nameBuilder).toList(), [ - Album( + Collection( name: "2016", - provider: - AlbumMemoryProvider(year: 2016, month: today.month, day: today.day), - coverProvider: AlbumMemoryCoverProvider(coverFile: file), - sortProvider: const AlbumTimeSortProvider(isAscending: false), - lastUpdated: DateTime(2021), + contentProvider: CollectionMemoryProvider( + account: account, year: 2016, month: 2, day: 29, cover: file), ), ], ); @@ -327,8 +301,9 @@ void _onFeb29AddMar2LeapYear() { /// File: 2019-12-31 /// Expect: empty void _onJan1AddDec31() { + final account = util.buildAccount(); final today = DateTime(2020, 1, 1); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2019, 12, 31)); obj.addFile(file); @@ -341,24 +316,19 @@ void _onJan1AddDec31() { /// File: 2018-12-31 /// Expect: [2019] void _onJan1AddDec31PrevYear() { + final account = util.buildAccount(); final today = DateTime(2020, 1, 1); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2018, 12, 31)); obj.addFile(file); expect( - obj - .build(_nameBuilder) - .map((a) => a.copyWith(lastUpdated: OrNull(DateTime(2021)))) - .toList(), + obj.build(_nameBuilder).toList(), [ - Album( + Collection( name: "2019", - provider: - AlbumMemoryProvider(year: 2019, month: today.month, day: today.day), - coverProvider: AlbumMemoryCoverProvider(coverFile: file), - sortProvider: const AlbumTimeSortProvider(isAscending: false), - lastUpdated: DateTime(2021), + contentProvider: CollectionMemoryProvider( + account: account, year: 2019, month: 1, day: 1, cover: file), ), ], ); @@ -370,24 +340,19 @@ void _onJan1AddDec31PrevYear() { /// File: 2020-01-01 /// Expect: [2019] void _onDec31AddJan1() { + final account = util.buildAccount(); final today = DateTime(2020, 12, 31); - final obj = MemoryAlbumHelper(today: today, dayRange: 2); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 1, 1)); obj.addFile(file); expect( - obj - .build(_nameBuilder) - .map((a) => a.copyWith(lastUpdated: OrNull(DateTime(2021)))) - .toList(), + obj.build(_nameBuilder).toList(), [ - Album( + Collection( name: "2019", - provider: - AlbumMemoryProvider(year: 2019, month: today.month, day: today.day), - coverProvider: AlbumMemoryCoverProvider(coverFile: file), - sortProvider: const AlbumTimeSortProvider(isAscending: false), - lastUpdated: DateTime(2021), + contentProvider: CollectionMemoryProvider( + account: account, year: 2019, month: 12, day: 31, cover: file), ), ], ); @@ -399,24 +364,19 @@ void _onDec31AddJan1() { /// File: 2021-05-15 /// Expect: [2022] void _onMay15AddMay15Range0() { + final account = util.buildAccount(); final today = DateTime(2022, 5, 15); - final obj = MemoryAlbumHelper(today: today, dayRange: 0); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 0); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2021, 5, 15)); obj.addFile(file); expect( - obj - .build(_nameBuilder) - .map((a) => a.copyWith(lastUpdated: OrNull(DateTime(2021)))) - .toList(), + obj.build(_nameBuilder).toList(), [ - Album( + Collection( name: "2021", - provider: - AlbumMemoryProvider(year: 2021, month: today.month, day: today.day), - coverProvider: AlbumMemoryCoverProvider(coverFile: file), - sortProvider: const AlbumTimeSortProvider(isAscending: false), - lastUpdated: DateTime(2021), + contentProvider: CollectionMemoryProvider( + account: account, year: 2021, month: 5, day: 15, cover: file), ), ], ); @@ -428,8 +388,9 @@ void _onMay15AddMay15Range0() { /// File: 2021-05-16 /// Expect: [] void _onMay15AddMay16Range0() { + final account = util.buildAccount(); final today = DateTime(2022, 5, 15); - final obj = MemoryAlbumHelper(today: today, dayRange: 0); + final obj = MemoryCollectionHelper(account, today: today, dayRange: 0); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2021, 5, 16)); obj.addFile(file); @@ -442,8 +403,9 @@ void _onMay15AddMay16Range0() { /// File: 2021-05-16 /// Expect: [] void _onMay15AddMay16RangeNegative() { + final account = util.buildAccount(); final today = DateTime(2022, 5, 15); - final obj = MemoryAlbumHelper(today: today, dayRange: -1); + final obj = MemoryCollectionHelper(account, today: today, dayRange: -1); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2021, 5, 16)); obj.addFile(file);