mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +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:collection';
|
||||
|
||||
import 'package:bloc_concurrency/bloc_concurrency.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/collection_items_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/debug_util.dart';
|
||||
import 'package:nc_photos/di_container.dart';
|
||||
import 'package:nc_photos/download_handler.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/session_storage.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/collection_picker.dart';
|
||||
import 'package:nc_photos/widget/draggable_item_list.dart';
|
||||
|
@ -103,13 +101,14 @@ class CollectionBrowser extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final accountController = context.read<AccountController>();
|
||||
return BlocProvider(
|
||||
create: (_) => _Bloc(
|
||||
container: KiwiContainer().resolve(),
|
||||
account: context.read<AccountController>().account,
|
||||
account: accountController.account,
|
||||
prefController: context.read(),
|
||||
collectionsController:
|
||||
context.read<AccountController>().collectionsController,
|
||||
collectionsController: accountController.collectionsController,
|
||||
filesController: accountController.filesController,
|
||||
collection: collection,
|
||||
),
|
||||
child: const _WrappedCollectionBrowser(),
|
||||
|
@ -211,14 +210,22 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
|||
}
|
||||
},
|
||||
),
|
||||
_BlocListener(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.error != current.error,
|
||||
listener: (context, state) {
|
||||
if (state.error != null && isPageVisible()) {
|
||||
_BlocListenerT<ExceptionEvent?>(
|
||||
selector: (state) => state.error,
|
||||
listener: (context, error) {
|
||||
if (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(
|
||||
content:
|
||||
Text(exception_util.toUserString(state.error!.error)),
|
||||
content: Text(content),
|
||||
duration: k.snackBarDurationNormal,
|
||||
));
|
||||
}
|
||||
|
@ -395,6 +402,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
|||
|
||||
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||
typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
||||
// typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||
|
||||
extension on BuildContext {
|
||||
|
|
|
@ -17,6 +17,8 @@ abstract class $_StateCopyWithWorker {
|
|||
{Collection? collection,
|
||||
String? coverUrl,
|
||||
List<CollectionItem>? items,
|
||||
List<CollectionItem>? rawItems,
|
||||
Set<int>? itemsWhitelist,
|
||||
bool? isLoading,
|
||||
List<_Item>? transformedItems,
|
||||
Set<_Item>? selectedItems,
|
||||
|
@ -45,6 +47,8 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
|||
{dynamic collection,
|
||||
dynamic coverUrl = copyWithNull,
|
||||
dynamic items,
|
||||
dynamic rawItems,
|
||||
dynamic itemsWhitelist = copyWithNull,
|
||||
dynamic isLoading,
|
||||
dynamic transformedItems,
|
||||
dynamic selectedItems,
|
||||
|
@ -68,6 +72,10 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
|||
coverUrl:
|
||||
coverUrl == copyWithNull ? that.coverUrl : coverUrl as String?,
|
||||
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,
|
||||
transformedItems:
|
||||
transformedItems as List<_Item>? ?? that.transformedItems,
|
||||
|
@ -143,7 +151,7 @@ extension _$_BlocNpLog on _Bloc {
|
|||
extension _$_StateToString on _State {
|
||||
String _$toString() {
|
||||
// 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}";
|
||||
}
|
||||
}
|
||||
|
||||
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.prefController,
|
||||
required this.collectionsController,
|
||||
required this.filesController,
|
||||
required Collection collection,
|
||||
}) : _c = container,
|
||||
_isAdHocCollection = !collectionsController.stream.value.data
|
||||
|
@ -109,20 +110,40 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
|||
|
||||
Future<void> _onLoad(_LoadItems ev, Emitter<_State> emit) async {
|
||||
_log.info(ev);
|
||||
return emit.forEach<CollectionItemStreamData>(
|
||||
itemsController.stream,
|
||||
onData: (data) => state.copyWith(
|
||||
items: data.items,
|
||||
isLoading: data.hasNext,
|
||||
await Future.wait([
|
||||
emit.forEach<CollectionItemStreamData>(
|
||||
itemsController.stream,
|
||||
onData: (data) => state.copyWith(
|
||||
items: _filterItems(data.items, state.itemsWhitelist),
|
||||
rawItems: data.items,
|
||||
isLoading: data.hasNext,
|
||||
),
|
||||
onError: (e, stackTrace) {
|
||||
_log.severe("[_onLoad] Uncaught exception", e, stackTrace);
|
||||
return state.copyWith(
|
||||
isLoading: false,
|
||||
error: ExceptionEvent(e, stackTrace),
|
||||
);
|
||||
},
|
||||
),
|
||||
onError: (e, stackTrace) {
|
||||
_log.severe("[_onLoad] Uncaught exception", e, stackTrace);
|
||||
return state.copyWith(
|
||||
isLoading: false,
|
||||
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) {
|
||||
|
@ -306,23 +327,18 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _onArchiveSelectedItems(
|
||||
_ArchiveSelectedItems ev, Emitter<_State> emit) async {
|
||||
void _onArchiveSelectedItems(_ArchiveSelectedItems ev, Emitter<_State> emit) {
|
||||
_log.info(ev);
|
||||
final selected = state.selectedItems;
|
||||
_clearSelection(emit);
|
||||
final selectedFds =
|
||||
final selectedFiles =
|
||||
selected.whereType<_FileItem>().map((e) => e.file).toList();
|
||||
if (selectedFds.isNotEmpty) {
|
||||
final selectedFiles =
|
||||
await InflateFileDescriptor(_c)(account, selectedFds);
|
||||
final count = await ArchiveFile(_c)(account, selectedFiles);
|
||||
if (count != selectedFiles.length) {
|
||||
emit(state.copyWith(
|
||||
message: L10n.global()
|
||||
.archiveSelectedFailureNotification(selectedFiles.length - count),
|
||||
));
|
||||
}
|
||||
if (selectedFiles.isNotEmpty) {
|
||||
filesController.updateProperty(
|
||||
selectedFiles,
|
||||
isArchived: const OrNull(true),
|
||||
errorBuilder: (fileIds) => _ArchiveFailedError(fileIds.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,29 +348,25 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
|||
final selected = state.selectedItems;
|
||||
_clearSelection(emit);
|
||||
final adapter = CollectionAdapter.of(_c, account, state.collection);
|
||||
final selectedItems = selected
|
||||
.whereType<_ActualItem>()
|
||||
.map((e) => e.original)
|
||||
.where(adapter.isItemRemovable)
|
||||
.toList();
|
||||
final selectedFiles = selected
|
||||
.whereType<_FileItem>()
|
||||
.where((e) => adapter.isItemDeletable(e.original))
|
||||
.map((e) => e.file)
|
||||
.toList();
|
||||
if (selectedFiles.isNotEmpty) {
|
||||
final count = await Remove(_c)(
|
||||
account,
|
||||
await filesController.remove(
|
||||
selectedFiles,
|
||||
onError: (_, f, e, stackTrace) {
|
||||
_log.severe(
|
||||
"[_onDeleteSelectedItems] Failed while Remove: ${logFilename(f.strippedPath)}",
|
||||
e,
|
||||
stackTrace,
|
||||
);
|
||||
errorBuilder: (fileIds) {
|
||||
return _RemoveFailedError(fileIds.length);
|
||||
},
|
||||
);
|
||||
if (count != selectedFiles.length) {
|
||||
emit(state.copyWith(
|
||||
message: L10n.global()
|
||||
.deleteSelectedFailureNotification(selectedFiles.length - count),
|
||||
));
|
||||
}
|
||||
// deleting files will also remove them from the collection
|
||||
unawaited(itemsController.removeItems(selectedItems));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
try {
|
||||
final firstFile =
|
||||
|
@ -524,6 +550,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
|||
final Account account;
|
||||
final PrefController prefController;
|
||||
final CollectionsController collectionsController;
|
||||
final FilesController filesController;
|
||||
late final CollectionItemsController itemsController;
|
||||
|
||||
/// Specify if the supplied [collection] is an "inline" one, which means it's
|
||||
|
|
|
@ -7,6 +7,8 @@ class _State {
|
|||
required this.collection,
|
||||
this.coverUrl,
|
||||
required this.items,
|
||||
required this.rawItems,
|
||||
this.itemsWhitelist,
|
||||
required this.isLoading,
|
||||
required this.transformedItems,
|
||||
required this.selectedItems,
|
||||
|
@ -36,6 +38,7 @@ class _State {
|
|||
collection: collection,
|
||||
coverUrl: coverUrl,
|
||||
items: const [],
|
||||
rawItems: const [],
|
||||
isLoading: false,
|
||||
transformedItems: const [],
|
||||
selectedItems: const {},
|
||||
|
@ -57,6 +60,8 @@ class _State {
|
|||
final Collection collection;
|
||||
final String? coverUrl;
|
||||
final List<CollectionItem> items;
|
||||
final List<CollectionItem> rawItems;
|
||||
final Set<int>? itemsWhitelist;
|
||||
final bool isLoading;
|
||||
final List<_Item> transformedItems;
|
||||
|
||||
|
|
|
@ -174,3 +174,23 @@ class _DateItem extends _Item {
|
|||
|
||||
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