mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-13 18:58:53 +01:00
Abstract album content provider
This commit is contained in:
parent
d4785b1f74
commit
0e7f2462b6
13 changed files with 452 additions and 149 deletions
|
@ -138,6 +138,7 @@ class AppDbAlbumEntry {
|
|||
Album.fromJson(
|
||||
json["album"].cast<String, dynamic>(),
|
||||
upgraderV1: AlbumUpgraderV1(),
|
||||
upgraderV2: AlbumUpgraderV2(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
|
@ -8,6 +7,7 @@ import 'package:idb_sqflite/idb_sqflite.dart';
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/app_db.dart';
|
||||
import 'package:nc_photos/entity/album/provider.dart';
|
||||
import 'package:nc_photos/entity/album/upgrader.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:nc_photos/entity/file/data_source.dart';
|
||||
|
@ -126,15 +126,15 @@ class Album with EquatableMixin {
|
|||
Album({
|
||||
DateTime lastUpdated,
|
||||
@required String name,
|
||||
@required List<AlbumItem> items,
|
||||
@required this.provider,
|
||||
this.albumFile,
|
||||
}) : this.lastUpdated = (lastUpdated ?? DateTime.now()).toUtc(),
|
||||
this.name = name ?? "",
|
||||
this.items = UnmodifiableListView(items);
|
||||
this.name = name ?? "";
|
||||
|
||||
factory Album.fromJson(
|
||||
Map<String, dynamic> json, {
|
||||
AlbumUpgraderV1 upgraderV1,
|
||||
AlbumUpgraderV2 upgraderV2,
|
||||
}) {
|
||||
final jsonVersion = json["version"];
|
||||
if (jsonVersion < 2) {
|
||||
|
@ -144,14 +144,20 @@ class Album with EquatableMixin {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
if (jsonVersion < 3) {
|
||||
json = upgraderV2?.call(json);
|
||||
if (json == null) {
|
||||
_log.info("[fromJson] Version $jsonVersion not compatible");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return Album(
|
||||
lastUpdated: json["lastUpdated"] == null
|
||||
? null
|
||||
: DateTime.parse(json["lastUpdated"]),
|
||||
name: json["name"],
|
||||
items: (json["items"] as List)
|
||||
.map((e) => AlbumItem.fromJson(e.cast<String, dynamic>()))
|
||||
.toList(),
|
||||
provider:
|
||||
AlbumProvider.fromJson(json["provider"].cast<String, dynamic>()),
|
||||
albumFile: json["albumFile"] == null
|
||||
? null
|
||||
: File.fromJson(json["albumFile"].cast<String, dynamic>()),
|
||||
|
@ -160,12 +166,10 @@ class Album with EquatableMixin {
|
|||
|
||||
@override
|
||||
toString({bool isDeep = false}) {
|
||||
final itemsStr =
|
||||
isDeep ? items.toReadableString() : "List {length: ${items.length}}";
|
||||
return "$runtimeType {"
|
||||
"lastUpdated: $lastUpdated, "
|
||||
"name: $name, "
|
||||
"items: $itemsStr, "
|
||||
"provider: ${provider.toString(isDeep: isDeep)}, "
|
||||
"albumFile: $albumFile, "
|
||||
"}";
|
||||
}
|
||||
|
@ -178,13 +182,13 @@ class Album with EquatableMixin {
|
|||
Album copyWith({
|
||||
DateTime lastUpdated,
|
||||
String name,
|
||||
List<AlbumItem> items,
|
||||
AlbumProvider provider,
|
||||
File albumFile,
|
||||
}) {
|
||||
return Album(
|
||||
lastUpdated: lastUpdated,
|
||||
name: name ?? this.name,
|
||||
items: items ?? this.items,
|
||||
provider: provider ?? this.provider,
|
||||
albumFile: albumFile ?? this.albumFile,
|
||||
);
|
||||
}
|
||||
|
@ -194,7 +198,7 @@ class Album with EquatableMixin {
|
|||
"version": version,
|
||||
"lastUpdated": lastUpdated.toIso8601String(),
|
||||
"name": name,
|
||||
"items": items.map((e) => e.toJson()).toList(),
|
||||
"provider": provider.toJson(),
|
||||
// ignore albumFile
|
||||
};
|
||||
}
|
||||
|
@ -204,7 +208,7 @@ class Album with EquatableMixin {
|
|||
"version": version,
|
||||
"lastUpdated": lastUpdated.toIso8601String(),
|
||||
"name": name,
|
||||
"items": items.map((e) => e.toJson()).toList(),
|
||||
"provider": provider.toJson(),
|
||||
if (albumFile != null) "albumFile": albumFile.toJson(),
|
||||
};
|
||||
}
|
||||
|
@ -213,15 +217,14 @@ class Album with EquatableMixin {
|
|||
get props => [
|
||||
lastUpdated,
|
||||
name,
|
||||
items,
|
||||
provider,
|
||||
albumFile,
|
||||
];
|
||||
|
||||
final DateTime lastUpdated;
|
||||
final String name;
|
||||
|
||||
/// Immutable list of items. Modifying the list will result in an error
|
||||
final List<AlbumItem> items;
|
||||
final AlbumProvider provider;
|
||||
|
||||
/// How is this album stored on server
|
||||
///
|
||||
|
@ -229,7 +232,7 @@ class Album with EquatableMixin {
|
|||
final File albumFile;
|
||||
|
||||
/// versioning of this class, use to upgrade old persisted album
|
||||
static const version = 2;
|
||||
static const version = 3;
|
||||
}
|
||||
|
||||
class AlbumRepo {
|
||||
|
@ -281,6 +284,7 @@ class AlbumRemoteDataSource implements AlbumDataSource {
|
|||
return Album.fromJson(
|
||||
jsonDecode(utf8.decode(data)),
|
||||
upgraderV1: AlbumUpgraderV1(),
|
||||
upgraderV2: AlbumUpgraderV2(),
|
||||
).copyWith(albumFile: albumFile);
|
||||
} catch (e, stacktrace) {
|
||||
dynamic d = data;
|
||||
|
@ -343,11 +347,19 @@ class AlbumAppDbDataSource implements AlbumDataSource {
|
|||
if (results?.isNotEmpty == true) {
|
||||
final entries = results
|
||||
.map((e) => AppDbAlbumEntry.fromJson(e.cast<String, dynamic>()));
|
||||
final items = entries.map((e) {
|
||||
_log.info("[get] ${e.path}[${e.index}]");
|
||||
return e.album.items;
|
||||
}).reduce((value, element) => value + element);
|
||||
return entries.first.album.copyWith(items: items);
|
||||
if (entries.length > 1) {
|
||||
final items = entries.map((e) {
|
||||
_log.info("[get] ${e.path}[${e.index}]");
|
||||
return AlbumStaticProvider.of(e.album).items;
|
||||
}).reduce((value, element) => value + element);
|
||||
return entries.first.album.copyWith(
|
||||
provider: AlbumStaticProvider(
|
||||
items: items,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return entries.first.album;
|
||||
}
|
||||
} else {
|
||||
throw CacheNotFoundException("No entry: $path");
|
||||
}
|
||||
|
@ -461,27 +473,37 @@ Future<void> _cacheAlbum(
|
|||
final range = KeyRange.bound([path, 0], [path, int_util.int32Max]);
|
||||
// count number of entries for this album
|
||||
final count = await index.count(range);
|
||||
int newCount = 0;
|
||||
|
||||
var albumItemLists =
|
||||
partition(album.items, AppDbAlbumEntry.maxDataSize).toList();
|
||||
if (albumItemLists.isEmpty) {
|
||||
albumItemLists = [<AlbumItem>[]];
|
||||
// cut large album into smaller pieces, needed to workaround Android DB
|
||||
// limitation
|
||||
final entries = <AppDbAlbumEntry>[];
|
||||
if (album.provider is AlbumStaticProvider) {
|
||||
var albumItemLists = partition(
|
||||
AlbumStaticProvider.of(album).items, AppDbAlbumEntry.maxDataSize)
|
||||
.toList();
|
||||
if (albumItemLists.isEmpty) {
|
||||
albumItemLists = [<AlbumItem>[]];
|
||||
}
|
||||
entries.addAll(albumItemLists.withIndex().map((pair) => AppDbAlbumEntry(
|
||||
path,
|
||||
pair.item1,
|
||||
album.copyWith(
|
||||
provider: AlbumStaticProvider(items: pair.item2),
|
||||
))));
|
||||
} else {
|
||||
entries.add(AppDbAlbumEntry(path, 0, album));
|
||||
}
|
||||
|
||||
for (final pair in albumItemLists.withIndex()) {
|
||||
_log.info(
|
||||
"[_cacheAlbum] Caching $path[${pair.item1}], length: ${pair.item2.length}");
|
||||
await store.put(
|
||||
AppDbAlbumEntry(path, pair.item1, album.copyWith(items: pair.item2))
|
||||
.toJson(),
|
||||
AppDbAlbumEntry.toPrimaryKey(account, album.albumFile, pair.item1),
|
||||
);
|
||||
++newCount;
|
||||
for (final e in entries) {
|
||||
_log.info("[_cacheAlbum] Caching ${e.path}[${e.index}]");
|
||||
await store.put(e.toJson(),
|
||||
AppDbAlbumEntry.toPrimaryKey(account, e.album.albumFile, e.index));
|
||||
}
|
||||
if (count > newCount) {
|
||||
|
||||
if (count > entries.length) {
|
||||
// index is 0-based
|
||||
final rmRange = KeyRange.bound([path, newCount], [path, int_util.int32Max]);
|
||||
final rmRange =
|
||||
KeyRange.bound([path, entries.length], [path, int_util.int32Max]);
|
||||
final rmKeys = await index
|
||||
.openKeyCursor(range: rmRange, autoAdvance: true)
|
||||
.map((cursor) => cursor.primaryKey)
|
||||
|
|
85
lib/entity/album/provider.dart
Normal file
85
lib/entity/album/provider.dart
Normal file
|
@ -0,0 +1,85 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/iterable_extension.dart';
|
||||
|
||||
abstract class AlbumProvider with EquatableMixin {
|
||||
const AlbumProvider();
|
||||
|
||||
factory AlbumProvider.fromJson(Map<String, dynamic> json) {
|
||||
final type = json["type"];
|
||||
final content = json["content"];
|
||||
switch (type) {
|
||||
case AlbumStaticProvider._type:
|
||||
return AlbumStaticProvider.fromJson(content.cast<String, dynamic>());
|
||||
default:
|
||||
_log.shout("[fromJson] Unknown type: $type");
|
||||
throw ArgumentError.value(type, "type");
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
String getType() {
|
||||
if (this is AlbumStaticProvider) {
|
||||
return AlbumStaticProvider._type;
|
||||
} else {
|
||||
throw StateError("Unknwon subtype");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"type": getType(),
|
||||
"content": _toContentJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
toString({bool isDeep = false});
|
||||
|
||||
Map<String, dynamic> _toContentJson();
|
||||
|
||||
static final _log = Logger("entity.album.provider.AlbumProvider");
|
||||
}
|
||||
|
||||
class AlbumStaticProvider extends AlbumProvider {
|
||||
AlbumStaticProvider({
|
||||
@required List<AlbumItem> items,
|
||||
}) : this.items = UnmodifiableListView(items);
|
||||
|
||||
factory AlbumStaticProvider.fromJson(Map<String, dynamic> json) {
|
||||
return AlbumStaticProvider(
|
||||
items: (json["items"] as List)
|
||||
.map((e) => AlbumItem.fromJson(e.cast<String, dynamic>()))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
toString({bool isDeep = false}) {
|
||||
final itemsStr =
|
||||
isDeep ? items.toReadableString() : "List {length: ${items.length}}";
|
||||
return "$runtimeType {"
|
||||
"items: $itemsStr, "
|
||||
"}";
|
||||
}
|
||||
|
||||
@override
|
||||
get props => [
|
||||
items,
|
||||
];
|
||||
|
||||
@override
|
||||
_toContentJson() {
|
||||
return {
|
||||
"items": items.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Immutable list of items. Modifying the list will result in an error
|
||||
final List<AlbumItem> items;
|
||||
|
||||
static const _type = "static";
|
||||
}
|
|
@ -23,3 +23,29 @@ class AlbumUpgraderV1 implements AlbumUpgrader {
|
|||
|
||||
static final _log = Logger("entity.album.upgrader.AlbumUpgraderV1");
|
||||
}
|
||||
|
||||
/// Upgrade v2 Album to v3
|
||||
class AlbumUpgraderV2 implements AlbumUpgrader {
|
||||
AlbumUpgraderV2({
|
||||
this.logFilePath,
|
||||
});
|
||||
|
||||
Map<String, dynamic> call(Map<String, dynamic> json) {
|
||||
// move v2 items to v3 provider
|
||||
_log.fine("[call] Upgrade v2 Album for file: $logFilePath");
|
||||
final result = Map<String, dynamic>.from(json);
|
||||
result["provider"] = <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": result["items"],
|
||||
}
|
||||
};
|
||||
result.remove("items");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// File path for logging only
|
||||
final String logFilePath;
|
||||
|
||||
static final _log = Logger("entity.album.upgrader.AlbumUpgraderV2");
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:kiwi/kiwi.dart';
|
|||
import 'package:logging/logging.dart';
|
||||
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/entity/file.dart';
|
||||
import 'package:nc_photos/event/event.dart';
|
||||
import 'package:nc_photos/use_case/list_album.dart';
|
||||
|
@ -23,18 +24,27 @@ class Remove {
|
|||
|
||||
Future<void> _cleanUpAlbums(Account account, File file) async {
|
||||
final albums = await ListAlbum(fileRepo, albumRepo)(account);
|
||||
for (final a in albums) {
|
||||
// clean up only make sense for static albums
|
||||
for (final a
|
||||
in albums.where((element) => element.provider is AlbumStaticProvider)) {
|
||||
try {
|
||||
if (a.items.any((element) =>
|
||||
final provider = AlbumStaticProvider.of(a);
|
||||
if (provider.items.any((element) =>
|
||||
element is AlbumFileItem && element.file.path == file.path)) {
|
||||
final newItems = a.items.where((element) {
|
||||
final newItems = provider.items.where((element) {
|
||||
if (element is AlbumFileItem) {
|
||||
return element.file.path != file.path;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}).toList();
|
||||
await UpdateAlbum(albumRepo)(account, a.copyWith(items: newItems));
|
||||
await UpdateAlbum(albumRepo)(
|
||||
account,
|
||||
a.copyWith(
|
||||
provider: AlbumStaticProvider(
|
||||
items: newItems,
|
||||
),
|
||||
));
|
||||
}
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout(
|
||||
|
|
|
@ -4,18 +4,24 @@ import 'package:logging/logging.dart';
|
|||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/app_db.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/entity/album/provider.dart';
|
||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||
|
||||
/// Resync files inside an album with the file db
|
||||
class ResyncAlbum {
|
||||
Future<Album> call(Account account, Album album) async {
|
||||
if (album.provider is! AlbumStaticProvider) {
|
||||
_log.warning(
|
||||
"[call] Resync only make sense for static albums: ${album.name}");
|
||||
return album;
|
||||
}
|
||||
return await AppDb.use((db) async {
|
||||
final transaction =
|
||||
db.transaction(AppDb.fileDbStoreName, idbModeReadWrite);
|
||||
final store = transaction.objectStore(AppDb.fileDbStoreName);
|
||||
final index = store.index(AppDbFileDbEntry.indexName);
|
||||
final newItems = <AlbumItem>[];
|
||||
for (final item in album.items) {
|
||||
for (final item in AlbumStaticProvider.of(album).items) {
|
||||
if (item is AlbumFileItem) {
|
||||
try {
|
||||
newItems.add(await _syncOne(account, item, store, index));
|
||||
|
@ -30,7 +36,7 @@ class ResyncAlbum {
|
|||
newItems.add(item);
|
||||
}
|
||||
}
|
||||
return album.copyWith(items: newItems);
|
||||
return album.copyWith(provider: AlbumStaticProvider(items: newItems));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:logging/logging.dart';
|
|||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/bloc/list_album.dart';
|
||||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/entity/album/provider.dart';
|
||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/snack_bar_manager.dart';
|
||||
|
@ -139,7 +140,8 @@ class _AlbumPickerDialogState extends State<AlbumPickerDialog> {
|
|||
|
||||
void _transformItems(List<Album> albums) {
|
||||
_items.clear();
|
||||
_items.addAll(albums);
|
||||
_items.addAll(
|
||||
albums.where((element) => element.provider is AlbumStaticProvider));
|
||||
}
|
||||
|
||||
void _reqQuery() {
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:nc_photos/account.dart';
|
|||
import 'package:nc_photos/api/api.dart';
|
||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||
import 'package:nc_photos/entity/album.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/exception_util.dart' as exception_util;
|
||||
|
@ -77,6 +78,7 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
get itemStreamListCellSize => _thumbSize;
|
||||
|
||||
void _initAlbum() {
|
||||
assert(widget.album.provider is AlbumStaticProvider);
|
||||
ResyncAlbum()(widget.account, widget.album).then((album) {
|
||||
if (_shouldPropagateResyncedAlbum(album)) {
|
||||
UpdateAlbum(AlbumRepo(AlbumCachedDataSource()))(widget.account, album)
|
||||
|
@ -247,7 +249,7 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
final selectedIndexes =
|
||||
selectedListItems.map((e) => itemStreamListItems.indexOf(e)).toList();
|
||||
final selectedFiles = _backingFiles.takeIndex(selectedIndexes).toList();
|
||||
final newItems = _album.items.where((element) {
|
||||
final newItems = _getAlbumItemsOf(_album).where((element) {
|
||||
if (element is AlbumFileItem) {
|
||||
return !selectedFiles.any((select) => select.path == element.file.path);
|
||||
} else {
|
||||
|
@ -256,7 +258,9 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
}).toList();
|
||||
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
||||
final newAlbum = _album.copyWith(
|
||||
items: newItems,
|
||||
provider: AlbumStaticProvider(
|
||||
items: newItems,
|
||||
),
|
||||
);
|
||||
UpdateAlbum(albumRepo)(widget.account, newAlbum).then((_) {
|
||||
SnackBarManager().showSnackBar(SnackBar(
|
||||
|
@ -286,7 +290,7 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
}
|
||||
|
||||
void _transformItems() {
|
||||
_backingFiles = _album.items
|
||||
_backingFiles = _getAlbumItemsOf(_album)
|
||||
.whereType<AlbumFileItem>()
|
||||
.map((e) => e.file)
|
||||
.where((element) => file_util.isSupportedFormat(element))
|
||||
|
@ -320,12 +324,14 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
}
|
||||
|
||||
bool _shouldPropagateResyncedAlbum(Album album) {
|
||||
if (widget.album.items.length != album.items.length) {
|
||||
final origItems = _getAlbumItemsOf(widget.album);
|
||||
final resyncItems = _getAlbumItemsOf(album);
|
||||
if (origItems.length != resyncItems.length) {
|
||||
_log.info(
|
||||
"[_shouldPropagateResyncedAlbum] Item length differ: ${widget.album.items.length}, ${album.items.length}");
|
||||
"[_shouldPropagateResyncedAlbum] Item length differ: ${origItems.length}, ${resyncItems.length}");
|
||||
return true;
|
||||
}
|
||||
for (final z in zip([widget.album.items, album.items])) {
|
||||
for (final z in zip([origItems, resyncItems])) {
|
||||
final a = z[0], b = z[1];
|
||||
bool isEqual;
|
||||
if (a is AlbumFileItem && b is AlbumFileItem) {
|
||||
|
@ -344,6 +350,9 @@ class _AlbumViewerState extends State<AlbumViewer>
|
|||
return false;
|
||||
}
|
||||
|
||||
static List<AlbumItem> _getAlbumItemsOf(Album a) =>
|
||||
AlbumStaticProvider.of(a).items;
|
||||
|
||||
int get _thumbSize {
|
||||
switch (_thumbZoomLevel) {
|
||||
case 1:
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:nc_photos/api/api.dart';
|
|||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||
import 'package:nc_photos/bloc/list_album.dart';
|
||||
import 'package:nc_photos/entity/album.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;
|
||||
|
@ -175,10 +176,15 @@ class _HomeAlbumsState extends State<HomeAlbums> {
|
|||
|
||||
Widget _buildAlbumItem(BuildContext context, int index) {
|
||||
final item = _items[index];
|
||||
var subtitle = "";
|
||||
if (item.album.provider is AlbumStaticProvider) {
|
||||
subtitle = AppLocalizations.of(context)
|
||||
.albumSize(AlbumStaticProvider.of(item.album).items.length);
|
||||
}
|
||||
return AlbumGridItem(
|
||||
cover: _buildAlbumCover(context, item.album),
|
||||
title: item.album.name,
|
||||
subtitle: AppLocalizations.of(context).albumSize(item.album.items.length),
|
||||
subtitle: subtitle,
|
||||
isSelected: _selectedItems.contains(item),
|
||||
onTap: () => _onItemTap(item),
|
||||
onLongPress: _isSelectionMode ? null : () => _onItemLongPress(item),
|
||||
|
@ -386,7 +392,8 @@ class _HomeAlbumsState extends State<HomeAlbums> {
|
|||
final sortedAlbums = albums.map((e) {
|
||||
// find the latest file in this album
|
||||
try {
|
||||
final lastItem = e.items
|
||||
final lastItem = AlbumStaticProvider.of(e)
|
||||
.items
|
||||
.whereType<AlbumFileItem>()
|
||||
.map((e) => e.file)
|
||||
.where((element) => file_util.isSupportedFormat(element))
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:nc_photos/account.dart';
|
|||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||
import 'package:nc_photos/bloc/scan_dir.dart';
|
||||
import 'package:nc_photos/entity/album.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;
|
||||
|
@ -311,6 +312,7 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
}
|
||||
|
||||
Future<void> _addSelectedToAlbum(BuildContext context, Album album) async {
|
||||
assert(album.provider is AlbumStaticProvider);
|
||||
final selected = selectedListItems
|
||||
.whereType<_FileListItem>()
|
||||
.map((e) => AlbumFileItem(file: e.file))
|
||||
|
@ -320,10 +322,12 @@ class _HomePhotosState extends State<HomePhotos>
|
|||
await UpdateAlbum(albumRepo)(
|
||||
widget.account,
|
||||
album.copyWith(
|
||||
items: makeDistinctAlbumItems([
|
||||
...album.items,
|
||||
...selected,
|
||||
]),
|
||||
provider: AlbumStaticProvider(
|
||||
items: makeDistinctAlbumItems([
|
||||
...AlbumStaticProvider.of(album).items,
|
||||
...selected,
|
||||
]),
|
||||
),
|
||||
));
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout(
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|||
import 'package:logging/logging.dart';
|
||||
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/use_case/create_album.dart';
|
||||
|
||||
/// Dialog to create a new album
|
||||
|
@ -63,7 +64,9 @@ class _NewAlbumDialogState extends State<NewAlbumDialog> {
|
|||
_formKey.currentState.save();
|
||||
final album = Album(
|
||||
name: _formValue.name,
|
||||
items: const [],
|
||||
provider: AlbumStaticProvider(
|
||||
items: const [],
|
||||
),
|
||||
);
|
||||
_log.info("[_onOkPressed] Creating album: $album");
|
||||
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:logging/logging.dart';
|
|||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/double_extension.dart';
|
||||
import 'package:nc_photos/entity/album.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/exception_util.dart' as exception_util;
|
||||
|
@ -348,10 +349,12 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
|||
}
|
||||
|
||||
Future<void> _addToAlbum(BuildContext context, Album album) async {
|
||||
assert(album.provider is AlbumStaticProvider);
|
||||
try {
|
||||
final albumRepo = AlbumRepo(AlbumCachedDataSource());
|
||||
final newItem = AlbumFileItem(file: widget.file);
|
||||
if (album.items
|
||||
if (AlbumStaticProvider.of(album)
|
||||
.items
|
||||
.whereType<AlbumFileItem>()
|
||||
.containsIf(newItem, (a, b) => a.file.path == b.file.path)) {
|
||||
// already added, do nothing
|
||||
|
@ -366,7 +369,12 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
|||
await UpdateAlbum(albumRepo)(
|
||||
widget.account,
|
||||
album.copyWith(
|
||||
items: [...album.items, AlbumFileItem(file: widget.file)],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [
|
||||
...AlbumStaticProvider.of(album).items,
|
||||
AlbumFileItem(file: widget.file),
|
||||
],
|
||||
),
|
||||
));
|
||||
} catch (e, stacktrace) {
|
||||
_log.shout("[_addToAlbum] Failed while updating album", e, stacktrace);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:nc_photos/entity/album.dart';
|
||||
import 'package:nc_photos/entity/album/provider.dart';
|
||||
import 'package:nc_photos/entity/album/upgrader.dart';
|
||||
import 'package:nc_photos/entity/file.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
@ -10,14 +11,21 @@ void main() {
|
|||
final json = <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"items": [],
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [],
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(
|
||||
Album.fromJson(json),
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
items: [],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [],
|
||||
),
|
||||
));
|
||||
});
|
||||
|
||||
|
@ -26,54 +34,68 @@ void main() {
|
|||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "album",
|
||||
"items": [],
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [],
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(
|
||||
Album.fromJson(json),
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "album",
|
||||
items: [],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [],
|
||||
),
|
||||
));
|
||||
});
|
||||
|
||||
test("items", () {
|
||||
test("AlbumStaticProvider", () {
|
||||
final json = <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "",
|
||||
"items": [
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test2.jpg",
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test2.jpg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
};
|
||||
expect(
|
||||
Album.fromJson(json),
|
||||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
items: [
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
|
||||
),
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
|
||||
),
|
||||
],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
|
||||
),
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
});
|
||||
|
||||
|
@ -81,7 +103,12 @@ void main() {
|
|||
final json = <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"items": [],
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [],
|
||||
},
|
||||
},
|
||||
"albumFile": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||
},
|
||||
|
@ -91,7 +118,9 @@ void main() {
|
|||
Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
items: [],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [],
|
||||
),
|
||||
albumFile: File(path: "remote.php/dav/files/admin/test1.jpg"),
|
||||
));
|
||||
});
|
||||
|
@ -102,13 +131,20 @@ void main() {
|
|||
final album = Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
items: [],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [],
|
||||
),
|
||||
);
|
||||
expect(album.toRemoteJson(), <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "",
|
||||
"items": [],
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -116,51 +152,65 @@ void main() {
|
|||
final album = Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "album",
|
||||
items: [],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [],
|
||||
),
|
||||
);
|
||||
expect(album.toRemoteJson(), <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "album",
|
||||
"items": [],
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("items", () {
|
||||
test("AlbumStaticProvider", () {
|
||||
final album = Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
items: [
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
|
||||
),
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
|
||||
),
|
||||
],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
|
||||
),
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
expect(album.toRemoteJson(), <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "",
|
||||
"items": [
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test2.jpg",
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test2.jpg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -170,13 +220,20 @@ void main() {
|
|||
final album = Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
items: [],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [],
|
||||
),
|
||||
);
|
||||
expect(album.toAppDbJson(), <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "",
|
||||
"items": [],
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -184,51 +241,65 @@ void main() {
|
|||
final album = Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "album",
|
||||
items: [],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [],
|
||||
),
|
||||
);
|
||||
expect(album.toAppDbJson(), <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "album",
|
||||
"items": [],
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("items", () {
|
||||
test("AlbumStaticProvider", () {
|
||||
final album = Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
items: [
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
|
||||
),
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
|
||||
),
|
||||
],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
|
||||
),
|
||||
AlbumFileItem(
|
||||
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
expect(album.toAppDbJson(), <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "",
|
||||
"items": [
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test2.jpg",
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test2.jpg",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -236,14 +307,21 @@ void main() {
|
|||
final album = Album(
|
||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
|
||||
name: "",
|
||||
items: [],
|
||||
provider: AlbumStaticProvider(
|
||||
items: [],
|
||||
),
|
||||
albumFile: File(path: "remote.php/dav/files/admin/test1.jpg"),
|
||||
);
|
||||
expect(album.toAppDbJson(), <String, dynamic>{
|
||||
"version": Album.version,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"name": "",
|
||||
"items": [],
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [],
|
||||
},
|
||||
},
|
||||
"albumFile": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||
},
|
||||
|
@ -278,5 +356,47 @@ void main() {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("AlbumUpgraderV2", () {
|
||||
final json = <String, dynamic>{
|
||||
"version": 2,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"items": [
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
"albumFile": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.json",
|
||||
},
|
||||
};
|
||||
expect(AlbumUpgraderV2()(json), <String, dynamic>{
|
||||
"version": 2,
|
||||
"lastUpdated": "2020-01-02T03:04:05.678901Z",
|
||||
"provider": <String, dynamic>{
|
||||
"type": "static",
|
||||
"content": <String, dynamic>{
|
||||
"items": [
|
||||
<String, dynamic>{
|
||||
"type": "file",
|
||||
"content": <String, dynamic>{
|
||||
"file": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"albumFile": <String, dynamic>{
|
||||
"path": "remote.php/dav/files/admin/test1.json",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue