import 'package:bloc/bloc.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/event/event.dart'; import 'package:nc_photos/throttler.dart'; import 'package:nc_photos/use_case/list_location_file.dart'; import 'package:nc_photos/use_case/list_location_group.dart'; import 'package:np_codegen/np_codegen.dart'; import 'package:to_string/to_string.dart'; part 'list_location.g.dart'; abstract class ListLocationBlocEvent { const ListLocationBlocEvent(); } @toString class ListLocationBlocQuery extends ListLocationBlocEvent { const ListLocationBlocQuery(this.account); @override String toString() => _$toString(); final Account account; } /// An external event has happened and may affect the state of this bloc @toString class _ListLocationBlocExternalEvent extends ListLocationBlocEvent { const _ListLocationBlocExternalEvent(); @override String toString() => _$toString(); } @toString abstract class ListLocationBlocState { const ListLocationBlocState(this.account, this.result); @override String toString() => _$toString(); final Account? account; final LocationGroupResult result; } class ListLocationBlocInit extends ListLocationBlocState { ListLocationBlocInit() : super(null, const LocationGroupResult([], [], [], [])); } class ListLocationBlocLoading extends ListLocationBlocState { const ListLocationBlocLoading(Account? account, LocationGroupResult result) : super(account, result); } class ListLocationBlocSuccess extends ListLocationBlocState { const ListLocationBlocSuccess(Account? account, LocationGroupResult result) : super(account, result); } @toString class ListLocationBlocFailure extends ListLocationBlocState { const ListLocationBlocFailure( Account? account, LocationGroupResult result, this.exception) : super(account, result); @override String toString() => _$toString(); final Object exception; } /// The state of this bloc is inconsistent. This typically means that the data /// may have been changed externally class ListLocationBlocInconsistent extends ListLocationBlocState { const ListLocationBlocInconsistent( Account? account, LocationGroupResult result) : super(account, result); } /// List all files associated with a specific tag @npLog class ListLocationBloc extends Bloc { ListLocationBloc(this._c) : assert(require(_c)), assert(ListLocationFile.require(_c)), super(ListLocationBlocInit()) { _fileRemovedEventListener.begin(); on(_onEvent); } static bool require(DiContainer c) => DiContainer.has(c, DiType.taggedFileRepo); @override close() { _fileRemovedEventListener.end(); return super.close(); } Future _onEvent( ListLocationBlocEvent event, Emitter emit) async { _log.info("[_onEvent] $event"); if (event is ListLocationBlocQuery) { await _onEventQuery(event, emit); } else if (event is _ListLocationBlocExternalEvent) { await _onExternalEvent(event, emit); } } Future _onEventQuery( ListLocationBlocQuery ev, Emitter emit) async { try { emit(ListLocationBlocLoading(ev.account, state.result)); emit(ListLocationBlocSuccess(ev.account, await _query(ev))); } catch (e, stackTrace) { _log.severe("[_onEventQuery] Exception while request", e, stackTrace); emit(ListLocationBlocFailure(ev.account, state.result, e)); } } Future _onExternalEvent(_ListLocationBlocExternalEvent ev, Emitter emit) async { emit(ListLocationBlocInconsistent(state.account, state.result)); } void _onFileRemovedEvent(FileRemovedEvent ev) { if (state is ListLocationBlocInit) { // no data in this bloc, ignore return; } if (_isFileOfInterest(ev.file)) { _refreshThrottler.trigger( maxResponceTime: const Duration(seconds: 3), maxPendingCount: 10, ); } } Future _query(ListLocationBlocQuery ev) => ListLocationGroup(_c.withLocalRepo())(ev.account); bool _isFileOfInterest(FileDescriptor file) { if (!file_util.isSupportedFormat(file)) { return false; } for (final r in state.account?.roots ?? []) { final dir = File(path: file_util.unstripPath(state.account!, r)); if (file_util.isUnderDir(file, dir)) { return true; } } return false; } final DiContainer _c; late final _fileRemovedEventListener = AppEventListener(_onFileRemovedEvent); late final _refreshThrottler = Throttler( onTriggered: (_) { add(const _ListLocationBlocExternalEvent()); }, logTag: "ListLocationBloc.refresh", ); }