Fix Viewer not working with shared files in NcAlbum

This commit is contained in:
Ming Ming 2024-12-12 01:32:28 +08:00
parent 0ded662de5
commit 71201dab74
9 changed files with 152 additions and 159 deletions

View file

@ -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<StatefulWidget> 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<FileContentView> {
super.initState();
_bloc = _Bloc(
account: context.read<AccountController>().account,
filesController: context.read<AccountController>().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();
}
}
}

View file

@ -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}";
}
}

View file

@ -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 = <StreamSubscription>[];
var _isHandlingError = false;

View file

@ -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);

View file

@ -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());
},
),
);
}
}

View file

@ -15,6 +15,7 @@ part of 'viewer.dart';
abstract class $_StateCopyWithWorker {
_State call(
{List<int>? fileIdOrders,
Map<int, FileDescriptor>? rawFiles,
Map<int, FileDescriptor>? files,
Map<int, _PageState>? fileStates,
int? index,
@ -22,7 +23,7 @@ abstract class $_StateCopyWithWorker {
_PageState? currentFileState,
Collection? collection,
CollectionItemsController? collectionItemsController,
Map<int, CollectionItem>? collectionItems,
Map<int, CollectionFileItem>? 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<int>? ?? that.fileIdOrders,
rawFiles: rawFiles as Map<int, FileDescriptor>? ?? that.rawFiles,
files: files as Map<int, FileDescriptor>? ?? that.files,
fileStates: fileStates as Map<int, _PageState>? ?? 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<int, CollectionItem>?,
: collectionItems as Map<int, CollectionFileItem>?,
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

View file

@ -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<int, FileDescriptor> 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;

View file

@ -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<int> fileIdOrders;
final Map<int, FileDescriptor> rawFiles;
final Map<int, FileDescriptor> files;
final Map<int, _PageState> fileStates;
final int index;
@ -77,7 +80,7 @@ class _State {
final _PageState? currentFileState;
final Collection? collection;
final CollectionItemsController? collectionItemsController;
final Map<int, CollectionItem>? collectionItems;
final Map<int, CollectionFileItem>? collectionItems;
final bool isShowDetailPane;
final bool isClosingDetailPane;
final bool isDetailPaneActive;
@ -177,6 +180,17 @@ class _SetCollectionItems implements _Event {
final List<CollectionItem>? 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();

View file

@ -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 ??