diff --git a/app/lib/widget/slideshow_viewer.dart b/app/lib/widget/slideshow_viewer.dart index e5067061..a397fc10 100644 --- a/app/lib/widget/slideshow_viewer.dart +++ b/app/lib/widget/slideshow_viewer.dart @@ -10,7 +10,9 @@ import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/bloc_util.dart'; import 'package:nc_photos/controller/account_controller.dart'; +import 'package:nc_photos/controller/collections_controller.dart'; import 'package:nc_photos/controller/files_controller.dart'; +import 'package:nc_photos/entity/collection_item.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/k.dart' as k; @@ -25,6 +27,7 @@ import 'package:nc_photos/widget/video_viewer.dart'; import 'package:nc_photos/widget/viewer_mixin.dart'; import 'package:nc_photos/widget/wakelock_util.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_collection/np_collection.dart'; import 'package:np_common/object_util.dart'; import 'package:np_ui/np_ui.dart'; import 'package:to_string/to_string.dart'; @@ -37,18 +40,19 @@ part 'slideshow_viewer/view.dart'; class SlideshowViewerArguments { const SlideshowViewerArguments( - this.account, this.fileIds, this.startIndex, + this.collectionId, this.config, ); - final Account account; final List fileIds; final int startIndex; + final String? collectionId; final SlideshowConfig config; } +// fix for shared files class SlideshowViewer extends StatelessWidget { static const routeName = "/slideshow-viewer"; @@ -61,38 +65,41 @@ class SlideshowViewer extends StatelessWidget { const SlideshowViewer({ super.key, - required this.account, required this.fileIds, required this.startIndex, + required this.collectionId, required this.config, }); SlideshowViewer.fromArgs(SlideshowViewerArguments args, {Key? key}) : this( key: key, - account: args.account, fileIds: args.fileIds, startIndex: args.startIndex, + collectionId: args.collectionId, config: args.config, ); @override Widget build(BuildContext context) { + final accountController = context.read(); return BlocProvider( create: (context) => _Bloc( - filesController: context.read().filesController, - account: context.read().account, + account: accountController.account, + filesController: accountController.filesController, + collectionsController: accountController.collectionsController, fileIds: fileIds, startIndex: startIndex, + collectionId: collectionId, config: config, )..add(const _Init()), child: const _WrappedSlideshowViewer(), ); } - final Account account; final List fileIds; final int startIndex; + final String? collectionId; final SlideshowConfig config; } @@ -166,6 +173,7 @@ class _WrappedSlideshowViewerState extends State<_WrappedSlideshowViewer> typedef _BlocListener = BlocListener<_Bloc, _State>; typedef _BlocListenerT = BlocListenerT<_Bloc, _State, T>; typedef _BlocSelector = BlocSelector<_Bloc, _State, T>; +typedef _Emitter = Emitter<_State>; extension on BuildContext { _Bloc get bloc => read<_Bloc>(); diff --git a/app/lib/widget/slideshow_viewer.g.dart b/app/lib/widget/slideshow_viewer.g.dart index 51d8eccd..a766f052 100644 --- a/app/lib/widget/slideshow_viewer.g.dart +++ b/app/lib/widget/slideshow_viewer.g.dart @@ -18,6 +18,8 @@ abstract class $_StateCopyWithWorker { int? page, int? nextPage, bool? shouldAnimateNextPage, + Map? rawFiles, + Map? collectionItems, List? files, FileDescriptor? currentFile, bool? isShowUi, @@ -39,6 +41,8 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { dynamic page, dynamic nextPage, dynamic shouldAnimateNextPage, + dynamic rawFiles, + dynamic collectionItems = copyWithNull, dynamic files, dynamic currentFile = copyWithNull, dynamic isShowUi, @@ -55,6 +59,10 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { nextPage: nextPage as int? ?? that.nextPage, shouldAnimateNextPage: shouldAnimateNextPage as bool? ?? that.shouldAnimateNextPage, + rawFiles: rawFiles as Map? ?? that.rawFiles, + collectionItems: collectionItems == copyWithNull + ? that.collectionItems + : collectionItems as Map?, files: files as List? ?? that.files, currentFile: currentFile == copyWithNull ? that.currentFile @@ -109,7 +117,7 @@ extension _$_PageViewNpLog on _PageView { extension _$_StateToString on _State { String _$toString() { // ignore: unnecessary_string_interpolations - return "_State {hasInit: $hasInit, page: $page, nextPage: $nextPage, shouldAnimateNextPage: $shouldAnimateNextPage, files: [length: ${files.length}], currentFile: ${currentFile == null ? null : "${currentFile!.fdPath}"}, isShowUi: $isShowUi, isPlay: $isPlay, isVideoCompleted: $isVideoCompleted, hasPrev: $hasPrev, hasNext: $hasNext, isShowTimeline: $isShowTimeline, hasShownTimeline: $hasShownTimeline, hasRequestExit: $hasRequestExit}"; + return "_State {hasInit: $hasInit, page: $page, nextPage: $nextPage, shouldAnimateNextPage: $shouldAnimateNextPage, rawFiles: {length: ${rawFiles.length}}, collectionItems: ${collectionItems == null ? null : "{length: ${collectionItems!.length}}"}, files: [length: ${files.length}], currentFile: ${currentFile == null ? null : "${currentFile!.fdPath}"}, isShowUi: $isShowUi, isPlay: $isPlay, isVideoCompleted: $isVideoCompleted, hasPrev: $hasPrev, hasNext: $hasNext, isShowTimeline: $isShowTimeline, hasShownTimeline: $hasShownTimeline, hasRequestExit: $hasRequestExit}"; } } @@ -120,10 +128,10 @@ extension _$_InitToString on _Init { } } -extension _$_SetFilesToString on _SetFiles { +extension _$_MergeFilesToString on _MergeFiles { String _$toString() { // ignore: unnecessary_string_interpolations - return "_SetFiles {dataMap: {length: ${dataMap.length}}}"; + return "_MergeFiles {}"; } } @@ -210,3 +218,10 @@ extension _$_RequestExitToString on _RequestExit { return "_RequestExit {}"; } } + +extension _$_SetCollectionItemsToString on _SetCollectionItems { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "_SetCollectionItems {value: ${value == null ? null : "[length: ${value!.length}]"}}"; + } +} diff --git a/app/lib/widget/slideshow_viewer/bloc.dart b/app/lib/widget/slideshow_viewer/bloc.dart index 6f470f7a..a9ae5824 100644 --- a/app/lib/widget/slideshow_viewer/bloc.dart +++ b/app/lib/widget/slideshow_viewer/bloc.dart @@ -1,16 +1,21 @@ part of '../slideshow_viewer.dart'; @npLog -class _Bloc extends Bloc<_Event, _State> with BlocLogger { +class _Bloc extends Bloc<_Event, _State> + with BlocLogger, BlocForEachMixin<_Event, _State> { _Bloc({ - required this.filesController, required this.account, + required this.filesController, + required this.collectionsController, required this.fileIds, required this.startIndex, + required this.collectionId, required this.config, }) : super(_State.init()) { on<_Init>(_onInit); - on<_SetFiles>(_onSetFiles); + on<_SetCollectionItems>(_onSetCollectionItems); + on<_MergeFiles>(_onMergeFiles); + on<_ToggleShowUi>(_onToggleShowUi); on<_PreloadSidePages>(_onPreloadSidePages); on<_VideoCompleted>(_onVideoCompleted); @@ -24,14 +29,35 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { on<_RequestPage>(_onRequestPage); on<_RequestExit>(_onRequestExit); - _subscriptions.add(filesController.stream.listen((event) { - add(_SetFiles(event.dataMap)); + if (collectionId != null) { + _subscriptions.add(collectionsController.stream.listen((event) { + for (final c in event.data) { + if (c.collection.id == collectionId) { + _collectionItemsSubscription?.cancel(); + _collectionItemsSubscription = c.controller.stream.listen((event) { + add(_SetCollectionItems(event.items)); + }); + return; + } + } + _log.warning("[_Bloc] Collection not found: $collectionId"); + add(const _SetCollectionItems(null)); + _collectionItemsSubscription?.cancel(); + })); + } + _subscriptions.add(stream + .distinct((a, b) => + identical(a.rawFiles, b.rawFiles) && + identical(a.collectionItems, b.collectionItems)) + .listen((event) { + add(const _MergeFiles()); })); } @override Future close() { _pageChangeTimer?.cancel(); + _collectionItemsSubscription?.cancel(); for (final s in _subscriptions) { s.cancel(); } @@ -83,11 +109,40 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { )); } unawaited(_prepareNextPage()); + + await forEach( + emit, + filesController.stream, + onData: (data) => state.copyWith( + rawFiles: data.dataMap, + ), + ); } - void _onSetFiles(_SetFiles ev, Emitter<_State> emit) { + void _onSetCollectionItems(_SetCollectionItems ev, _Emitter emit) { _log.info(ev); - final files = fileIds.map((e) => ev.dataMap[e]).toList(); + final itemMap = ev.value + ?.whereType() + .map((e) => MapEntry(e.file.fdId, e)) + .toMap(); + emit(state.copyWith(collectionItems: itemMap)); + } + + void _onMergeFiles(_MergeFiles ev, _Emitter emit) { + _log.info(ev); + final Map merged; + if (collectionId == null) { + // not collection, nothing to merge + merged = state.rawFiles; + } else { + if (state.collectionItems == null) { + // collection not ready + return; + } + merged = state.rawFiles.addedAll(state.collectionItems! + .map((key, value) => MapEntry(key, value.file))); + } + final files = fileIds.map((e) => merged[e]).toList(); emit(state.copyWith(files: files)); if (state.hasInit) { emit(state.copyWith( @@ -283,10 +338,12 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { add(_NextPage(nextPage)); } - final FilesController filesController; final Account account; + final FilesController filesController; + final CollectionsController collectionsController; final List fileIds; final int startIndex; + final String? collectionId; final SlideshowConfig config; late final Map> _shuffledIndex; @@ -295,4 +352,5 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { Timer? _pageChangeTimer; final _subscriptions = []; + StreamSubscription? _collectionItemsSubscription; } diff --git a/app/lib/widget/slideshow_viewer/state_event.dart b/app/lib/widget/slideshow_viewer/state_event.dart index c85135f1..0c800418 100644 --- a/app/lib/widget/slideshow_viewer/state_event.dart +++ b/app/lib/widget/slideshow_viewer/state_event.dart @@ -8,6 +8,8 @@ class _State { required this.page, required this.nextPage, required this.shouldAnimateNextPage, + required this.rawFiles, + this.collectionItems, required this.files, this.currentFile, required this.isShowUi, @@ -25,6 +27,7 @@ class _State { page: 0, nextPage: 0, shouldAnimateNextPage: true, + rawFiles: {}, files: [], isShowUi: false, isPlay: true, @@ -43,8 +46,11 @@ class _State { final int page; final int nextPage; final bool shouldAnimateNextPage; + final Map rawFiles; + final Map? collectionItems; final List files; final FileDescriptor? currentFile; + final bool isShowUi; final bool isPlay; final bool isVideoCompleted; @@ -66,13 +72,14 @@ class _Init implements _Event { } @toString -class _SetFiles implements _Event { - const _SetFiles(this.dataMap); +/// Merge regular files with collection items. The point of doing this is to +/// support shared files in an server side shared album, as these files do not +/// have a record in filesController +class _MergeFiles implements _Event { + const _MergeFiles(); @override String toString() => _$toString(); - - final Map dataMap; } @toString @@ -178,3 +185,13 @@ class _RequestExit implements _Event { @override String toString() => _$toString(); } + +@toString +class _SetCollectionItems implements _Event { + const _SetCollectionItems(this.value); + + @override + String toString() => _$toString(); + + final List? value; +} diff --git a/app/lib/widget/viewer.dart b/app/lib/widget/viewer.dart index 0b421aca..52c8ad78 100644 --- a/app/lib/widget/viewer.dart +++ b/app/lib/widget/viewer.dart @@ -253,9 +253,9 @@ class _WrappedViewerState extends State<_WrappedViewer> final newIndex = await Navigator.of(context).pushNamed( SlideshowViewer.routeName, arguments: SlideshowViewerArguments( - slideshowRequest.value!.account, slideshowRequest.value!.fileIds, slideshowRequest.value!.startIndex, + slideshowRequest.value!.collectionId, result, ), ); diff --git a/app/lib/widget/viewer/bloc.dart b/app/lib/widget/viewer/bloc.dart index 5f695f0d..56657543 100644 --- a/app/lib/widget/viewer/bloc.dart +++ b/app/lib/widget/viewer/bloc.dart @@ -371,10 +371,10 @@ class _Bloc extends Bloc<_Event, _State> void _onStartSlideshow(_StartSlideshow ev, _Emitter emit) { _log.info(ev); final req = _SlideshowRequest( - account: account, fileIds: state.fileIdOrders, startIndex: state.fileIdOrders.indexOf(ev.fileId).let((i) => i == -1 ? 0 : i), + collectionId: collectionId, ); emit(state.copyWith(slideshowRequest: Unique(req))); } diff --git a/app/lib/widget/viewer/type.dart b/app/lib/widget/viewer/type.dart index 8d7af11e..8535509b 100644 --- a/app/lib/widget/viewer/type.dart +++ b/app/lib/widget/viewer/type.dart @@ -14,14 +14,14 @@ class _ShareRequest { class _SlideshowRequest { const _SlideshowRequest({ - required this.account, required this.fileIds, required this.startIndex, + required this.collectionId, }); - final Account account; final List fileIds; final int startIndex; + final String? collectionId; } class _SetAsRequest {