From 71201dab74c1adfc5e294bc49a32a21c3d2db03f Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Thu, 12 Dec 2024 01:32:28 +0800 Subject: [PATCH] Fix Viewer not working with shared files in NcAlbum --- app/lib/widget/file_content_view.dart | 69 +++++----- app/lib/widget/file_content_view.g.dart | 16 +-- app/lib/widget/file_content_view/bloc.dart | 20 +-- .../widget/file_content_view/state_event.dart | 14 -- app/lib/widget/file_content_view/view.dart | 127 ++++++++---------- app/lib/widget/viewer.g.dart | 16 ++- app/lib/widget/viewer/bloc.dart | 31 ++++- app/lib/widget/viewer/state_event.dart | 16 ++- app/lib/widget/viewer/view.dart | 2 +- 9 files changed, 152 insertions(+), 159 deletions(-) diff --git a/app/lib/widget/file_content_view.dart b/app/lib/widget/file_content_view.dart index d0e6d15d..47e04381 100644 --- a/app/lib/widget/file_content_view.dart +++ b/app/lib/widget/file_content_view.dart @@ -7,7 +7,6 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/bloc_util.dart'; import 'package:nc_photos/controller/account_controller.dart'; -import 'package:nc_photos/controller/files_controller.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/exception_event.dart'; @@ -29,7 +28,7 @@ part 'file_content_view/view.dart'; class FileContentView extends StatefulWidget { const FileContentView({ super.key, - required this.fileId, + required this.file, required this.shouldPlayLivePhoto, required this.canZoom, required this.canPlay, @@ -43,7 +42,7 @@ class FileContentView extends StatefulWidget { @override State createState() => _FileContentViewState(); - final int fileId; + final FileDescriptor file; final bool shouldPlayLivePhoto; final bool canZoom; final bool canPlay; @@ -60,8 +59,7 @@ class _FileContentViewState extends State { super.initState(); _bloc = _Bloc( account: context.read().account, - filesController: context.read().filesController, - fileId: widget.fileId, + file: widget.file, shouldPlayLivePhoto: widget.shouldPlayLivePhoto, canZoom: widget.canZoom, canPlay: widget.canPlay, @@ -135,40 +133,33 @@ class _WrappedFileContentView extends StatelessWidget { @override Widget build(BuildContext context) { - return _BlocSelector( - selector: (state) => state.file, - builder: (context, file) { - if (file == null) { - _log.severe("[build] File is null"); - return Container(); - } else if (file_util.isSupportedImageFormat(file)) { - return _BlocSelector( - selector: (state) => state.shouldPlayLivePhoto, - builder: (context, shouldPlayLivePhoto) { - if (shouldPlayLivePhoto) { - final livePhotoType = getLivePhotoTypeFromFile(file); - if (livePhotoType != null) { - return _LivePhotoPageContentView( - livePhotoType: livePhotoType, - ); - } else { - _log.warning("[build] Not a live photo"); - return const _PhotoPageContentView(); - } - } else { - return const _PhotoPageContentView(); - } - }, - ); - } else if (file_util.isSupportedVideoFormat(file)) { - return const _VideoPageContentView(); - } else { - _log.shout("[build] Unknown file format: ${file.fdMime}"); - // _pageStates[index]!.itemHeight = 0; - return Container(); - } - }, - ); + final file = context.bloc.file; + if (file_util.isSupportedImageFormat(file)) { + return _BlocSelector( + selector: (state) => state.shouldPlayLivePhoto, + builder: (context, shouldPlayLivePhoto) { + if (shouldPlayLivePhoto) { + final livePhotoType = getLivePhotoTypeFromFile(file); + if (livePhotoType != null) { + return _LivePhotoPageContentView( + livePhotoType: livePhotoType, + ); + } else { + _log.warning("[build] Not a live photo"); + return const _PhotoPageContentView(); + } + } else { + return const _PhotoPageContentView(); + } + }, + ); + } else if (file_util.isSupportedVideoFormat(file)) { + return const _VideoPageContentView(); + } else { + _log.shout("[build] Unknown file format: ${file.fdMime}"); + // _pageStates[index]!.itemHeight = 0; + return Container(); + } } } diff --git a/app/lib/widget/file_content_view.g.dart b/app/lib/widget/file_content_view.g.dart index 74fc6b91..2e694cc4 100644 --- a/app/lib/widget/file_content_view.g.dart +++ b/app/lib/widget/file_content_view.g.dart @@ -14,8 +14,7 @@ part of 'file_content_view.dart'; abstract class $_StateCopyWithWorker { _State call( - {FileDescriptor? file, - bool? shouldPlayLivePhoto, + {bool? shouldPlayLivePhoto, bool? canZoom, bool? canPlay, bool? isPlayControlVisible, @@ -32,8 +31,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { @override _State call( - {dynamic file = copyWithNull, - dynamic shouldPlayLivePhoto, + {dynamic shouldPlayLivePhoto, dynamic canZoom, dynamic canPlay, dynamic isPlayControlVisible, @@ -44,7 +42,6 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { dynamic contentHeight = copyWithNull, dynamic error = copyWithNull}) { return _State( - file: file == copyWithNull ? that.file : file as FileDescriptor?, shouldPlayLivePhoto: shouldPlayLivePhoto as bool? ?? that.shouldPlayLivePhoto, canZoom: canZoom as bool? ?? that.canZoom, @@ -102,14 +99,7 @@ extension _$_BlocNpLog on _Bloc { extension _$_StateToString on _State { String _$toString() { // ignore: unnecessary_string_interpolations - return "_State {file: ${file == null ? null : "${file!.fdPath}"}, shouldPlayLivePhoto: $shouldPlayLivePhoto, canZoom: $canZoom, canPlay: $canPlay, isPlayControlVisible: $isPlayControlVisible, isLoaded: $isLoaded, isZoomed: $isZoomed, isPlaying: $isPlaying, isLivePhotoLoadFailed: $isLivePhotoLoadFailed, contentHeight: ${contentHeight == null ? null : "${contentHeight!.toStringAsFixed(3)}"}, error: $error}"; - } -} - -extension _$_SetFileToString on _SetFile { - String _$toString() { - // ignore: unnecessary_string_interpolations - return "_SetFile {value: ${value == null ? null : "${value!.fdPath}"}}"; + return "_State {shouldPlayLivePhoto: $shouldPlayLivePhoto, canZoom: $canZoom, canPlay: $canPlay, isPlayControlVisible: $isPlayControlVisible, isLoaded: $isLoaded, isZoomed: $isZoomed, isPlaying: $isPlaying, isLivePhotoLoadFailed: $isLivePhotoLoadFailed, contentHeight: ${contentHeight == null ? null : "${contentHeight!.toStringAsFixed(3)}"}, error: $error}"; } } diff --git a/app/lib/widget/file_content_view/bloc.dart b/app/lib/widget/file_content_view/bloc.dart index 276721f9..c6f2014b 100644 --- a/app/lib/widget/file_content_view/bloc.dart +++ b/app/lib/widget/file_content_view/bloc.dart @@ -4,20 +4,17 @@ part of '../file_content_view.dart'; class _Bloc extends Bloc<_Event, _State> with BlocLogger { _Bloc({ required this.account, - required this.filesController, - required this.fileId, + required this.file, required bool shouldPlayLivePhoto, required bool canZoom, required bool canPlay, required bool isPlayControlVisible, }) : super(_State.init( - file: filesController.stream.value.dataMap[fileId], shouldPlayLivePhoto: shouldPlayLivePhoto, canZoom: canZoom, canPlay: canPlay, isPlayControlVisible: isPlayControlVisible, )) { - on<_SetFile>(_onSetFile); on<_SetShouldPlayLivePhoto>(_onSetShouldPlayLivePhoto); on<_SetCanZoom>(_onSetCanZoom); on<_SetCanPlay>(_onSetCanPlay); @@ -30,13 +27,6 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { on<_SetLivePhotoLoadFailed>(_onSetLivePhotoLoadFailed); on<_SetError>(_onSetError); - - _subscriptions.add(filesController.stream.listen((ev) { - add(_SetFile(ev.dataMap[fileId])); - })); - _subscriptions.add(filesController.errorStream.listen((ev) { - add(_SetError(ev.error, ev.stackTrace)); - })); } @override @@ -63,11 +53,6 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { super.onError(error, stackTrace); } - void _onSetFile(_SetFile ev, _Emitter emit) { - _log.info(ev); - emit(state.copyWith(file: ev.value)); - } - void _onSetShouldPlayLivePhoto(_SetShouldPlayLivePhoto ev, _Emitter emit) { _log.info(ev); emit(state.copyWith(shouldPlayLivePhoto: ev.value)); @@ -127,8 +112,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { } final Account account; - final FilesController filesController; - final int fileId; + final FileDescriptor file; final _subscriptions = []; var _isHandlingError = false; diff --git a/app/lib/widget/file_content_view/state_event.dart b/app/lib/widget/file_content_view/state_event.dart index b890adc9..4d2a63ab 100644 --- a/app/lib/widget/file_content_view/state_event.dart +++ b/app/lib/widget/file_content_view/state_event.dart @@ -4,7 +4,6 @@ part of '../file_content_view.dart'; @toString class _State { const _State({ - required this.file, required this.shouldPlayLivePhoto, required this.canZoom, required this.canPlay, @@ -18,14 +17,12 @@ class _State { }); factory _State.init({ - required FileDescriptor? file, required bool shouldPlayLivePhoto, required bool canZoom, required bool canPlay, required bool isPlayControlVisible, }) => _State( - file: file, shouldPlayLivePhoto: shouldPlayLivePhoto, canZoom: canZoom, canPlay: canPlay, @@ -39,7 +36,6 @@ class _State { @override String toString() => _$toString(); - final FileDescriptor? file; final bool shouldPlayLivePhoto; final bool canZoom; final bool canPlay; @@ -55,16 +51,6 @@ class _State { abstract class _Event {} -@toString -class _SetFile implements _Event { - const _SetFile(this.value); - - @override - String toString() => _$toString(); - - final FileDescriptor? value; -} - @toString class _SetShouldPlayLivePhoto implements _Event { const _SetShouldPlayLivePhoto(this.value); diff --git a/app/lib/widget/file_content_view/view.dart b/app/lib/widget/file_content_view/view.dart index c164ac2b..13de15b3 100644 --- a/app/lib/widget/file_content_view/view.dart +++ b/app/lib/widget/file_content_view/view.dart @@ -8,25 +8,22 @@ class _LivePhotoPageContentView extends StatelessWidget { @override Widget build(BuildContext context) { return _BlocBuilder( - buildWhen: (previous, current) => - previous.file != current.file || previous.canPlay != current.canPlay, - builder: (context, state) => state.file == null - ? Container() - : LivePhotoViewer( - account: context.bloc.account, - file: state.file!, - livePhotoType: livePhotoType, - canPlay: state.canPlay, - onLoaded: () { - context.addEvent(const _SetLoaded()); - }, - onHeightChanged: (height) { - context.addEvent(_SetContentHeight(height)); - }, - onLoadFailure: () { - context.addEvent(const _SetLivePhotoLoadFailed()); - }, - ), + buildWhen: (previous, current) => previous.canPlay != current.canPlay, + builder: (context, state) => LivePhotoViewer( + account: context.bloc.account, + file: context.bloc.file, + livePhotoType: livePhotoType, + canPlay: state.canPlay, + onLoaded: () { + context.addEvent(const _SetLoaded()); + }, + onHeightChanged: (height) { + context.addEvent(_SetContentHeight(height)); + }, + onLoadFailure: () { + context.addEvent(const _SetLivePhotoLoadFailed()); + }, + ), ); } @@ -39,27 +36,24 @@ class _PhotoPageContentView extends StatelessWidget { @override Widget build(BuildContext context) { return _BlocBuilder( - buildWhen: (previous, current) => - previous.file != current.file || previous.canZoom != current.canZoom, - builder: (context, state) => state.file == null - ? Container() - : RemoteImageViewer( - account: context.bloc.account, - file: state.file!, - canZoom: state.canZoom, - onLoaded: () { - context.addEvent(const _SetLoaded()); - }, - onHeightChanged: (height) { - context.addEvent(_SetContentHeight(height)); - }, - onZoomStarted: () { - context.addEvent(const _SetIsZoomed(true)); - }, - onZoomEnded: () { - context.addEvent(const _SetIsZoomed(false)); - }, - ), + buildWhen: (previous, current) => previous.canZoom != current.canZoom, + builder: (context, state) => RemoteImageViewer( + account: context.bloc.account, + file: context.bloc.file, + canZoom: state.canZoom, + onLoaded: () { + context.addEvent(const _SetLoaded()); + }, + onHeightChanged: (height) { + context.addEvent(_SetContentHeight(height)); + }, + onZoomStarted: () { + context.addEvent(const _SetIsZoomed(true)); + }, + onZoomEnded: () { + context.addEvent(const _SetIsZoomed(false)); + }, + ), ); } } @@ -71,37 +65,34 @@ class _VideoPageContentView extends StatelessWidget { Widget build(BuildContext context) { return _BlocBuilder( buildWhen: (previous, current) => - previous.file != current.file || previous.canZoom != current.canZoom || previous.isPlayControlVisible != current.isPlayControlVisible || previous.canPlay != current.canPlay, - builder: (context, state) => state.file == null - ? Container() - : VideoViewer( - account: context.bloc.account, - file: state.file!, - canZoom: state.canZoom, - canPlay: state.canPlay, - isControlVisible: state.isPlayControlVisible, - onLoaded: () { - context.addEvent(const _SetLoaded()); - }, - onHeightChanged: (height) { - context.addEvent(_SetContentHeight(height)); - }, - onZoomStarted: () { - context.addEvent(const _SetIsZoomed(true)); - }, - onZoomEnded: () { - context.addEvent(const _SetIsZoomed(false)); - }, - onPlay: () { - context.addEvent(const _SetPlaying()); - }, - onPause: () { - context.addEvent(const _SetPause()); - }, - ), + builder: (context, state) => VideoViewer( + account: context.bloc.account, + file: context.bloc.file, + canZoom: state.canZoom, + canPlay: state.canPlay, + isControlVisible: state.isPlayControlVisible, + onLoaded: () { + context.addEvent(const _SetLoaded()); + }, + onHeightChanged: (height) { + context.addEvent(_SetContentHeight(height)); + }, + onZoomStarted: () { + context.addEvent(const _SetIsZoomed(true)); + }, + onZoomEnded: () { + context.addEvent(const _SetIsZoomed(false)); + }, + onPlay: () { + context.addEvent(const _SetPlaying()); + }, + onPause: () { + context.addEvent(const _SetPause()); + }, + ), ); } } diff --git a/app/lib/widget/viewer.g.dart b/app/lib/widget/viewer.g.dart index fb56c6d1..fab1e2c4 100644 --- a/app/lib/widget/viewer.g.dart +++ b/app/lib/widget/viewer.g.dart @@ -15,6 +15,7 @@ part of 'viewer.dart'; abstract class $_StateCopyWithWorker { _State call( {List? fileIdOrders, + Map? rawFiles, Map? files, Map? fileStates, int? index, @@ -22,7 +23,7 @@ abstract class $_StateCopyWithWorker { _PageState? currentFileState, Collection? collection, CollectionItemsController? collectionItemsController, - Map? collectionItems, + Map? collectionItems, bool? isShowDetailPane, bool? isClosingDetailPane, bool? isDetailPaneActive, @@ -48,6 +49,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { @override _State call( {dynamic fileIdOrders, + dynamic rawFiles, dynamic files, dynamic fileStates, dynamic index, @@ -75,6 +77,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { dynamic error = copyWithNull}) { return _State( fileIdOrders: fileIdOrders as List? ?? that.fileIdOrders, + rawFiles: rawFiles as Map? ?? that.rawFiles, files: files as Map? ?? that.files, fileStates: fileStates as Map? ?? that.fileStates, index: index as int? ?? that.index, @@ -92,7 +95,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { : collectionItemsController as CollectionItemsController?, collectionItems: collectionItems == copyWithNull ? that.collectionItems - : collectionItems as Map?, + : collectionItems as Map?, isShowDetailPane: isShowDetailPane as bool? ?? that.isShowDetailPane, isClosingDetailPane: isClosingDetailPane as bool? ?? that.isClosingDetailPane, @@ -206,7 +209,7 @@ extension _$_PageViewStateNpLog on _PageViewState { extension _$_StateToString on _State { String _$toString() { // ignore: unnecessary_string_interpolations - return "_State {fileIdOrders: $fileIdOrders, files: {length: ${files.length}}, fileStates: {length: ${fileStates.length}}, index: $index, currentFile: ${currentFile == null ? null : "${currentFile!.fdPath}"}, currentFileState: $currentFileState, collection: $collection, collectionItemsController: $collectionItemsController, collectionItems: ${collectionItems == null ? null : "{length: ${collectionItems!.length}}"}, isShowDetailPane: $isShowDetailPane, isClosingDetailPane: $isClosingDetailPane, isDetailPaneActive: $isDetailPaneActive, openDetailPaneRequest: $openDetailPaneRequest, closeDetailPane: $closeDetailPane, isZoomed: $isZoomed, isInitialLoad: $isInitialLoad, isShowAppBar: $isShowAppBar, appBarButtons: [length: ${appBarButtons.length}], bottomAppBarButtons: [length: ${bottomAppBarButtons.length}], pendingRemovePage: $pendingRemovePage, imageEditorRequest: $imageEditorRequest, imageEnhancerRequest: $imageEnhancerRequest, shareRequest: $shareRequest, slideshowRequest: $slideshowRequest, setAsRequest: $setAsRequest, error: $error}"; + return "_State {fileIdOrders: $fileIdOrders, rawFiles: {length: ${rawFiles.length}}, files: {length: ${files.length}}, fileStates: {length: ${fileStates.length}}, index: $index, currentFile: ${currentFile == null ? null : "${currentFile!.fdPath}"}, currentFileState: $currentFileState, collection: $collection, collectionItemsController: $collectionItemsController, collectionItems: ${collectionItems == null ? null : "{length: ${collectionItems!.length}}"}, isShowDetailPane: $isShowDetailPane, isClosingDetailPane: $isClosingDetailPane, isDetailPaneActive: $isDetailPaneActive, openDetailPaneRequest: $openDetailPaneRequest, closeDetailPane: $closeDetailPane, isZoomed: $isZoomed, isInitialLoad: $isInitialLoad, isShowAppBar: $isShowAppBar, appBarButtons: [length: ${appBarButtons.length}], bottomAppBarButtons: [length: ${bottomAppBarButtons.length}], pendingRemovePage: $pendingRemovePage, imageEditorRequest: $imageEditorRequest, imageEnhancerRequest: $imageEnhancerRequest, shareRequest: $shareRequest, slideshowRequest: $slideshowRequest, setAsRequest: $setAsRequest, error: $error}"; } } @@ -252,6 +255,13 @@ extension _$_SetCollectionItemsToString on _SetCollectionItems { } } +extension _$_MergeFilesToString on _MergeFiles { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "_MergeFiles {}"; + } +} + extension _$_ToggleAppBarToString on _ToggleAppBar { String _$toString() { // ignore: unnecessary_string_interpolations diff --git a/app/lib/widget/viewer/bloc.dart b/app/lib/widget/viewer/bloc.dart index 42ccd739..5f695f0d 100644 --- a/app/lib/widget/viewer/bloc.dart +++ b/app/lib/widget/viewer/bloc.dart @@ -26,6 +26,7 @@ class _Bloc extends Bloc<_Event, _State> on<_RequestPage>(_onRequestPage); on<_SetCollection>(_onSetCollection); on<_SetCollectionItems>(_onSetCollectionItems); + on<_MergeFiles>(_onMergeFiles); on<_ToggleAppBar>(_onToggleAppBar); on<_ShowAppBar>(_onShowAppBar); @@ -86,6 +87,13 @@ class _Bloc extends Bloc<_Event, _State> .add(prefController.viewerBottomAppBarButtonsChange.listen((event) { add(_SetBottomAppBarButtons(event)); })); + _subscriptions.add(stream + .distinct((a, b) => + identical(a.rawFiles, b.rawFiles) && + identical(a.collectionItems, b.collectionItems)) + .listen((event) { + add(const _MergeFiles()); + })); add(_SetIndex(startIndex)); } @@ -121,8 +129,7 @@ class _Bloc extends Bloc<_Event, _State> emit, filesController.stream, onData: (data) => state.copyWith( - files: data.dataMap, - currentFile: data.dataMap[state.fileIdOrders[state.index]], + rawFiles: data.dataMap, ), ), forEach( @@ -176,6 +183,26 @@ class _Bloc extends Bloc<_Event, _State> emit(state.copyWith(collectionItems: itemMap)); } + void _onMergeFiles(_MergeFiles ev, _Emitter emit) { + _log.info(ev); + final Map files; + if (collectionId == null) { + // not collection, nothing to merge + files = state.rawFiles; + } else { + if (state.collectionItems == null) { + // collection not ready + return; + } + files = state.rawFiles.addedAll(state.collectionItems! + .map((key, value) => MapEntry(key, value.file))); + } + emit(state.copyWith( + files: files, + currentFile: files[state.fileIdOrders[state.index]], + )); + } + void _onToggleAppBar(_ToggleAppBar ev, _Emitter emit) { _log.info(ev); final to = !state.isShowAppBar; diff --git a/app/lib/widget/viewer/state_event.dart b/app/lib/widget/viewer/state_event.dart index b3e6a8c6..1e9899ce 100644 --- a/app/lib/widget/viewer/state_event.dart +++ b/app/lib/widget/viewer/state_event.dart @@ -5,6 +5,7 @@ part of '../viewer.dart'; class _State { const _State({ required this.fileIdOrders, + required this.rawFiles, required this.files, required this.fileStates, required this.index, @@ -41,6 +42,7 @@ class _State { }) => _State( fileIdOrders: fileIds, + rawFiles: const {}, files: const {}, fileStates: const {}, index: index, @@ -70,6 +72,7 @@ class _State { @Format(r"$$?") final List fileIdOrders; + final Map rawFiles; final Map files; final Map fileStates; final int index; @@ -77,7 +80,7 @@ class _State { final _PageState? currentFileState; final Collection? collection; final CollectionItemsController? collectionItemsController; - final Map? collectionItems; + final Map? collectionItems; final bool isShowDetailPane; final bool isClosingDetailPane; final bool isDetailPaneActive; @@ -177,6 +180,17 @@ class _SetCollectionItems implements _Event { final List? value; } +@toString +/// 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(); +} + @toString class _ToggleAppBar implements _Event { const _ToggleAppBar(); diff --git a/app/lib/widget/viewer/view.dart b/app/lib/widget/viewer/view.dart index ff27e9b3..152d1034 100644 --- a/app/lib/widget/viewer/view.dart +++ b/app/lib/widget/viewer/view.dart @@ -257,7 +257,7 @@ class _PageViewState extends State<_PageView> { previous.isDetailPaneActive != current.isDetailPaneActive, builder: (context, state) => FileContentView( - fileId: file.fdId, + file: file, shouldPlayLivePhoto: state .fileStates[widget.fileId] ?.shouldPlayLivePhoto ??