part of '../archive_browser.dart'; @npLog class _Bloc extends Bloc<_Event, _State> with BlocLogger { _Bloc({ required this.account, required this.controller, required this.prefController, }) : super(_State.init( zoom: prefController.albumBrowserZoomLevel.value, )) { on<_LoadItems>(_onLoad); on<_TransformItems>(_onTransformItems); on<_OnItemTransformed>(_onOnItemTransformed); on<_SetSelectedItems>(_onSetSelectedItems); on<_UnarchiveSelectedItems>(_onUnarchiveSelectedItems); on<_StartScaling>(_onStartScaling); on<_EndScaling>(_onEndScaling); on<_SetScale>(_onSetScale); on<_SetError>(_onSetError); } @override Future close() { for (final s in _subscriptions) { s.cancel(); } return super.close(); } @override String get tag => _log.fullName; @override bool Function(dynamic, dynamic)? get shouldLog => (currentState, nextState) { currentState = currentState as _State; nextState = nextState as _State; return currentState.scale == nextState.scale && currentState.visibleItems == nextState.visibleItems; }; @override void onError(Object error, StackTrace stackTrace) { // we need this to prevent onError being triggered recursively if (!isClosed && !_isHandlingError) { _isHandlingError = true; try { add(_SetError(error, stackTrace)); } catch (_) {} _isHandlingError = false; } super.onError(error, stackTrace); } Future _onLoad(_LoadItems ev, Emitter<_State> emit) { _log.info(ev); return emit.forEach( controller.stream, onData: (data) => state.copyWith( files: data.data, isLoading: data.hasNext || _itemTransformerQueue.isProcessing, ), onError: (e, stackTrace) { _log.severe("[_onLoad] Uncaught exception", e, stackTrace); return state.copyWith( isLoading: _itemTransformerQueue.isProcessing, error: ExceptionEvent(e, stackTrace), ); }, ); } void _onTransformItems(_TransformItems ev, Emitter<_State> emit) { _log.info(ev); _transformItems(ev.items); emit(state.copyWith(isLoading: true)); } void _onOnItemTransformed(_OnItemTransformed ev, Emitter<_State> emit) { _log.info(ev); emit(state.copyWith( transformedItems: ev.items, isLoading: _itemTransformerQueue.isProcessing, )); } void _onSetSelectedItems(_SetSelectedItems ev, Emitter<_State> emit) { _log.info(ev); emit(state.copyWith(selectedItems: ev.items)); } void _onUnarchiveSelectedItems( _UnarchiveSelectedItems ev, Emitter<_State> emit) { _log.info(ev); final selected = state.selectedItems; _clearSelection(emit); final selectedFiles = selected.whereType<_FileItem>().map((e) => e.file).toList(); if (selectedFiles.isNotEmpty) { controller.updateProperty( selectedFiles, isArchived: const OrNull(false), errorBuilder: (fileIds) => _UnarchiveFailedError(fileIds.length), ); } } void _onStartScaling(_StartScaling ev, Emitter<_State> emit) { _log.info(ev); } void _onEndScaling(_EndScaling ev, Emitter<_State> emit) { _log.info(ev); if (state.scale == null) { return; } final int newZoom; if (state.scale! >= 1.25) { // scale up newZoom = (state.zoom + 1).clamp(-1, 2); } else if (state.scale! <= 0.75) { newZoom = (state.zoom - 1).clamp(-1, 2); } else { newZoom = state.zoom; } emit(state.copyWith( zoom: newZoom, scale: null, )); unawaited(prefController.setAlbumBrowserZoomLevel(newZoom)); } void _onSetScale(_SetScale ev, Emitter<_State> emit) { // _log.info(ev); emit(state.copyWith(scale: ev.scale)); } void _onSetError(_SetError ev, Emitter<_State> emit) { _log.info(ev); emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace))); } Future _transformItems(List files) async { _log.info("[_transformItems] Queue ${files.length} items"); _itemTransformerQueue.addJob( _ItemTransformerArgument( account: account, files: files, ), _buildItem, (result) { safeAdd(_OnItemTransformed(result.items)); }, ); } void _clearSelection(Emitter<_State> emit) { emit(state.copyWith(selectedItems: const {})); } final Account account; final FilesController controller; final PrefController prefController; final _itemTransformerQueue = ComputeQueue<_ItemTransformerArgument, _ItemTransformerResult>(); final _subscriptions = []; var _isHandlingError = false; } @toString class _UnarchiveFailedError implements Exception { const _UnarchiveFailedError(this.count); @override String toString() => _$toString(); final int count; } _ItemTransformerResult _buildItem(_ItemTransformerArgument arg) { final sortedFiles = arg.files .where((f) => f.fdIsArchived == true) .sorted(compareFileDescriptorDateTimeDescending); final transformed = <_Item>[]; for (int i = 0; i < sortedFiles.length; ++i) { final file = sortedFiles[i]; final item = _buildSingleItem(arg.account, file); if (item == null) { continue; } transformed.add(item); } return _ItemTransformerResult(items: transformed); } _Item? _buildSingleItem(Account account, FileDescriptor file) { if (file_util.isSupportedImageFormat(file)) { return _PhotoItem( file: file, account: account, ); } else if (file_util.isSupportedVideoFormat(file)) { return _VideoItem( file: file, account: account, ); } else { _$__NpLog.log .shout("[_buildSingleItem] Unsupported file format: ${file.fdMime}"); return null; } }