mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 16:56:19 +01:00
Migrate collections browser to use files controller
This commit is contained in:
parent
f1a340d550
commit
db8f93b052
5 changed files with 137 additions and 55 deletions
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
import 'package:bloc_concurrency/bloc_concurrency.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
@ -19,8 +20,8 @@ import 'package:nc_photos/cache_manager_util.dart';
|
||||||
import 'package:nc_photos/controller/account_controller.dart';
|
import 'package:nc_photos/controller/account_controller.dart';
|
||||||
import 'package:nc_photos/controller/collection_items_controller.dart';
|
import 'package:nc_photos/controller/collection_items_controller.dart';
|
||||||
import 'package:nc_photos/controller/collections_controller.dart';
|
import 'package:nc_photos/controller/collections_controller.dart';
|
||||||
|
import 'package:nc_photos/controller/files_controller.dart';
|
||||||
import 'package:nc_photos/controller/pref_controller.dart';
|
import 'package:nc_photos/controller/pref_controller.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/download_handler.dart';
|
import 'package:nc_photos/download_handler.dart';
|
||||||
import 'package:nc_photos/entity/collection.dart';
|
import 'package:nc_photos/entity/collection.dart';
|
||||||
|
@ -41,9 +42,6 @@ import 'package:nc_photos/np_api_util.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/session_storage.dart';
|
import 'package:nc_photos/session_storage.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/use_case/archive_file.dart';
|
|
||||||
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
|
||||||
import 'package:nc_photos/use_case/remove.dart';
|
|
||||||
import 'package:nc_photos/widget/album_share_outlier_browser.dart';
|
import 'package:nc_photos/widget/album_share_outlier_browser.dart';
|
||||||
import 'package:nc_photos/widget/collection_picker.dart';
|
import 'package:nc_photos/widget/collection_picker.dart';
|
||||||
import 'package:nc_photos/widget/draggable_item_list.dart';
|
import 'package:nc_photos/widget/draggable_item_list.dart';
|
||||||
|
@ -103,13 +101,14 @@ class CollectionBrowser extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final accountController = context.read<AccountController>();
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => _Bloc(
|
create: (_) => _Bloc(
|
||||||
container: KiwiContainer().resolve(),
|
container: KiwiContainer().resolve(),
|
||||||
account: context.read<AccountController>().account,
|
account: accountController.account,
|
||||||
prefController: context.read(),
|
prefController: context.read(),
|
||||||
collectionsController:
|
collectionsController: accountController.collectionsController,
|
||||||
context.read<AccountController>().collectionsController,
|
filesController: accountController.filesController,
|
||||||
collection: collection,
|
collection: collection,
|
||||||
),
|
),
|
||||||
child: const _WrappedCollectionBrowser(),
|
child: const _WrappedCollectionBrowser(),
|
||||||
|
@ -211,14 +210,22 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_BlocListener(
|
_BlocListenerT<ExceptionEvent?>(
|
||||||
listenWhen: (previous, current) =>
|
selector: (state) => state.error,
|
||||||
previous.error != current.error,
|
listener: (context, error) {
|
||||||
listener: (context, state) {
|
if (error != null && isPageVisible()) {
|
||||||
if (state.error != null && isPageVisible()) {
|
final String content;
|
||||||
|
if (error.error is _ArchiveFailedError) {
|
||||||
|
content = L10n.global().archiveSelectedFailureNotification(
|
||||||
|
(error.error as _ArchiveFailedError).count);
|
||||||
|
} else if (error.error is _RemoveFailedError) {
|
||||||
|
content = L10n.global().deleteSelectedFailureNotification(
|
||||||
|
(error.error as _RemoveFailedError).count);
|
||||||
|
} else {
|
||||||
|
content = exception_util.toUserString(error.error);
|
||||||
|
}
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
content:
|
content: Text(content),
|
||||||
Text(exception_util.toUserString(state.error!.error)),
|
|
||||||
duration: k.snackBarDurationNormal,
|
duration: k.snackBarDurationNormal,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -395,6 +402,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
|
|
||||||
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||||
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||||
|
typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
||||||
// typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
// typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||||
|
|
||||||
extension on BuildContext {
|
extension on BuildContext {
|
||||||
|
|
|
@ -17,6 +17,8 @@ abstract class $_StateCopyWithWorker {
|
||||||
{Collection? collection,
|
{Collection? collection,
|
||||||
String? coverUrl,
|
String? coverUrl,
|
||||||
List<CollectionItem>? items,
|
List<CollectionItem>? items,
|
||||||
|
List<CollectionItem>? rawItems,
|
||||||
|
Set<int>? itemsWhitelist,
|
||||||
bool? isLoading,
|
bool? isLoading,
|
||||||
List<_Item>? transformedItems,
|
List<_Item>? transformedItems,
|
||||||
Set<_Item>? selectedItems,
|
Set<_Item>? selectedItems,
|
||||||
|
@ -45,6 +47,8 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
{dynamic collection,
|
{dynamic collection,
|
||||||
dynamic coverUrl = copyWithNull,
|
dynamic coverUrl = copyWithNull,
|
||||||
dynamic items,
|
dynamic items,
|
||||||
|
dynamic rawItems,
|
||||||
|
dynamic itemsWhitelist = copyWithNull,
|
||||||
dynamic isLoading,
|
dynamic isLoading,
|
||||||
dynamic transformedItems,
|
dynamic transformedItems,
|
||||||
dynamic selectedItems,
|
dynamic selectedItems,
|
||||||
|
@ -68,6 +72,10 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
coverUrl:
|
coverUrl:
|
||||||
coverUrl == copyWithNull ? that.coverUrl : coverUrl as String?,
|
coverUrl == copyWithNull ? that.coverUrl : coverUrl as String?,
|
||||||
items: items as List<CollectionItem>? ?? that.items,
|
items: items as List<CollectionItem>? ?? that.items,
|
||||||
|
rawItems: rawItems as List<CollectionItem>? ?? that.rawItems,
|
||||||
|
itemsWhitelist: itemsWhitelist == copyWithNull
|
||||||
|
? that.itemsWhitelist
|
||||||
|
: itemsWhitelist as Set<int>?,
|
||||||
isLoading: isLoading as bool? ?? that.isLoading,
|
isLoading: isLoading as bool? ?? that.isLoading,
|
||||||
transformedItems:
|
transformedItems:
|
||||||
transformedItems as List<_Item>? ?? that.transformedItems,
|
transformedItems as List<_Item>? ?? that.transformedItems,
|
||||||
|
@ -143,7 +151,7 @@ extension _$_BlocNpLog on _Bloc {
|
||||||
extension _$_StateToString on _State {
|
extension _$_StateToString on _State {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// ignore: unnecessary_string_interpolations
|
||||||
return "_State {collection: $collection, coverUrl: $coverUrl, items: [length: ${items.length}], isLoading: $isLoading, transformedItems: [length: ${transformedItems.length}], selectedItems: {length: ${selectedItems.length}}, isSelectionRemovable: $isSelectionRemovable, isSelectionManageableFile: $isSelectionManageableFile, isSelectionDeletable: $isSelectionDeletable, isEditMode: $isEditMode, isEditBusy: $isEditBusy, editName: $editName, editItems: ${editItems == null ? null : "[length: ${editItems!.length}]"}, editTransformedItems: ${editTransformedItems == null ? null : "[length: ${editTransformedItems!.length}]"}, editSort: ${editSort == null ? null : "${editSort!.name}"}, isDragging: $isDragging, zoom: $zoom, scale: ${scale == null ? null : "${scale!.toStringAsFixed(3)}"}, importResult: $importResult, error: $error, message: $message}";
|
return "_State {collection: $collection, coverUrl: $coverUrl, items: [length: ${items.length}], rawItems: [length: ${rawItems.length}], itemsWhitelist: ${itemsWhitelist == null ? null : "{length: ${itemsWhitelist!.length}}"}, isLoading: $isLoading, transformedItems: [length: ${transformedItems.length}], selectedItems: {length: ${selectedItems.length}}, isSelectionRemovable: $isSelectionRemovable, isSelectionManageableFile: $isSelectionManageableFile, isSelectionDeletable: $isSelectionDeletable, isEditMode: $isEditMode, isEditBusy: $isEditBusy, editName: $editName, editItems: ${editItems == null ? null : "[length: ${editItems!.length}]"}, editTransformedItems: ${editTransformedItems == null ? null : "[length: ${editTransformedItems!.length}]"}, editSort: ${editSort == null ? null : "${editSort!.name}"}, isDragging: $isDragging, zoom: $zoom, scale: ${scale == null ? null : "${scale!.toStringAsFixed(3)}"}, importResult: $importResult, error: $error, message: $message}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,3 +346,17 @@ extension _$_SetMessageToString on _SetMessage {
|
||||||
return "_SetMessage {message: $message}";
|
return "_SetMessage {message: $message}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension _$_ArchiveFailedErrorToString on _ArchiveFailedError {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_ArchiveFailedError {count: $count}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_RemoveFailedErrorToString on _RemoveFailedError {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_RemoveFailedError {count: $count}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
required this.account,
|
required this.account,
|
||||||
required this.prefController,
|
required this.prefController,
|
||||||
required this.collectionsController,
|
required this.collectionsController,
|
||||||
|
required this.filesController,
|
||||||
required Collection collection,
|
required Collection collection,
|
||||||
}) : _c = container,
|
}) : _c = container,
|
||||||
_isAdHocCollection = !collectionsController.stream.value.data
|
_isAdHocCollection = !collectionsController.stream.value.data
|
||||||
|
@ -109,10 +110,12 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
|
|
||||||
Future<void> _onLoad(_LoadItems ev, Emitter<_State> emit) async {
|
Future<void> _onLoad(_LoadItems ev, Emitter<_State> emit) async {
|
||||||
_log.info(ev);
|
_log.info(ev);
|
||||||
return emit.forEach<CollectionItemStreamData>(
|
await Future.wait([
|
||||||
|
emit.forEach<CollectionItemStreamData>(
|
||||||
itemsController.stream,
|
itemsController.stream,
|
||||||
onData: (data) => state.copyWith(
|
onData: (data) => state.copyWith(
|
||||||
items: data.items,
|
items: _filterItems(data.items, state.itemsWhitelist),
|
||||||
|
rawItems: data.items,
|
||||||
isLoading: data.hasNext,
|
isLoading: data.hasNext,
|
||||||
),
|
),
|
||||||
onError: (e, stackTrace) {
|
onError: (e, stackTrace) {
|
||||||
|
@ -122,7 +125,25 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
error: ExceptionEvent(e, stackTrace),
|
error: ExceptionEvent(e, stackTrace),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
emit.forEach<FilesStreamEvent>(
|
||||||
|
filesController.stream,
|
||||||
|
onData: (data) {
|
||||||
|
final whitelist = HashSet.of(data.dataMap.keys);
|
||||||
|
return state.copyWith(
|
||||||
|
items: _filterItems(state.rawItems, whitelist),
|
||||||
|
itemsWhitelist: whitelist,
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
onError: (e, stackTrace) {
|
||||||
|
_log.severe("[_onLoad] Uncaught exception", e, stackTrace);
|
||||||
|
return state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
error: ExceptionEvent(e, stackTrace),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onTransformItems(_TransformItems ev, Emitter<_State> emit) {
|
void _onTransformItems(_TransformItems ev, Emitter<_State> emit) {
|
||||||
|
@ -306,23 +327,18 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onArchiveSelectedItems(
|
void _onArchiveSelectedItems(_ArchiveSelectedItems ev, Emitter<_State> emit) {
|
||||||
_ArchiveSelectedItems ev, Emitter<_State> emit) async {
|
|
||||||
_log.info(ev);
|
_log.info(ev);
|
||||||
final selected = state.selectedItems;
|
final selected = state.selectedItems;
|
||||||
_clearSelection(emit);
|
_clearSelection(emit);
|
||||||
final selectedFds =
|
|
||||||
selected.whereType<_FileItem>().map((e) => e.file).toList();
|
|
||||||
if (selectedFds.isNotEmpty) {
|
|
||||||
final selectedFiles =
|
final selectedFiles =
|
||||||
await InflateFileDescriptor(_c)(account, selectedFds);
|
selected.whereType<_FileItem>().map((e) => e.file).toList();
|
||||||
final count = await ArchiveFile(_c)(account, selectedFiles);
|
if (selectedFiles.isNotEmpty) {
|
||||||
if (count != selectedFiles.length) {
|
filesController.updateProperty(
|
||||||
emit(state.copyWith(
|
selectedFiles,
|
||||||
message: L10n.global()
|
isArchived: const OrNull(true),
|
||||||
.archiveSelectedFailureNotification(selectedFiles.length - count),
|
errorBuilder: (fileIds) => _ArchiveFailedError(fileIds.length),
|
||||||
));
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,29 +348,25 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
final selected = state.selectedItems;
|
final selected = state.selectedItems;
|
||||||
_clearSelection(emit);
|
_clearSelection(emit);
|
||||||
final adapter = CollectionAdapter.of(_c, account, state.collection);
|
final adapter = CollectionAdapter.of(_c, account, state.collection);
|
||||||
|
final selectedItems = selected
|
||||||
|
.whereType<_ActualItem>()
|
||||||
|
.map((e) => e.original)
|
||||||
|
.where(adapter.isItemRemovable)
|
||||||
|
.toList();
|
||||||
final selectedFiles = selected
|
final selectedFiles = selected
|
||||||
.whereType<_FileItem>()
|
.whereType<_FileItem>()
|
||||||
.where((e) => adapter.isItemDeletable(e.original))
|
.where((e) => adapter.isItemDeletable(e.original))
|
||||||
.map((e) => e.file)
|
.map((e) => e.file)
|
||||||
.toList();
|
.toList();
|
||||||
if (selectedFiles.isNotEmpty) {
|
if (selectedFiles.isNotEmpty) {
|
||||||
final count = await Remove(_c)(
|
await filesController.remove(
|
||||||
account,
|
|
||||||
selectedFiles,
|
selectedFiles,
|
||||||
onError: (_, f, e, stackTrace) {
|
errorBuilder: (fileIds) {
|
||||||
_log.severe(
|
return _RemoveFailedError(fileIds.length);
|
||||||
"[_onDeleteSelectedItems] Failed while Remove: ${logFilename(f.strippedPath)}",
|
|
||||||
e,
|
|
||||||
stackTrace,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (count != selectedFiles.length) {
|
// deleting files will also remove them from the collection
|
||||||
emit(state.copyWith(
|
unawaited(itemsController.removeItems(selectedItems));
|
||||||
message: L10n.global()
|
|
||||||
.deleteSelectedFailureNotification(selectedFiles.length - count),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,6 +500,20 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<CollectionItem> _filterItems(
|
||||||
|
List<CollectionItem> rawItems, Set<int>? whitelist) {
|
||||||
|
if (whitelist == null) {
|
||||||
|
return rawItems;
|
||||||
|
}
|
||||||
|
return rawItems.where((e) {
|
||||||
|
if (e is CollectionFileItem) {
|
||||||
|
return whitelist.contains(e.file.fdId);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
String? _getCoverUrlByItems() {
|
String? _getCoverUrlByItems() {
|
||||||
try {
|
try {
|
||||||
final firstFile =
|
final firstFile =
|
||||||
|
@ -524,6 +550,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
final Account account;
|
final Account account;
|
||||||
final PrefController prefController;
|
final PrefController prefController;
|
||||||
final CollectionsController collectionsController;
|
final CollectionsController collectionsController;
|
||||||
|
final FilesController filesController;
|
||||||
late final CollectionItemsController itemsController;
|
late final CollectionItemsController itemsController;
|
||||||
|
|
||||||
/// Specify if the supplied [collection] is an "inline" one, which means it's
|
/// Specify if the supplied [collection] is an "inline" one, which means it's
|
||||||
|
|
|
@ -7,6 +7,8 @@ class _State {
|
||||||
required this.collection,
|
required this.collection,
|
||||||
this.coverUrl,
|
this.coverUrl,
|
||||||
required this.items,
|
required this.items,
|
||||||
|
required this.rawItems,
|
||||||
|
this.itemsWhitelist,
|
||||||
required this.isLoading,
|
required this.isLoading,
|
||||||
required this.transformedItems,
|
required this.transformedItems,
|
||||||
required this.selectedItems,
|
required this.selectedItems,
|
||||||
|
@ -36,6 +38,7 @@ class _State {
|
||||||
collection: collection,
|
collection: collection,
|
||||||
coverUrl: coverUrl,
|
coverUrl: coverUrl,
|
||||||
items: const [],
|
items: const [],
|
||||||
|
rawItems: const [],
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
transformedItems: const [],
|
transformedItems: const [],
|
||||||
selectedItems: const {},
|
selectedItems: const {},
|
||||||
|
@ -57,6 +60,8 @@ class _State {
|
||||||
final Collection collection;
|
final Collection collection;
|
||||||
final String? coverUrl;
|
final String? coverUrl;
|
||||||
final List<CollectionItem> items;
|
final List<CollectionItem> items;
|
||||||
|
final List<CollectionItem> rawItems;
|
||||||
|
final Set<int>? itemsWhitelist;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final List<_Item> transformedItems;
|
final List<_Item> transformedItems;
|
||||||
|
|
||||||
|
|
|
@ -174,3 +174,23 @@ class _DateItem extends _Item {
|
||||||
|
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _ArchiveFailedError implements Exception {
|
||||||
|
const _ArchiveFailedError(this.count);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final int count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _RemoveFailedError implements Exception {
|
||||||
|
const _RemoveFailedError(this.count);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final int count;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue