Migrate Memory to use collection browser

This commit is contained in:
Ming Ming 2023-04-29 01:21:53 +08:00
parent 71d2b56607
commit 53b51b77b1
26 changed files with 392 additions and 740 deletions

View file

@ -26,9 +26,6 @@ abstract class AlbumCoverProvider with EquatableMixin {
case AlbumManualCoverProvider._type:
return AlbumManualCoverProvider.fromJson(
content.cast<String, dynamic>());
case AlbumMemoryCoverProvider._type:
return AlbumMemoryCoverProvider.fromJson(
content.cast<String, dynamic>());
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<String, dynamic>()),
);
}
@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";
}

View file

@ -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}}";
}
}

View file

@ -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;
}

View file

@ -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}";
}
}

View file

@ -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<Object?> 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,
});
}

View file

@ -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:

View file

@ -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<List<CollectionItem>> 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<CollectionItem> adaptToNewItem(CollectionItem original) async {
if (original is CollectionFileItem) {
return BasicCollectionFileItem(original.file);
} else {
throw UnsupportedError("Unsupported type: ${original.runtimeType}");
}
}
@override
Future<void> 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;
}

View file

@ -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<Object?> get props => [account, album];
final Account account;
final Album album;
}

View file

@ -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<Object?> get props => [account, location];
final Account account;
final LocationGroup location;
}

View file

@ -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<CollectionCapability> 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<Object?> get props => [account, year, month, day, cover];
final Account account;
final int year;
final int month;
final int day;
final FileDescriptor? cover;
}

View file

@ -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}"}}";
}
}

View file

@ -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<Object?> get props => [account, album];
final Account account;
final NcAlbum album;
}

View file

@ -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<Object?> get props => [account, person];
final Account account;
final Person person;
}

View file

@ -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<Object?> get props => [account, tags];
final Account account;
final List<Tag> tags;

View file

@ -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<Object?> get props => [
path,
lastPhoto,
nbItems,
location,
dateStart,
dateEnd,
collaborators,
];
final String path;
/// File ID of the last photo

View file

@ -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<List<AlbumItem>> _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;
}

View file

@ -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));
}
}

View file

@ -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<FileDescriptor> backingFiles;
final List<SelectableItem> listItems;
final List<Album> smartAlbums;
final List<Collection> smartCollections;
}
typedef PhotoListItemSorter = int Function(FileDescriptor, FileDescriptor);
@ -137,7 +137,7 @@ class _PhotoListItemBuilder {
Account account, List<FileDescriptor> files) {
final today = clock.now();
final memoryAlbumHelper = smartAlbumConfig != null
? MemoryAlbumHelper(
? MemoryCollectionHelper(account,
today: today, dayRange: smartAlbumConfig!.memoriesDayRange)
: null;
final listItems = <SelectableItem>[];
@ -155,7 +155,7 @@ class _PhotoListItemBuilder {
return PhotoListItemBuilderResult(
files,
listItems,
smartAlbums: smartAlbums ?? [],
smartCollections: smartAlbums ?? [],
);
}

View file

@ -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;
}

View file

@ -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<HomePhotos>
_web?.buildContent(context),
if (AccountPref.of(widget.account)
.isEnableMemoryAlbumOr(true) &&
_smartAlbums.isNotEmpty)
_smartCollections.isNotEmpty)
_buildSmartAlbumList(context),
BlocBuilder<ScanAccountDirBloc, ScanAccountDirBlocState>(
bloc: _bloc,
@ -352,9 +351,9 @@ class _HomePhotosState extends State<HomePhotos>
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<HomePhotos>
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<HomePhotos>
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<HomePhotos>
late final _queryProgressBloc = ProgressBloc();
var _backingFiles = <FileDescriptor>[];
var _smartAlbums = <Album>[];
var _smartCollections = <Collection>[];
final _buildItemQueue =
ComputeQueue<PhotoListItemBuilderArguments, PhotoListItemBuilderResult>();
@ -1006,10 +1005,10 @@ class _MetadataTaskLoadingIcon extends AnimatedWidget {
Animation<double> get _progress => listenable as Animation<double>;
}
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<Album> albums;
final List<Collection> collections;
}
class _SmartAlbumItem extends StatelessWidget {

View file

@ -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<dynamic>? _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<dynamic>? _handleEnhancedPhotoBrowserRoute(RouteSettings settings) {
try {
if (settings.name == EnhancedPhotoBrowser.routeName &&

View file

@ -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<Album> build(String Function(int year) nameBuilder) {
List<Collection> 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 = <int, _MemoryAlbumHelperItem>{};
final _data = <int, _MemoryCollectionHelperItem>{};
}
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) =>

View file

@ -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");
}

View file

@ -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<SmartAlbumBrowser>
with
SelectableItemStreamListMixin<SmartAlbumBrowser>,
AlbumBrowserMixin<SmartAlbumBrowser> {
_SmartAlbumBrowserState() {
final c = KiwiContainer().resolve<DiContainer>();
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<void> _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 = <PopupMenuEntry<int>>[
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<DiContainer>();
DownloadHandler(c).downloadFiles(
widget.account,
_sortedItems.whereType<AlbumFileItem>().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<DiContainer>();
final selected = selectedListItems
.whereType<_FileListItem>()
.map((e) => e.file)
.toList();
ShareHandler(
c,
context: context,
clearSelection: () {
setState(() {
clearSelectedItems();
});
},
).shareFiles(widget.account, selected);
}
Future<void> _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<DiContainer>();
final selected = selectedListItems
.whereType<_FileListItem>()
.map((e) => e.file)
.toList();
DownloadHandler(c).downloadFiles(widget.account, selected);
setState(() {
clearSelectedItems();
});
}
void _transformItems(List<AlbumItem> items) {
// items come sorted for smart album
_sortedItems = _album!.sortProvider.sort(items);
_backingFiles = _sortedItems
.whereType<AlbumFileItem>()
.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 = <AlbumItem>[];
var _backingFiles = <File>[];
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;
}

View file

@ -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}";
}
}

View file

@ -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);