fix: Add labels to each cubit using repositories and state properties, remove label cubits

This commit is contained in:
Anton Stubenbord
2023-04-04 20:30:25 +02:00
parent 78fbd042a6
commit a2388b014b
95 changed files with 4790 additions and 1823 deletions
-34
View File
@@ -1,34 +0,0 @@
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
import 'package:rxdart/subjects.dart';
///
/// Base repository class which all repositories should implement
///
abstract class BaseRepository<T> extends Cubit<IndexedRepositoryState<T>>
with HydratedMixin {
final IndexedRepositoryState<T> _initialState;
BaseRepository(this._initialState) : super(_initialState) {
hydrate();
}
Stream<IndexedRepositoryState<T>?> get values =>
BehaviorSubject.seeded(state)..addStream(super.stream);
IndexedRepositoryState<T>? get current => state;
bool get isInitialized => state.hasLoaded;
Future<T> create(T object);
Future<T?> find(int id);
Future<Iterable<T>> findAll([Iterable<int>? ids]);
Future<T> update(T object);
Future<int> delete(T object);
@override
Future<void> clear() async {
await super.clear();
emit(_initialState);
}
}
@@ -1,69 +0,0 @@
import 'dart:async';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
class CorrespondentRepositoryImpl extends LabelRepository<Correspondent> {
final PaperlessLabelsApi _api;
CorrespondentRepositoryImpl(this._api)
: super(const CorrespondentRepositoryState());
@override
Future<Correspondent> create(Correspondent correspondent) async {
final created = await _api.saveCorrespondent(correspondent);
final updatedState = {...state.values ?? {}}
..putIfAbsent(created.id!, () => created);
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
return created;
}
@override
Future<int> delete(Correspondent correspondent) async {
await _api.deleteCorrespondent(correspondent);
final updatedState = {...state.values ?? {}}
..removeWhere((k, v) => k == correspondent.id);
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
return correspondent.id!;
}
@override
Future<Correspondent?> find(int id) async {
final correspondent = await _api.getCorrespondent(id);
if (correspondent != null) {
final updatedState = {...state.values ?? {}}..[id] = correspondent;
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
return correspondent;
}
return null;
}
@override
Future<Iterable<Correspondent>> findAll([Iterable<int>? ids]) async {
final correspondents = await _api.getCorrespondents(ids);
final updatedState = {...state.values ?? {}}
..addEntries(correspondents.map((e) => MapEntry(e.id!, e)));
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
return correspondents;
}
@override
Future<Correspondent> update(Correspondent correspondent) async {
final updated = await _api.updateCorrespondent(correspondent);
final updatedState = {...state.values ?? {}}
..update(updated.id!, (_) => updated);
emit(CorrespondentRepositoryState(values: updatedState, hasLoaded: true));
return updated;
}
@override
CorrespondentRepositoryState fromJson(Map<String, dynamic> json) {
return CorrespondentRepositoryState.fromJson(json);
}
@override
Map<String, dynamic> toJson(covariant CorrespondentRepositoryState state) {
return state.toJson();
}
}
@@ -1,67 +0,0 @@
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
class DocumentTypeRepositoryImpl extends LabelRepository<DocumentType> {
final PaperlessLabelsApi _api;
DocumentTypeRepositoryImpl(this._api)
: super(const DocumentTypeRepositoryState());
@override
Future<DocumentType> create(DocumentType documentType) async {
final created = await _api.saveDocumentType(documentType);
final updatedState = {...state.values ?? {}}
..putIfAbsent(created.id!, () => created);
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
return created;
}
@override
Future<int> delete(DocumentType documentType) async {
await _api.deleteDocumentType(documentType);
final updatedState = {...state.values ?? {}}
..removeWhere((k, v) => k == documentType.id);
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
return documentType.id!;
}
@override
Future<DocumentType?> find(int id) async {
final documentType = await _api.getDocumentType(id);
if (documentType != null) {
final updatedState = {...state.values ?? {}}..[id] = documentType;
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
return documentType;
}
return null;
}
@override
Future<Iterable<DocumentType>> findAll([Iterable<int>? ids]) async {
final documentTypes = await _api.getDocumentTypes(ids);
final updatedState = {...state.values ?? {}}
..addEntries(documentTypes.map((e) => MapEntry(e.id!, e)));
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
return documentTypes;
}
@override
Future<DocumentType> update(DocumentType documentType) async {
final updated = await _api.updateDocumentType(documentType);
final updatedState = {...state.values ?? {}}
..update(updated.id!, (_) => updated);
emit(DocumentTypeRepositoryState(values: updatedState, hasLoaded: true));
return updated;
}
@override
DocumentTypeRepositoryState fromJson(Map<String, dynamic> json) {
return DocumentTypeRepositoryState.fromJson(json);
}
@override
Map<String, dynamic> toJson(covariant DocumentTypeRepositoryState state) {
return state.toJson();
}
}
@@ -1,62 +0,0 @@
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/saved_view_repository_state.dart';
class SavedViewRepositoryImpl extends SavedViewRepository {
final PaperlessSavedViewsApi _api;
SavedViewRepositoryImpl(this._api) : super(const SavedViewRepositoryState());
@override
Future<SavedView> create(SavedView object) async {
final created = await _api.save(object);
final updatedState = {...state.values ?? {}}
..putIfAbsent(created.id!, () => created);
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
return created;
}
@override
Future<int> delete(SavedView view) async {
await _api.delete(view);
final updatedState = {...state.values ?? {}}..remove(view.id);
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
return view.id!;
}
@override
Future<SavedView?> find(int id) async {
final found = await _api.find(id);
final updatedState = {...state.values ?? {}}
..update(id, (_) => found, ifAbsent: () => found);
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
return found;
}
@override
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
final found = await _api.findAll(ids);
final updatedState = {
...state.values ?? {},
...{for (final view in found) view.id!: view},
};
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
return found;
}
@override
Future<SavedView> update(SavedView object) {
throw UnimplementedError(
"Saved view update is not yet implemented as it is not supported by the API.");
}
@override
SavedViewRepositoryState fromJson(Map<String, dynamic> json) {
return SavedViewRepositoryState.fromJson(json);
}
@override
Map<String, dynamic> toJson(covariant SavedViewRepositoryState state) {
return state.toJson();
}
}
@@ -1,68 +0,0 @@
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:rxdart/rxdart.dart' show BehaviorSubject;
class StoragePathRepositoryImpl extends LabelRepository<StoragePath> {
final PaperlessLabelsApi _api;
StoragePathRepositoryImpl(this._api)
: super(const StoragePathRepositoryState());
@override
Future<StoragePath> create(StoragePath storagePath) async {
final created = await _api.saveStoragePath(storagePath);
final updatedState = {...state.values ?? {}}
..putIfAbsent(created.id!, () => created);
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
return created;
}
@override
Future<int> delete(StoragePath storagePath) async {
await _api.deleteStoragePath(storagePath);
final updatedState = {...state.values ?? {}}
..removeWhere((k, v) => k == storagePath.id);
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
return storagePath.id!;
}
@override
Future<StoragePath?> find(int id) async {
final storagePath = await _api.getStoragePath(id);
if (storagePath != null) {
final updatedState = {...state.values ?? {}}..[id] = storagePath;
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
return storagePath;
}
return null;
}
@override
Future<Iterable<StoragePath>> findAll([Iterable<int>? ids]) async {
final storagePaths = await _api.getStoragePaths(ids);
final updatedState = {...state.values ?? {}}
..addEntries(storagePaths.map((e) => MapEntry(e.id!, e)));
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
return storagePaths;
}
@override
Future<StoragePath> update(StoragePath storagePath) async {
final updated = await _api.updateStoragePath(storagePath);
final updatedState = {...state.values ?? {}}
..update(updated.id!, (_) => updated);
emit(StoragePathRepositoryState(values: updatedState, hasLoaded: true));
return updated;
}
@override
StoragePathRepositoryState fromJson(Map<String, dynamic> json) {
return StoragePathRepositoryState.fromJson(json);
}
@override
Map<String, dynamic> toJson(covariant StoragePathRepositoryState state) {
return state.toJson();
}
}
@@ -1,66 +0,0 @@
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
class TagRepositoryImpl extends LabelRepository<Tag> {
final PaperlessLabelsApi _api;
TagRepositoryImpl(this._api) : super(const TagRepositoryState());
@override
Future<Tag> create(Tag object) async {
final created = await _api.saveTag(object);
final updatedState = {...state.values ?? {}}
..putIfAbsent(created.id!, () => created);
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
return created;
}
@override
Future<int> delete(Tag tag) async {
await _api.deleteTag(tag);
final updatedState = {...state.values ?? {}}
..removeWhere((k, v) => k == tag.id);
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
return tag.id!;
}
@override
Future<Tag?> find(int id) async {
final tag = await _api.getTag(id);
if (tag != null) {
final updatedState = {...state.values ?? {}}..[id] = tag;
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
return tag;
}
return null;
}
@override
Future<Iterable<Tag>> findAll([Iterable<int>? ids]) async {
final tags = await _api.getTags(ids);
final updatedState = {...state.values ?? {}}
..addEntries(tags.map((e) => MapEntry(e.id!, e)));
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
return tags;
}
@override
Future<Tag> update(Tag tag) async {
final updated = await _api.updateTag(tag);
final updatedState = {...state.values ?? {}}
..update(updated.id!, (_) => updated);
emit(TagRepositoryState(values: updatedState, hasLoaded: true));
return updated;
}
@override
TagRepositoryState? fromJson(Map<String, dynamic> json) {
return TagRepositoryState.fromJson(json);
}
@override
Map<String, dynamic>? toJson(covariant TagRepositoryState state) {
return state.toJson();
}
}
+216 -5
View File
@@ -1,7 +1,218 @@
import 'package:paperless_api/paperless_api.dart'; import 'dart:async';
import 'package:paperless_mobile/core/repository/base_repository.dart';
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
abstract class LabelRepository<T extends Label> extends BaseRepository<T> { import 'package:hydrated_bloc/hydrated_bloc.dart';
LabelRepository(IndexedRepositoryState<T> initial) : super(initial); import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository_state.dart';
class LabelRepository extends HydratedCubit<LabelRepositoryState> {
final PaperlessLabelsApi _api;
final Map<Object, StreamSubscription> _subscribers = {};
void subscribe(
Object source, {
required void Function(LabelRepositoryState) onChanged,
}) {
_subscribers.putIfAbsent(source, () {
onChanged(state);
return stream.listen((event) => onChanged(event));
});
}
void unsubscribe(Object source) async {
await _subscribers[source]?.cancel();
_subscribers.remove(source);
}
LabelRepository(this._api) : super(const LabelRepositoryState());
Future<Tag> createTag(Tag object) async {
final created = await _api.saveTag(object);
final updatedState = {...state.tags}
..putIfAbsent(created.id!, () => created);
emit(state.copyWith(tags: updatedState));
return created;
}
Future<int> deleteTag(Tag tag) async {
await _api.deleteTag(tag);
final updatedState = {...state.tags}..removeWhere((k, v) => k == tag.id);
emit(state.copyWith(tags: updatedState));
return tag.id!;
}
Future<Tag?> findTag(int id) async {
final tag = await _api.getTag(id);
if (tag != null) {
final updatedState = {...state.tags}..[id] = tag;
emit(state.copyWith(tags: updatedState));
return tag;
}
return null;
}
Future<Iterable<Tag>> findAllTags([Iterable<int>? ids]) async {
final tags = await _api.getTags(ids);
final updatedState = {...state.tags}
..addEntries(tags.map((e) => MapEntry(e.id!, e)));
emit(state.copyWith(tags: updatedState));
return tags;
}
Future<Tag> updateTag(Tag tag) async {
final updated = await _api.updateTag(tag);
final updatedState = {...state.tags}..update(updated.id!, (_) => updated);
emit(state.copyWith(tags: updatedState));
return updated;
}
Future<Correspondent> createCorrespondent(Correspondent correspondent) async {
final created = await _api.saveCorrespondent(correspondent);
final updatedState = {...state.correspondents}
..putIfAbsent(created.id!, () => created);
emit(state.copyWith(correspondents: updatedState));
return created;
}
Future<int> deleteCorrespondent(Correspondent correspondent) async {
await _api.deleteCorrespondent(correspondent);
final updatedState = {...state.correspondents}
..removeWhere((k, v) => k == correspondent.id);
emit(state.copyWith(correspondents: updatedState));
return correspondent.id!;
}
Future<Correspondent?> findCorrespondent(int id) async {
final correspondent = await _api.getCorrespondent(id);
if (correspondent != null) {
final updatedState = {...state.correspondents}..[id] = correspondent;
emit(state.copyWith(correspondents: updatedState));
return correspondent;
}
return null;
}
Future<Iterable<Correspondent>> findAllCorrespondents(
[Iterable<int>? ids]) async {
final correspondents = await _api.getCorrespondents(ids);
final updatedState = {...state.correspondents}
..addEntries(correspondents.map((e) => MapEntry(e.id!, e)));
emit(state.copyWith(correspondents: updatedState));
return correspondents;
}
Future<Correspondent> updateCorrespondent(Correspondent correspondent) async {
final updated = await _api.updateCorrespondent(correspondent);
final updatedState = {...state.correspondents}
..update(updated.id!, (_) => updated);
emit(state.copyWith(correspondents: updatedState));
return updated;
}
Future<DocumentType> createDocumentType(DocumentType documentType) async {
final created = await _api.saveDocumentType(documentType);
final updatedState = {...state.documentTypes}
..putIfAbsent(created.id!, () => created);
emit(state.copyWith(documentTypes: updatedState));
return created;
}
Future<int> deleteDocumentType(DocumentType documentType) async {
await _api.deleteDocumentType(documentType);
final updatedState = {...state.documentTypes}
..removeWhere((k, v) => k == documentType.id);
emit(state.copyWith(documentTypes: updatedState));
return documentType.id!;
}
Future<DocumentType?> findDocumentType(int id) async {
final documentType = await _api.getDocumentType(id);
if (documentType != null) {
final updatedState = {...state.documentTypes}..[id] = documentType;
emit(state.copyWith(documentTypes: updatedState));
return documentType;
}
return null;
}
Future<Iterable<DocumentType>> findAllDocumentTypes(
[Iterable<int>? ids]) async {
final documentTypes = await _api.getDocumentTypes(ids);
final updatedState = {...state.documentTypes}
..addEntries(documentTypes.map((e) => MapEntry(e.id!, e)));
emit(state.copyWith(documentTypes: updatedState));
return documentTypes;
}
Future<DocumentType> updateDocumentType(DocumentType documentType) async {
final updated = await _api.updateDocumentType(documentType);
final updatedState = {...state.documentTypes}
..update(updated.id!, (_) => updated);
emit(state.copyWith(documentTypes: updatedState));
return updated;
}
Future<StoragePath> createStoragePath(StoragePath storagePath) async {
final created = await _api.saveStoragePath(storagePath);
final updatedState = {...state.storagePaths}
..putIfAbsent(created.id!, () => created);
emit(state.copyWith(storagePaths: updatedState));
return created;
}
Future<int> deleteStoragePath(StoragePath storagePath) async {
await _api.deleteStoragePath(storagePath);
final updatedState = {...state.storagePaths}
..removeWhere((k, v) => k == storagePath.id);
emit(state.copyWith(storagePaths: updatedState));
return storagePath.id!;
}
Future<StoragePath?> findStoragePath(int id) async {
final storagePath = await _api.getStoragePath(id);
if (storagePath != null) {
final updatedState = {...state.storagePaths}..[id] = storagePath;
emit(state.copyWith(storagePaths: updatedState));
return storagePath;
}
return null;
}
Future<Iterable<StoragePath>> findAllStoragePaths(
[Iterable<int>? ids]) async {
final storagePaths = await _api.getStoragePaths(ids);
final updatedState = {...state.storagePaths}
..addEntries(storagePaths.map((e) => MapEntry(e.id!, e)));
emit(state.copyWith(storagePaths: updatedState));
return storagePaths;
}
Future<StoragePath> updateStoragePath(StoragePath storagePath) async {
final updated = await _api.updateStoragePath(storagePath);
final updatedState = {...state.storagePaths}
..update(updated.id!, (_) => updated);
emit(state.copyWith(storagePaths: updatedState));
return updated;
}
@override
Future<void> close() {
_subscribers.forEach((key, subscription) {
subscription.cancel();
});
return super.close();
}
@override
LabelRepositoryState? fromJson(Map<String, dynamic> json) {
return LabelRepositoryState.fromJson(json);
}
@override
Map<String, dynamic>? toJson(LabelRepositoryState state) {
return state.toJson();
}
} }
@@ -0,0 +1,18 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
part 'label_repository_state.freezed.dart';
part 'label_repository_state.g.dart';
@freezed
class LabelRepositoryState with _$LabelRepositoryState {
const factory LabelRepositoryState({
@Default({}) Map<int, Correspondent> correspondents,
@Default({}) Map<int, DocumentType> documentTypes,
@Default({}) Map<int, Tag> tags,
@Default({}) Map<int, StoragePath> storagePaths,
}) = _LabelRepositoryState;
factory LabelRepositoryState.fromJson(Map<String, dynamic> json) =>
_$LabelRepositoryStateFromJson(json);
}
@@ -0,0 +1,258 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'label_repository_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
LabelRepositoryState _$LabelRepositoryStateFromJson(Map<String, dynamic> json) {
return _LabelRepositoryState.fromJson(json);
}
/// @nodoc
mixin _$LabelRepositoryState {
Map<int, Correspondent> get correspondents =>
throw _privateConstructorUsedError;
Map<int, DocumentType> get documentTypes =>
throw _privateConstructorUsedError;
Map<int, Tag> get tags => throw _privateConstructorUsedError;
Map<int, StoragePath> get storagePaths => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$LabelRepositoryStateCopyWith<LabelRepositoryState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $LabelRepositoryStateCopyWith<$Res> {
factory $LabelRepositoryStateCopyWith(LabelRepositoryState value,
$Res Function(LabelRepositoryState) then) =
_$LabelRepositoryStateCopyWithImpl<$Res, LabelRepositoryState>;
@useResult
$Res call(
{Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, Tag> tags,
Map<int, StoragePath> storagePaths});
}
/// @nodoc
class _$LabelRepositoryStateCopyWithImpl<$Res,
$Val extends LabelRepositoryState>
implements $LabelRepositoryStateCopyWith<$Res> {
_$LabelRepositoryStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? correspondents = null,
Object? documentTypes = null,
Object? tags = null,
Object? storagePaths = null,
}) {
return _then(_value.copyWith(
correspondents: null == correspondents
? _value.correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value.documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
tags: null == tags
? _value.tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
storagePaths: null == storagePaths
? _value.storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
) as $Val);
}
}
/// @nodoc
abstract class _$$_LabelRepositoryStateCopyWith<$Res>
implements $LabelRepositoryStateCopyWith<$Res> {
factory _$$_LabelRepositoryStateCopyWith(_$_LabelRepositoryState value,
$Res Function(_$_LabelRepositoryState) then) =
__$$_LabelRepositoryStateCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, Tag> tags,
Map<int, StoragePath> storagePaths});
}
/// @nodoc
class __$$_LabelRepositoryStateCopyWithImpl<$Res>
extends _$LabelRepositoryStateCopyWithImpl<$Res, _$_LabelRepositoryState>
implements _$$_LabelRepositoryStateCopyWith<$Res> {
__$$_LabelRepositoryStateCopyWithImpl(_$_LabelRepositoryState _value,
$Res Function(_$_LabelRepositoryState) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? correspondents = null,
Object? documentTypes = null,
Object? tags = null,
Object? storagePaths = null,
}) {
return _then(_$_LabelRepositoryState(
correspondents: null == correspondents
? _value._correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value._documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
tags: null == tags
? _value._tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
storagePaths: null == storagePaths
? _value._storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_LabelRepositoryState implements _LabelRepositoryState {
const _$_LabelRepositoryState(
{final Map<int, Correspondent> correspondents = const {},
final Map<int, DocumentType> documentTypes = const {},
final Map<int, Tag> tags = const {},
final Map<int, StoragePath> storagePaths = const {}})
: _correspondents = correspondents,
_documentTypes = documentTypes,
_tags = tags,
_storagePaths = storagePaths;
factory _$_LabelRepositoryState.fromJson(Map<String, dynamic> json) =>
_$$_LabelRepositoryStateFromJson(json);
final Map<int, Correspondent> _correspondents;
@override
@JsonKey()
Map<int, Correspondent> get correspondents {
if (_correspondents is EqualUnmodifiableMapView) return _correspondents;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_correspondents);
}
final Map<int, DocumentType> _documentTypes;
@override
@JsonKey()
Map<int, DocumentType> get documentTypes {
if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_documentTypes);
}
final Map<int, Tag> _tags;
@override
@JsonKey()
Map<int, Tag> get tags {
if (_tags is EqualUnmodifiableMapView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_tags);
}
final Map<int, StoragePath> _storagePaths;
@override
@JsonKey()
Map<int, StoragePath> get storagePaths {
if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_storagePaths);
}
@override
String toString() {
return 'LabelRepositoryState(correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_LabelRepositoryState &&
const DeepCollectionEquality()
.equals(other._correspondents, _correspondents) &&
const DeepCollectionEquality()
.equals(other._documentTypes, _documentTypes) &&
const DeepCollectionEquality().equals(other._tags, _tags) &&
const DeepCollectionEquality()
.equals(other._storagePaths, _storagePaths));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_correspondents),
const DeepCollectionEquality().hash(_documentTypes),
const DeepCollectionEquality().hash(_tags),
const DeepCollectionEquality().hash(_storagePaths));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_LabelRepositoryStateCopyWith<_$_LabelRepositoryState> get copyWith =>
__$$_LabelRepositoryStateCopyWithImpl<_$_LabelRepositoryState>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_LabelRepositoryStateToJson(
this,
);
}
}
abstract class _LabelRepositoryState implements LabelRepositoryState {
const factory _LabelRepositoryState(
{final Map<int, Correspondent> correspondents,
final Map<int, DocumentType> documentTypes,
final Map<int, Tag> tags,
final Map<int, StoragePath> storagePaths}) = _$_LabelRepositoryState;
factory _LabelRepositoryState.fromJson(Map<String, dynamic> json) =
_$_LabelRepositoryState.fromJson;
@override
Map<int, Correspondent> get correspondents;
@override
Map<int, DocumentType> get documentTypes;
@override
Map<int, Tag> get tags;
@override
Map<int, StoragePath> get storagePaths;
@override
@JsonKey(ignore: true)
_$$_LabelRepositoryStateCopyWith<_$_LabelRepositoryState> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1,35 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/impl/document_type_repository_impl.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
class LabelRepositoriesProvider extends StatelessWidget {
final Widget child;
const LabelRepositoriesProvider({super.key, required this.child});
@override
Widget build(BuildContext context) {
return MultiRepositoryProvider(
providers: [
RepositoryProvider(
create: (context) => context.read<LabelRepository<Correspondent>>(),
),
RepositoryProvider(
create: (context) => context.read<LabelRepository<DocumentType>>(),
),
RepositoryProvider(
create: (context) => context.read<LabelRepository<StoragePath>>(),
),
RepositoryProvider(
create: (context) => context.read<LabelRepository<Tag>>(),
),
],
child: child,
);
}
}
+78 -6
View File
@@ -1,8 +1,80 @@
import 'package:paperless_api/paperless_api.dart'; import 'dart:async';
import 'package:paperless_mobile/core/repository/base_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/saved_view_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
abstract class SavedViewRepository extends BaseRepository<SavedView> { import 'package:hydrated_bloc/hydrated_bloc.dart';
SavedViewRepository(super.initialState); import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository_state.dart';
class SavedViewRepository extends HydratedCubit<SavedViewRepositoryState> {
final PaperlessSavedViewsApi _api;
final Map<Object, StreamSubscription> _subscribers = {};
void subscribe(
Object source,
void Function(Map<int, SavedView>) onChanged,
) {
_subscribers.putIfAbsent(source, () {
onChanged(state.savedViews);
return stream.listen((event) => onChanged(event.savedViews));
});
}
void unsubscribe(Object source) async {
await _subscribers[source]?.cancel();
_subscribers.remove(source);
}
SavedViewRepository(this._api) : super(const SavedViewRepositoryState());
Future<SavedView> create(SavedView object) async {
final created = await _api.save(object);
final updatedState = {...state.savedViews}
..putIfAbsent(created.id!, () => created);
emit(state.copyWith(savedViews: updatedState));
return created;
}
Future<int> delete(SavedView view) async {
await _api.delete(view);
final updatedState = {...state.savedViews}..remove(view.id);
emit(state.copyWith(savedViews: updatedState));
return view.id!;
}
Future<SavedView?> find(int id) async {
final found = await _api.find(id);
if (found != null) {
final updatedState = {...state.savedViews}
..update(id, (_) => found, ifAbsent: () => found);
emit(state.copyWith(savedViews: updatedState));
}
return found;
}
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
final found = await _api.findAll(ids);
final updatedState = {
...state.savedViews,
...{for (final view in found) view.id!: view},
};
emit(state.copyWith(savedViews: updatedState));
return found;
}
@override
Future<void> close() {
_subscribers.forEach((key, subscription) {
subscription.cancel();
});
return super.close();
}
@override
SavedViewRepositoryState? fromJson(Map<String, dynamic> json) {
return SavedViewRepositoryState.fromJson(json);
}
@override
Map<String, dynamic>? toJson(SavedViewRepositoryState state) {
return state.toJson();
}
} }
@@ -0,0 +1,15 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
part 'saved_view_repository_state.freezed.dart';
part 'saved_view_repository_state.g.dart';
@freezed
class SavedViewRepositoryState with _$SavedViewRepositoryState {
const factory SavedViewRepositoryState({
@Default({}) Map<int, SavedView> savedViews,
}) = _SavedViewRepositoryState;
factory SavedViewRepositoryState.fromJson(Map<String, dynamic> json) =>
_$SavedViewRepositoryStateFromJson(json);
}
@@ -0,0 +1,167 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'saved_view_repository_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
SavedViewRepositoryState _$SavedViewRepositoryStateFromJson(
Map<String, dynamic> json) {
return _SavedViewRepositoryState.fromJson(json);
}
/// @nodoc
mixin _$SavedViewRepositoryState {
Map<int, SavedView> get savedViews => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$SavedViewRepositoryStateCopyWith<SavedViewRepositoryState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SavedViewRepositoryStateCopyWith<$Res> {
factory $SavedViewRepositoryStateCopyWith(SavedViewRepositoryState value,
$Res Function(SavedViewRepositoryState) then) =
_$SavedViewRepositoryStateCopyWithImpl<$Res, SavedViewRepositoryState>;
@useResult
$Res call({Map<int, SavedView> savedViews});
}
/// @nodoc
class _$SavedViewRepositoryStateCopyWithImpl<$Res,
$Val extends SavedViewRepositoryState>
implements $SavedViewRepositoryStateCopyWith<$Res> {
_$SavedViewRepositoryStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? savedViews = null,
}) {
return _then(_value.copyWith(
savedViews: null == savedViews
? _value.savedViews
: savedViews // ignore: cast_nullable_to_non_nullable
as Map<int, SavedView>,
) as $Val);
}
}
/// @nodoc
abstract class _$$_SavedViewRepositoryStateCopyWith<$Res>
implements $SavedViewRepositoryStateCopyWith<$Res> {
factory _$$_SavedViewRepositoryStateCopyWith(
_$_SavedViewRepositoryState value,
$Res Function(_$_SavedViewRepositoryState) then) =
__$$_SavedViewRepositoryStateCopyWithImpl<$Res>;
@override
@useResult
$Res call({Map<int, SavedView> savedViews});
}
/// @nodoc
class __$$_SavedViewRepositoryStateCopyWithImpl<$Res>
extends _$SavedViewRepositoryStateCopyWithImpl<$Res,
_$_SavedViewRepositoryState>
implements _$$_SavedViewRepositoryStateCopyWith<$Res> {
__$$_SavedViewRepositoryStateCopyWithImpl(_$_SavedViewRepositoryState _value,
$Res Function(_$_SavedViewRepositoryState) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? savedViews = null,
}) {
return _then(_$_SavedViewRepositoryState(
savedViews: null == savedViews
? _value._savedViews
: savedViews // ignore: cast_nullable_to_non_nullable
as Map<int, SavedView>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_SavedViewRepositoryState implements _SavedViewRepositoryState {
const _$_SavedViewRepositoryState(
{final Map<int, SavedView> savedViews = const {}})
: _savedViews = savedViews;
factory _$_SavedViewRepositoryState.fromJson(Map<String, dynamic> json) =>
_$$_SavedViewRepositoryStateFromJson(json);
final Map<int, SavedView> _savedViews;
@override
@JsonKey()
Map<int, SavedView> get savedViews {
if (_savedViews is EqualUnmodifiableMapView) return _savedViews;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_savedViews);
}
@override
String toString() {
return 'SavedViewRepositoryState(savedViews: $savedViews)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_SavedViewRepositoryState &&
const DeepCollectionEquality()
.equals(other._savedViews, _savedViews));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, const DeepCollectionEquality().hash(_savedViews));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_SavedViewRepositoryStateCopyWith<_$_SavedViewRepositoryState>
get copyWith => __$$_SavedViewRepositoryStateCopyWithImpl<
_$_SavedViewRepositoryState>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_SavedViewRepositoryStateToJson(
this,
);
}
}
abstract class _SavedViewRepositoryState implements SavedViewRepositoryState {
const factory _SavedViewRepositoryState(
{final Map<int, SavedView> savedViews}) = _$_SavedViewRepositoryState;
factory _SavedViewRepositoryState.fromJson(Map<String, dynamic> json) =
_$_SavedViewRepositoryState.fromJson;
@override
Map<int, SavedView> get savedViews;
@override
@JsonKey(ignore: true)
_$$_SavedViewRepositoryStateCopyWith<_$_SavedViewRepositoryState>
get copyWith => throw _privateConstructorUsedError;
}
@@ -1,31 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
part 'correspondent_repository_state.g.dart';
@JsonSerializable()
class CorrespondentRepositoryState
extends IndexedRepositoryState<Correspondent> {
const CorrespondentRepositoryState({
super.values = const {},
super.hasLoaded,
});
@override
CorrespondentRepositoryState copyWith({
Map<int, Correspondent>? values,
bool? hasLoaded,
}) {
return CorrespondentRepositoryState(
values: values ?? this.values,
hasLoaded: hasLoaded ?? this.hasLoaded,
);
}
factory CorrespondentRepositoryState.fromJson(Map<String, dynamic> json) =>
_$CorrespondentRepositoryStateFromJson(json);
Map<String, dynamic> toJson() => _$CorrespondentRepositoryStateToJson(this);
}
@@ -1,29 +0,0 @@
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
import 'package:json_annotation/json_annotation.dart';
part 'document_type_repository_state.g.dart';
@JsonSerializable()
class DocumentTypeRepositoryState extends IndexedRepositoryState<DocumentType> {
const DocumentTypeRepositoryState({
super.values = const {},
super.hasLoaded,
});
@override
DocumentTypeRepositoryState copyWith({
Map<int, DocumentType>? values,
bool? hasLoaded,
}) {
return DocumentTypeRepositoryState(
values: values ?? this.values,
hasLoaded: hasLoaded ?? this.hasLoaded,
);
}
factory DocumentTypeRepositoryState.fromJson(Map<String, dynamic> json) =>
_$DocumentTypeRepositoryStateFromJson(json);
Map<String, dynamic> toJson() => _$DocumentTypeRepositoryStateToJson(this);
}
@@ -1,29 +0,0 @@
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
import 'package:json_annotation/json_annotation.dart';
part 'saved_view_repository_state.g.dart';
@JsonSerializable()
class SavedViewRepositoryState extends IndexedRepositoryState<SavedView> {
const SavedViewRepositoryState({
super.values = const {},
super.hasLoaded = false,
});
@override
SavedViewRepositoryState copyWith({
Map<int, SavedView>? values,
bool? hasLoaded,
}) {
return SavedViewRepositoryState(
values: values ?? this.values,
hasLoaded: hasLoaded ?? this.hasLoaded,
);
}
factory SavedViewRepositoryState.fromJson(Map<String, dynamic> json) =>
_$SavedViewRepositoryStateFromJson(json);
Map<String, dynamic> toJson() => _$SavedViewRepositoryStateToJson(this);
}
@@ -1,29 +0,0 @@
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
import 'package:json_annotation/json_annotation.dart';
part 'storage_path_repository_state.g.dart';
@JsonSerializable()
class StoragePathRepositoryState extends IndexedRepositoryState<StoragePath> {
const StoragePathRepositoryState({
super.values = const {},
super.hasLoaded = false,
});
@override
StoragePathRepositoryState copyWith({
Map<int, StoragePath>? values,
bool? hasLoaded,
}) {
return StoragePathRepositoryState(
values: values ?? this.values,
hasLoaded: hasLoaded ?? this.hasLoaded,
);
}
factory StoragePathRepositoryState.fromJson(Map<String, dynamic> json) =>
_$StoragePathRepositoryStateFromJson(json);
Map<String, dynamic> toJson() => _$StoragePathRepositoryStateToJson(this);
}
@@ -1,29 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
part 'tag_repository_state.g.dart';
@JsonSerializable()
class TagRepositoryState extends IndexedRepositoryState<Tag> {
const TagRepositoryState({
super.values = const {},
super.hasLoaded = false,
});
@override
TagRepositoryState copyWith({
Map<int, Tag>? values,
bool? hasLoaded,
}) {
return TagRepositoryState(
values: values ?? this.values,
hasLoaded: hasLoaded ?? this.hasLoaded,
);
}
factory TagRepositoryState.fromJson(Map<String, dynamic> json) =>
_$TagRepositoryStateFromJson(json);
Map<String, dynamic> toJson() => _$TagRepositoryStateToJson(this);
}
@@ -1,16 +0,0 @@
abstract class IndexedRepositoryState<T> {
final Map<int, T>? values;
final bool hasLoaded;
const IndexedRepositoryState({
required this.values,
this.hasLoaded = false,
}) : assert(!(values == null) || !hasLoaded);
IndexedRepositoryState.loaded(this.values) : hasLoaded = true;
IndexedRepositoryState<T> copyWith({
Map<int, T>? values,
bool? hasLoaded,
});
}
@@ -6,45 +6,28 @@ import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'document_bulk_action_state.dart'; part 'document_bulk_action_state.dart';
part 'document_bulk_action_cubit.freezed.dart';
class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> { class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
final PaperlessDocumentsApi _documentsApi; final PaperlessDocumentsApi _documentsApi;
final LabelRepository<Correspondent> _correspondentRepository; final LabelRepository _labelRepository;
final LabelRepository<DocumentType> _documentTypeRepository;
final LabelRepository<Tag> _tagRepository;
final LabelRepository<StoragePath> _storagePathRepository;
final DocumentChangedNotifier _notifier; final DocumentChangedNotifier _notifier;
final List<StreamSubscription> _subscriptions = [];
DocumentBulkActionCubit( DocumentBulkActionCubit(
this._documentsApi, this._documentsApi,
this._correspondentRepository, this._labelRepository,
this._documentTypeRepository,
this._tagRepository,
this._storagePathRepository,
this._notifier, { this._notifier, {
required List<DocumentModel> selection, required List<DocumentModel> selection,
}) : super( }) : super(
DocumentBulkActionState( DocumentBulkActionState(
selection: selection, selection: selection,
correspondentOptions: correspondents: _labelRepository.state.correspondents,
(_correspondentRepository.current?.hasLoaded ?? false) documentTypes: _labelRepository.state.documentTypes,
? _correspondentRepository.current!.values! storagePaths: _labelRepository.state.storagePaths,
: {}, tags: _labelRepository.state.tags,
tagOptions: (_tagRepository.current?.hasLoaded ?? false)
? _tagRepository.current!.values!
: {},
documentTypeOptions:
(_documentTypeRepository.current?.hasLoaded ?? false)
? _documentTypeRepository.current!.values!
: {},
storagePathOptions:
(_storagePathRepository.current?.hasLoaded ?? false)
? _storagePathRepository.current!.values!
: {},
), ),
) { ) {
_notifier.subscribe( _notifier.subscribe(
@@ -60,35 +43,18 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
); );
}, },
); );
_subscriptions.add( _labelRepository.subscribe(
_tagRepository.values.listen((event) { this,
if (event?.hasLoaded ?? false) { onChanged: (labels) {
emit(state.copyWith(tagOptions: event!.values)); emit(
} state.copyWith(
}), correspondents: labels.correspondents,
); documentTypes: labels.documentTypes,
_subscriptions.add( storagePaths: labels.storagePaths,
_correspondentRepository.values.listen((event) { tags: labels.tags,
if (event?.hasLoaded ?? false) { ),
emit(state.copyWith( );
correspondentOptions: event!.values, },
));
}
}),
);
_subscriptions.add(
_documentTypeRepository.values.listen((event) {
if (event?.hasLoaded ?? false) {
emit(state.copyWith(documentTypeOptions: event!.values));
}
}),
);
_subscriptions.add(
_storagePathRepository.values.listen((event) {
if (event?.hasLoaded ?? false) {
emit(state.copyWith(storagePathOptions: event!.values));
}
}),
); );
} }
@@ -177,9 +143,7 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
@override @override
Future<void> close() { Future<void> close() {
_notifier.unsubscribe(this); _notifier.unsubscribe(this);
for (final sub in _subscriptions) { _labelRepository.unsubscribe(this);
sub.cancel();
}
return super.close(); return super.close();
} }
} }
@@ -0,0 +1,269 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'document_bulk_action_cubit.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
mixin _$DocumentBulkActionState {
List<DocumentModel> get selection => throw _privateConstructorUsedError;
Map<int, Correspondent> get correspondents =>
throw _privateConstructorUsedError;
Map<int, DocumentType> get documentTypes =>
throw _privateConstructorUsedError;
Map<int, Tag> get tags => throw _privateConstructorUsedError;
Map<int, StoragePath> get storagePaths => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$DocumentBulkActionStateCopyWith<DocumentBulkActionState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $DocumentBulkActionStateCopyWith<$Res> {
factory $DocumentBulkActionStateCopyWith(DocumentBulkActionState value,
$Res Function(DocumentBulkActionState) then) =
_$DocumentBulkActionStateCopyWithImpl<$Res, DocumentBulkActionState>;
@useResult
$Res call(
{List<DocumentModel> selection,
Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, Tag> tags,
Map<int, StoragePath> storagePaths});
}
/// @nodoc
class _$DocumentBulkActionStateCopyWithImpl<$Res,
$Val extends DocumentBulkActionState>
implements $DocumentBulkActionStateCopyWith<$Res> {
_$DocumentBulkActionStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? selection = null,
Object? correspondents = null,
Object? documentTypes = null,
Object? tags = null,
Object? storagePaths = null,
}) {
return _then(_value.copyWith(
selection: null == selection
? _value.selection
: selection // ignore: cast_nullable_to_non_nullable
as List<DocumentModel>,
correspondents: null == correspondents
? _value.correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value.documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
tags: null == tags
? _value.tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
storagePaths: null == storagePaths
? _value.storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
) as $Val);
}
}
/// @nodoc
abstract class _$$_DocumentBulkActionStateCopyWith<$Res>
implements $DocumentBulkActionStateCopyWith<$Res> {
factory _$$_DocumentBulkActionStateCopyWith(_$_DocumentBulkActionState value,
$Res Function(_$_DocumentBulkActionState) then) =
__$$_DocumentBulkActionStateCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{List<DocumentModel> selection,
Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, Tag> tags,
Map<int, StoragePath> storagePaths});
}
/// @nodoc
class __$$_DocumentBulkActionStateCopyWithImpl<$Res>
extends _$DocumentBulkActionStateCopyWithImpl<$Res,
_$_DocumentBulkActionState>
implements _$$_DocumentBulkActionStateCopyWith<$Res> {
__$$_DocumentBulkActionStateCopyWithImpl(_$_DocumentBulkActionState _value,
$Res Function(_$_DocumentBulkActionState) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? selection = null,
Object? correspondents = null,
Object? documentTypes = null,
Object? tags = null,
Object? storagePaths = null,
}) {
return _then(_$_DocumentBulkActionState(
selection: null == selection
? _value._selection
: selection // ignore: cast_nullable_to_non_nullable
as List<DocumentModel>,
correspondents: null == correspondents
? _value._correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value._documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
tags: null == tags
? _value._tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
storagePaths: null == storagePaths
? _value._storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
));
}
}
/// @nodoc
class _$_DocumentBulkActionState extends _DocumentBulkActionState {
const _$_DocumentBulkActionState(
{required final List<DocumentModel> selection,
required final Map<int, Correspondent> correspondents,
required final Map<int, DocumentType> documentTypes,
required final Map<int, Tag> tags,
required final Map<int, StoragePath> storagePaths})
: _selection = selection,
_correspondents = correspondents,
_documentTypes = documentTypes,
_tags = tags,
_storagePaths = storagePaths,
super._();
final List<DocumentModel> _selection;
@override
List<DocumentModel> get selection {
if (_selection is EqualUnmodifiableListView) return _selection;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_selection);
}
final Map<int, Correspondent> _correspondents;
@override
Map<int, Correspondent> get correspondents {
if (_correspondents is EqualUnmodifiableMapView) return _correspondents;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_correspondents);
}
final Map<int, DocumentType> _documentTypes;
@override
Map<int, DocumentType> get documentTypes {
if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_documentTypes);
}
final Map<int, Tag> _tags;
@override
Map<int, Tag> get tags {
if (_tags is EqualUnmodifiableMapView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_tags);
}
final Map<int, StoragePath> _storagePaths;
@override
Map<int, StoragePath> get storagePaths {
if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_storagePaths);
}
@override
String toString() {
return 'DocumentBulkActionState(selection: $selection, correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_DocumentBulkActionState &&
const DeepCollectionEquality()
.equals(other._selection, _selection) &&
const DeepCollectionEquality()
.equals(other._correspondents, _correspondents) &&
const DeepCollectionEquality()
.equals(other._documentTypes, _documentTypes) &&
const DeepCollectionEquality().equals(other._tags, _tags) &&
const DeepCollectionEquality()
.equals(other._storagePaths, _storagePaths));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_selection),
const DeepCollectionEquality().hash(_correspondents),
const DeepCollectionEquality().hash(_documentTypes),
const DeepCollectionEquality().hash(_tags),
const DeepCollectionEquality().hash(_storagePaths));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_DocumentBulkActionStateCopyWith<_$_DocumentBulkActionState>
get copyWith =>
__$$_DocumentBulkActionStateCopyWithImpl<_$_DocumentBulkActionState>(
this, _$identity);
}
abstract class _DocumentBulkActionState extends DocumentBulkActionState {
const factory _DocumentBulkActionState(
{required final List<DocumentModel> selection,
required final Map<int, Correspondent> correspondents,
required final Map<int, DocumentType> documentTypes,
required final Map<int, Tag> tags,
required final Map<int, StoragePath> storagePaths}) =
_$_DocumentBulkActionState;
const _DocumentBulkActionState._() : super._();
@override
List<DocumentModel> get selection;
@override
Map<int, Correspondent> get correspondents;
@override
Map<int, DocumentType> get documentTypes;
@override
Map<int, Tag> get tags;
@override
Map<int, StoragePath> get storagePaths;
@override
@JsonKey(ignore: true)
_$$_DocumentBulkActionStateCopyWith<_$_DocumentBulkActionState>
get copyWith => throw _privateConstructorUsedError;
}
@@ -1,44 +1,15 @@
part of 'document_bulk_action_cubit.dart'; part of 'document_bulk_action_cubit.dart';
class DocumentBulkActionState extends Equatable { @freezed
final List<DocumentModel> selection; class DocumentBulkActionState with _$DocumentBulkActionState {
final Map<int, Correspondent> correspondentOptions; const DocumentBulkActionState._();
final Map<int, DocumentType> documentTypeOptions; const factory DocumentBulkActionState({
final Map<int, Tag> tagOptions; required List<DocumentModel> selection,
final Map<int, StoragePath> storagePathOptions; required Map<int, Correspondent> correspondents,
required Map<int, DocumentType> documentTypes,
const DocumentBulkActionState({ required Map<int, Tag> tags,
this.correspondentOptions = const {}, required Map<int, StoragePath> storagePaths,
this.documentTypeOptions = const {}, }) = _DocumentBulkActionState;
this.tagOptions = const {},
this.storagePathOptions = const {},
this.selection = const [],
});
@override
List<Object> get props => [
selection,
correspondentOptions,
documentTypeOptions,
tagOptions,
storagePathOptions,
];
Iterable<int> get selectedIds => selection.map((d) => d.id); Iterable<int> get selectedIds => selection.map((d) => d.id);
DocumentBulkActionState copyWith({
List<DocumentModel>? selection,
Map<int, Correspondent>? correspondentOptions,
Map<int, DocumentType>? documentTypeOptions,
Map<int, Tag>? tagOptions,
Map<int, StoragePath>? storagePathOptions,
}) {
return DocumentBulkActionState(
selection: selection ?? this.selection,
correspondentOptions: correspondentOptions ?? this.correspondentOptions,
documentTypeOptions: documentTypeOptions ?? this.documentTypeOptions,
storagePathOptions: storagePathOptions ?? this.storagePathOptions,
tagOptions: tagOptions ?? this.tagOptions,
);
}
} }
@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:paperless_api/paperless_api.dart';
class BulkEditPage<int, T extends Label> extends StatefulWidget {
final bool enableMultipleChoice;
final Map<int, T> availableOptions;
const BulkEditPage({
super.key,
required this.enableMultipleChoice,
required this.availableOptions,
});
@override
State<BulkEditPage> createState() => _BulkEditPageState();
}
class _BulkEditPageState extends State<BulkEditPage> {
@override
Widget build(BuildContext context) {
return Container();
}
}
@@ -2,12 +2,11 @@ import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart'; import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bulk_action_cubit.dart'; import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bulk_action_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
class BulkEditTagsBottomSheet extends StatefulWidget { class BulkEditTagsBottomSheet extends StatefulWidget {
@@ -20,29 +19,42 @@ class BulkEditTagsBottomSheet extends StatefulWidget {
class _BulkEditTagsBottomSheetState extends State<BulkEditTagsBottomSheet> { class _BulkEditTagsBottomSheetState extends State<BulkEditTagsBottomSheet> {
final _formKey = GlobalKey<FormBuilderState>(); final _formKey = GlobalKey<FormBuilderState>();
List<int> _tagsToRemove = []; final _textEditingController = TextEditingController();
List<int> _tagsToAdd = []; late Set<int> _sharedTags;
late Set<int> _nonSharedTags;
final Set<int> _sharedTagsToRemove = {};
final Set<int> _nonSharedTagsToRemove = {};
final Set<int> _tagsToAdd = {};
@override
void initState() {
super.initState();
final state = context.read<DocumentBulkActionCubit>().state;
_sharedTags = state.selection
.map((doc) => doc.tags)
.reduce((previousValue, element) =>
previousValue.toSet().intersection(element.toSet()))
.toSet();
print(_sharedTags.map((e) => e).join(", "));
_nonSharedTags = state.selection
.map((doc) => doc.tags)
.flattened
.toSet()
.difference(_sharedTags)
.toSet();
print(_nonSharedTags.map((e) => e).join(", "));
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<DocumentBulkActionCubit, DocumentBulkActionState>( return BlocBuilder<DocumentBulkActionCubit, DocumentBulkActionState>(
builder: (context, state) { builder: (context, state) {
final sharedTags = state.selection
.map((doc) => doc.tags)
.reduce((previousValue, element) =>
previousValue.toSet().intersection(element.toSet()))
.toList();
final nonSharedTags = state.selection
.map((doc) => doc.tags)
.flattened
.toSet()
.difference(sharedTags.toSet())
.toList();
return Padding( return Padding(
padding: padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: BlocBuilder<DocumentBulkActionCubit, DocumentBulkActionState>( child: BlocBuilder<DocumentBulkActionCubit, DocumentBulkActionState>(
builder: (context, state) { builder: (context, state) {
print(state);
return Padding( return Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView( child: SingleChildScrollView(
@@ -54,25 +66,123 @@ class _BulkEditTagsBottomSheetState extends State<BulkEditTagsBottomSheet> {
"Bulk modify tags", "Bulk modify tags",
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
).paddedOnly(bottom: 24), ).paddedOnly(bottom: 24),
FormBuilder( TypeAheadFormField<Tag>(
key: _formKey, textFieldConfiguration: TextFieldConfiguration(
child: TagFormField( controller: _textEditingController,
initialValue: IdsTagsQuery( decoration: const InputDecoration(
sharedTags.map((tag) => IncludeTagIdQuery(tag)), labelText: "Tags",
hintText: "Start typing to add tags...",
), ),
name: "labelFormField",
selectableOptions: state.tagOptions,
allowCreation: false,
anyAssignedSelectable: false,
excludeAllowed: false,
), ),
onSuggestionSelected: (suggestion) {
setState(() {
_tagsToAdd.add(suggestion.id!);
});
_textEditingController.clear();
},
itemBuilder: (context, option) {
return ListTile(
leading: SizedBox(
width: 32,
height: 32,
child: DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: option.color!,
),
),
),
title: Text(option.name),
);
},
suggestionsCallback: (pattern) {
final searchString = pattern.toLowerCase();
return state.tags.entries
.where(
(tag) => tag.value.name
.toLowerCase()
.contains(searchString),
)
.map((e) => e.key)
.toSet()
.difference(_sharedTags)
.difference(_nonSharedTags)
.map((e) => state.tags[e]!);
},
),
Text("Shared tags"),
Wrap(
children: _sharedTags
.map(
(tag) => RemovableTagWidget(
tag: state.tags[tag]!,
onDeleted: (tag) {
setState(() {
_sharedTagsToRemove.add(tag);
_sharedTags.remove(tag);
});
},
),
)
.toList(),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text("Tags removed after apply"), Text("Non-shared tags"),
Wrap(), Wrap(
children: _nonSharedTags
.map(
(tag) => RemovableTagWidget(
tag: state.tags[tag]!,
onDeleted: (tag) {
setState(() {
_nonSharedTagsToRemove.add(tag);
_nonSharedTags.remove(tag);
});
},
),
)
.toList(),
),
Text("Remove"),
Wrap(
children: _sharedTagsToRemove.map((tag) {
return RemovableTagWidget(
tag: state.tags[tag]!,
onDeleted: (tag) {
setState(() {
_sharedTagsToRemove.remove(tag);
_sharedTags.add(tag);
});
},
);
}).toList() +
_nonSharedTagsToRemove.map((tag) {
return RemovableTagWidget(
tag: state.tags[tag]!,
onDeleted: (tag) {
setState(() {
_nonSharedTagsToRemove.remove(tag);
_nonSharedTags.add(tag);
});
},
);
}).toList(),
),
const SizedBox(height: 8), const SizedBox(height: 8),
Text("Tags added after apply"), Text("Add"),
Wrap(), Wrap(
children: _tagsToAdd
.map(
(tag) => RemovableTagWidget(
tag: state.tags[tag]!,
onDeleted: (tag) {
setState(() {
_tagsToAdd.remove(tag);
});
}),
)
.toList(),
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
@@ -107,3 +217,28 @@ class _BulkEditTagsBottomSheetState extends State<BulkEditTagsBottomSheet> {
); );
} }
} }
class RemovableTagWidget extends StatelessWidget {
final Tag tag;
final void Function(int tagId) onDeleted;
const RemovableTagWidget(
{super.key, required this.tag, required this.onDeleted});
@override
Widget build(BuildContext context) {
return Chip(
label: Text(
tag.name,
style: TextStyle(
color: tag.textColor,
),
),
onDeleted: () => onDeleted(tag.id!),
deleteIcon: Icon(Icons.clear),
backgroundColor: tag.color,
deleteIconColor: tag.textColor,
padding: EdgeInsets.zero,
side: BorderSide.none,
);
}
}
@@ -0,0 +1,30 @@
// import 'package:flutter/material.dart';
// import 'package:flutter/src/widgets/framework.dart';
// import 'package:flutter/src/widgets/placeholder.dart';
// class LabelBulkSelectionWidget extends StatelessWidget {
// final int labelId;
// final String title;
// final bool selected;
// final bool excluded;
// final Widget Function(int id) leadingWidgetBuilder;
// final void Function(int id) onSelected;
// final void Function(int id) onUnselected;
// final void Function(int id) onRemoved;
// const LabelBulkSelectionWidget({
// super.key,
// required this.labelId,
// required this.title,
// required this.leadingWidgetBuilder,
// required this.onSelected,
// required this.onUnselected,
// required this.onRemoved,
// });
// @override
// Widget build(BuildContext context) {
// return ListTile(
// title: Text(title),
// );
// }
// }
@@ -2,32 +2,47 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:open_filex/open_filex.dart'; import 'package:open_filex/open_filex.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/service/file_description.dart'; import 'package:paperless_mobile/core/service/file_description.dart';
import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/core/service/file_service.dart';
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart'; import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
part 'document_details_cubit.freezed.dart';
part 'document_details_state.dart'; part 'document_details_state.dart';
class DocumentDetailsCubit extends Cubit<DocumentDetailsState> { class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
final PaperlessDocumentsApi _api; final PaperlessDocumentsApi _api;
final DocumentChangedNotifier _notifier; final DocumentChangedNotifier _notifier;
final LocalNotificationService _notificationService; final LocalNotificationService _notificationService;
final LabelRepository _labelRepository;
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
DocumentDetailsCubit( DocumentDetailsCubit(
this._api, this._api,
this._labelRepository,
this._notifier, this._notifier,
this._notificationService, { this._notificationService, {
required DocumentModel initialDocument, required DocumentModel initialDocument,
}) : super(DocumentDetailsState(document: initialDocument)) { }) : super(DocumentDetailsState(
document: initialDocument,
)) {
_notifier.subscribe(this, onUpdated: replace); _notifier.subscribe(this, onUpdated: replace);
_labelRepository.subscribe(
this,
onChanged: (labels) => emit(
state.copyWith(
correspondents: labels.correspondents,
documentTypes: labels.documentTypes,
tags: labels.tags,
storagePaths: labels.storagePaths,
),
),
);
loadSuggestions(); loadSuggestions();
loadMetaData(); loadMetaData();
} }
@@ -0,0 +1,350 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'document_details_cubit.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
mixin _$DocumentDetailsState {
DocumentModel get document => throw _privateConstructorUsedError;
DocumentMetaData? get metaData => throw _privateConstructorUsedError;
bool get isFullContentLoaded => throw _privateConstructorUsedError;
String? get fullContent => throw _privateConstructorUsedError;
FieldSuggestions? get suggestions => throw _privateConstructorUsedError;
Map<int, Correspondent> get correspondents =>
throw _privateConstructorUsedError;
Map<int, DocumentType> get documentTypes =>
throw _privateConstructorUsedError;
Map<int, Tag> get tags => throw _privateConstructorUsedError;
Map<int, StoragePath> get storagePaths => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$DocumentDetailsStateCopyWith<DocumentDetailsState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $DocumentDetailsStateCopyWith<$Res> {
factory $DocumentDetailsStateCopyWith(DocumentDetailsState value,
$Res Function(DocumentDetailsState) then) =
_$DocumentDetailsStateCopyWithImpl<$Res, DocumentDetailsState>;
@useResult
$Res call(
{DocumentModel document,
DocumentMetaData? metaData,
bool isFullContentLoaded,
String? fullContent,
FieldSuggestions? suggestions,
Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, Tag> tags,
Map<int, StoragePath> storagePaths});
}
/// @nodoc
class _$DocumentDetailsStateCopyWithImpl<$Res,
$Val extends DocumentDetailsState>
implements $DocumentDetailsStateCopyWith<$Res> {
_$DocumentDetailsStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? document = null,
Object? metaData = freezed,
Object? isFullContentLoaded = null,
Object? fullContent = freezed,
Object? suggestions = freezed,
Object? correspondents = null,
Object? documentTypes = null,
Object? tags = null,
Object? storagePaths = null,
}) {
return _then(_value.copyWith(
document: null == document
? _value.document
: document // ignore: cast_nullable_to_non_nullable
as DocumentModel,
metaData: freezed == metaData
? _value.metaData
: metaData // ignore: cast_nullable_to_non_nullable
as DocumentMetaData?,
isFullContentLoaded: null == isFullContentLoaded
? _value.isFullContentLoaded
: isFullContentLoaded // ignore: cast_nullable_to_non_nullable
as bool,
fullContent: freezed == fullContent
? _value.fullContent
: fullContent // ignore: cast_nullable_to_non_nullable
as String?,
suggestions: freezed == suggestions
? _value.suggestions
: suggestions // ignore: cast_nullable_to_non_nullable
as FieldSuggestions?,
correspondents: null == correspondents
? _value.correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value.documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
tags: null == tags
? _value.tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
storagePaths: null == storagePaths
? _value.storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
) as $Val);
}
}
/// @nodoc
abstract class _$$_DocumentDetailsStateCopyWith<$Res>
implements $DocumentDetailsStateCopyWith<$Res> {
factory _$$_DocumentDetailsStateCopyWith(_$_DocumentDetailsState value,
$Res Function(_$_DocumentDetailsState) then) =
__$$_DocumentDetailsStateCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{DocumentModel document,
DocumentMetaData? metaData,
bool isFullContentLoaded,
String? fullContent,
FieldSuggestions? suggestions,
Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, Tag> tags,
Map<int, StoragePath> storagePaths});
}
/// @nodoc
class __$$_DocumentDetailsStateCopyWithImpl<$Res>
extends _$DocumentDetailsStateCopyWithImpl<$Res, _$_DocumentDetailsState>
implements _$$_DocumentDetailsStateCopyWith<$Res> {
__$$_DocumentDetailsStateCopyWithImpl(_$_DocumentDetailsState _value,
$Res Function(_$_DocumentDetailsState) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? document = null,
Object? metaData = freezed,
Object? isFullContentLoaded = null,
Object? fullContent = freezed,
Object? suggestions = freezed,
Object? correspondents = null,
Object? documentTypes = null,
Object? tags = null,
Object? storagePaths = null,
}) {
return _then(_$_DocumentDetailsState(
document: null == document
? _value.document
: document // ignore: cast_nullable_to_non_nullable
as DocumentModel,
metaData: freezed == metaData
? _value.metaData
: metaData // ignore: cast_nullable_to_non_nullable
as DocumentMetaData?,
isFullContentLoaded: null == isFullContentLoaded
? _value.isFullContentLoaded
: isFullContentLoaded // ignore: cast_nullable_to_non_nullable
as bool,
fullContent: freezed == fullContent
? _value.fullContent
: fullContent // ignore: cast_nullable_to_non_nullable
as String?,
suggestions: freezed == suggestions
? _value.suggestions
: suggestions // ignore: cast_nullable_to_non_nullable
as FieldSuggestions?,
correspondents: null == correspondents
? _value._correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value._documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
tags: null == tags
? _value._tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
storagePaths: null == storagePaths
? _value._storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
));
}
}
/// @nodoc
class _$_DocumentDetailsState implements _DocumentDetailsState {
const _$_DocumentDetailsState(
{required this.document,
this.metaData,
this.isFullContentLoaded = false,
this.fullContent,
this.suggestions,
final Map<int, Correspondent> correspondents = const {},
final Map<int, DocumentType> documentTypes = const {},
final Map<int, Tag> tags = const {},
final Map<int, StoragePath> storagePaths = const {}})
: _correspondents = correspondents,
_documentTypes = documentTypes,
_tags = tags,
_storagePaths = storagePaths;
@override
final DocumentModel document;
@override
final DocumentMetaData? metaData;
@override
@JsonKey()
final bool isFullContentLoaded;
@override
final String? fullContent;
@override
final FieldSuggestions? suggestions;
final Map<int, Correspondent> _correspondents;
@override
@JsonKey()
Map<int, Correspondent> get correspondents {
if (_correspondents is EqualUnmodifiableMapView) return _correspondents;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_correspondents);
}
final Map<int, DocumentType> _documentTypes;
@override
@JsonKey()
Map<int, DocumentType> get documentTypes {
if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_documentTypes);
}
final Map<int, Tag> _tags;
@override
@JsonKey()
Map<int, Tag> get tags {
if (_tags is EqualUnmodifiableMapView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_tags);
}
final Map<int, StoragePath> _storagePaths;
@override
@JsonKey()
Map<int, StoragePath> get storagePaths {
if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_storagePaths);
}
@override
String toString() {
return 'DocumentDetailsState(document: $document, metaData: $metaData, isFullContentLoaded: $isFullContentLoaded, fullContent: $fullContent, suggestions: $suggestions, correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_DocumentDetailsState &&
(identical(other.document, document) ||
other.document == document) &&
(identical(other.metaData, metaData) ||
other.metaData == metaData) &&
(identical(other.isFullContentLoaded, isFullContentLoaded) ||
other.isFullContentLoaded == isFullContentLoaded) &&
(identical(other.fullContent, fullContent) ||
other.fullContent == fullContent) &&
(identical(other.suggestions, suggestions) ||
other.suggestions == suggestions) &&
const DeepCollectionEquality()
.equals(other._correspondents, _correspondents) &&
const DeepCollectionEquality()
.equals(other._documentTypes, _documentTypes) &&
const DeepCollectionEquality().equals(other._tags, _tags) &&
const DeepCollectionEquality()
.equals(other._storagePaths, _storagePaths));
}
@override
int get hashCode => Object.hash(
runtimeType,
document,
metaData,
isFullContentLoaded,
fullContent,
suggestions,
const DeepCollectionEquality().hash(_correspondents),
const DeepCollectionEquality().hash(_documentTypes),
const DeepCollectionEquality().hash(_tags),
const DeepCollectionEquality().hash(_storagePaths));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_DocumentDetailsStateCopyWith<_$_DocumentDetailsState> get copyWith =>
__$$_DocumentDetailsStateCopyWithImpl<_$_DocumentDetailsState>(
this, _$identity);
}
abstract class _DocumentDetailsState implements DocumentDetailsState {
const factory _DocumentDetailsState(
{required final DocumentModel document,
final DocumentMetaData? metaData,
final bool isFullContentLoaded,
final String? fullContent,
final FieldSuggestions? suggestions,
final Map<int, Correspondent> correspondents,
final Map<int, DocumentType> documentTypes,
final Map<int, Tag> tags,
final Map<int, StoragePath> storagePaths}) = _$_DocumentDetailsState;
@override
DocumentModel get document;
@override
DocumentMetaData? get metaData;
@override
bool get isFullContentLoaded;
@override
String? get fullContent;
@override
FieldSuggestions? get suggestions;
@override
Map<int, Correspondent> get correspondents;
@override
Map<int, DocumentType> get documentTypes;
@override
Map<int, Tag> get tags;
@override
Map<int, StoragePath> get storagePaths;
@override
@JsonKey(ignore: true)
_$$_DocumentDetailsStateCopyWith<_$_DocumentDetailsState> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1,42 +1,16 @@
part of 'document_details_cubit.dart'; part of 'document_details_cubit.dart';
class DocumentDetailsState with EquatableMixin { @freezed
final DocumentModel document; class DocumentDetailsState with _$DocumentDetailsState {
final DocumentMetaData? metaData; const factory DocumentDetailsState({
final bool isFullContentLoaded; required DocumentModel document,
final String? fullContent;
final FieldSuggestions suggestions;
const DocumentDetailsState({
required this.document,
this.metaData,
this.suggestions = const FieldSuggestions(),
this.isFullContentLoaded = false,
this.fullContent,
});
@override
List<Object?> get props => [
document,
suggestions,
isFullContentLoaded,
fullContent,
metaData,
];
DocumentDetailsState copyWith({
DocumentModel? document,
FieldSuggestions? suggestions,
bool? isFullContentLoaded,
String? fullContent,
DocumentMetaData? metaData, DocumentMetaData? metaData,
}) { @Default(false) bool isFullContentLoaded,
return DocumentDetailsState( String? fullContent,
document: document ?? this.document, FieldSuggestions? suggestions,
suggestions: suggestions ?? this.suggestions, @Default({}) Map<int, Correspondent> correspondents,
isFullContentLoaded: isFullContentLoaded ?? this.isFullContentLoaded, @Default({}) Map<int, DocumentType> documentTypes,
fullContent: fullContent ?? this.fullContent, @Default({}) Map<int, Tag> tags,
metaData: metaData ?? this.metaData, @Default({}) Map<int, StoragePath> storagePaths,
); }) = _DocumentDetailsState;
}
} }
@@ -186,6 +186,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
document: state.document, document: state.document,
itemSpacing: _itemSpacing, itemSpacing: _itemSpacing,
queryString: widget.titleAndContentQueryString, queryString: widget.titleAndContentQueryString,
), ),
DocumentContentWidget( DocumentContentWidget(
isFullContentLoaded: state.isFullContentLoaded, isFullContentLoaded: state.isFullContentLoaded,
@@ -11,6 +11,10 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
class DocumentOverviewWidget extends StatelessWidget { class DocumentOverviewWidget extends StatelessWidget {
final DocumentModel document; final DocumentModel document;
final Map<int, Correspondent> availableCorrespondents;
final Map<int, DocumentType> availableDocumentTypes;
final Map<int, Tag> availableTags;
final Map<int, StoragePath> availableStoragePaths;
final String? queryString; final String? queryString;
final double itemSpacing; final double itemSpacing;
const DocumentOverviewWidget({ const DocumentOverviewWidget({
@@ -18,6 +22,10 @@ class DocumentOverviewWidget extends StatelessWidget {
required this.document, required this.document,
this.queryString, this.queryString,
required this.itemSpacing, required this.itemSpacing,
required this.availableCorrespondents,
required this.availableDocumentTypes,
required this.availableTags,
required this.availableStoragePaths,
}); });
@override @override
@@ -47,7 +55,7 @@ class DocumentOverviewWidget extends StatelessWidget {
label: S.of(context)!.documentType, label: S.of(context)!.documentType,
content: LabelText<DocumentType>( content: LabelText<DocumentType>(
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
id: document.documentType, label: availableDocumentTypes[document.documentType],
), ),
).paddedOnly(bottom: itemSpacing), ).paddedOnly(bottom: itemSpacing),
), ),
@@ -57,7 +65,7 @@ class DocumentOverviewWidget extends StatelessWidget {
label: S.of(context)!.correspondent, label: S.of(context)!.correspondent,
content: LabelText<Correspondent>( content: LabelText<Correspondent>(
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
id: document.correspondent, label: availableCorrespondents[document.correspondent],
), ),
).paddedOnly(bottom: itemSpacing), ).paddedOnly(bottom: itemSpacing),
), ),
@@ -65,8 +73,8 @@ class DocumentOverviewWidget extends StatelessWidget {
visible: document.storagePath != null, visible: document.storagePath != null,
child: DetailsItem( child: DetailsItem(
label: S.of(context)!.storagePath, label: S.of(context)!.storagePath,
content: StoragePathWidget( content: LabelText<StoragePath>(
pathId: document.storagePath, label: availableStoragePaths[document.storagePath],
), ),
).paddedOnly(bottom: itemSpacing), ).paddedOnly(bottom: itemSpacing),
), ),
@@ -78,7 +86,7 @@ class DocumentOverviewWidget extends StatelessWidget {
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: TagsWidget( child: TagsWidget(
isClickable: false, isClickable: false,
tagIds: document.tags, tags: document.tags.map((e) => availableTags[e]!).toList(),
), ),
), ),
).paddedOnly(bottom: itemSpacing), ).paddedOnly(bottom: itemSpacing),
@@ -1,66 +1,41 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
part 'document_edit_state.dart'; part 'document_edit_state.dart';
part 'document_edit_cubit.freezed.dart';
class DocumentEditCubit extends Cubit<DocumentEditState> { class DocumentEditCubit extends Cubit<DocumentEditState> {
final DocumentModel _initialDocument; final DocumentModel _initialDocument;
final PaperlessDocumentsApi _docsApi; final PaperlessDocumentsApi _docsApi;
final DocumentChangedNotifier _notifier; final DocumentChangedNotifier _notifier;
final LabelRepository<Correspondent> _correspondentRepository; final LabelRepository _labelRepository;
final LabelRepository<DocumentType> _documentTypeRepository;
final LabelRepository<StoragePath> _storagePathRepository;
final LabelRepository<Tag> _tagRepository;
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
DocumentEditCubit( DocumentEditCubit(
DocumentModel document, { this._labelRepository,
required PaperlessDocumentsApi documentsApi, this._docsApi,
required LabelRepository<Correspondent> correspondentRepository, this._notifier, {
required LabelRepository<DocumentType> documentTypeRepository, required DocumentModel document,
required LabelRepository<StoragePath> storagePathRepository,
required LabelRepository<Tag> tagRepository,
required DocumentChangedNotifier notifier,
}) : _initialDocument = document, }) : _initialDocument = document,
_docsApi = documentsApi,
_correspondentRepository = correspondentRepository,
_documentTypeRepository = documentTypeRepository,
_storagePathRepository = storagePathRepository,
_tagRepository = tagRepository,
_notifier = notifier,
super( super(
DocumentEditState( DocumentEditState(
document: document, document: document,
correspondents: correspondentRepository.current?.values ?? {}, correspondents: _labelRepository.state.correspondents,
documentTypes: documentTypeRepository.current?.values ?? {}, documentTypes: _labelRepository.state.documentTypes,
storagePaths: storagePathRepository.current?.values ?? {}, storagePaths: _labelRepository.state.storagePaths,
tags: tagRepository.current?.values ?? {}, tags: _labelRepository.state.tags,
), ),
) { ) {
_notifier.subscribe(this, onUpdated: replace); _notifier.subscribe(this, onUpdated: replace);
_subscriptions.add( _labelRepository.subscribe(
_correspondentRepository.values this,
.listen((v) => emit(state.copyWith(correspondents: v?.values))), onStateChanged: (labels) => emit(state.copyWith()),
);
_subscriptions.add(
_documentTypeRepository.values
.listen((v) => emit(state.copyWith(documentTypes: v?.values))),
);
_subscriptions.add(
_storagePathRepository.values
.listen((v) => emit(state.copyWith(storagePaths: v?.values))),
);
_subscriptions.add(
_tagRepository.values.listen(
(v) => emit(state.copyWith(tags: v?.values)),
),
); );
} }
@@ -70,20 +45,20 @@ class DocumentEditCubit extends Cubit<DocumentEditState> {
// Reload changed labels (documentCount property changes with removal/add) // Reload changed labels (documentCount property changes with removal/add)
if (document.documentType != _initialDocument.documentType) { if (document.documentType != _initialDocument.documentType) {
_documentTypeRepository _labelRepository.findDocumentType(
.find((document.documentType ?? _initialDocument.documentType)!); (document.documentType ?? _initialDocument.documentType)!);
} }
if (document.correspondent != _initialDocument.correspondent) { if (document.correspondent != _initialDocument.correspondent) {
_correspondentRepository _labelRepository.findCorrespondent(
.find((document.correspondent ?? _initialDocument.correspondent)!); (document.correspondent ?? _initialDocument.correspondent)!);
} }
if (document.storagePath != _initialDocument.storagePath) { if (document.storagePath != _initialDocument.storagePath) {
_storagePathRepository _labelRepository.findStoragePath(
.find((document.storagePath ?? _initialDocument.storagePath)!); (document.storagePath ?? _initialDocument.storagePath)!);
} }
if (!const DeepCollectionEquality.unordered() if (!const DeepCollectionEquality.unordered()
.equals(document.tags, _initialDocument.tags)) { .equals(document.tags, _initialDocument.tags)) {
_tagRepository.findAll(document.tags); _labelRepository.findAllTags(document.tags);
} }
} }
@@ -0,0 +1,256 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'document_edit_cubit.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
mixin _$DocumentEditState {
DocumentModel get document => throw _privateConstructorUsedError;
Map<int, Correspondent> get correspondents =>
throw _privateConstructorUsedError;
Map<int, DocumentType> get documentTypes =>
throw _privateConstructorUsedError;
Map<int, StoragePath> get storagePaths => throw _privateConstructorUsedError;
Map<int, Tag> get tags => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$DocumentEditStateCopyWith<DocumentEditState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $DocumentEditStateCopyWith<$Res> {
factory $DocumentEditStateCopyWith(
DocumentEditState value, $Res Function(DocumentEditState) then) =
_$DocumentEditStateCopyWithImpl<$Res, DocumentEditState>;
@useResult
$Res call(
{DocumentModel document,
Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, StoragePath> storagePaths,
Map<int, Tag> tags});
}
/// @nodoc
class _$DocumentEditStateCopyWithImpl<$Res, $Val extends DocumentEditState>
implements $DocumentEditStateCopyWith<$Res> {
_$DocumentEditStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? document = null,
Object? correspondents = null,
Object? documentTypes = null,
Object? storagePaths = null,
Object? tags = null,
}) {
return _then(_value.copyWith(
document: null == document
? _value.document
: document // ignore: cast_nullable_to_non_nullable
as DocumentModel,
correspondents: null == correspondents
? _value.correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value.documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
storagePaths: null == storagePaths
? _value.storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
tags: null == tags
? _value.tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
) as $Val);
}
}
/// @nodoc
abstract class _$$_DocumentEditStateCopyWith<$Res>
implements $DocumentEditStateCopyWith<$Res> {
factory _$$_DocumentEditStateCopyWith(_$_DocumentEditState value,
$Res Function(_$_DocumentEditState) then) =
__$$_DocumentEditStateCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{DocumentModel document,
Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, StoragePath> storagePaths,
Map<int, Tag> tags});
}
/// @nodoc
class __$$_DocumentEditStateCopyWithImpl<$Res>
extends _$DocumentEditStateCopyWithImpl<$Res, _$_DocumentEditState>
implements _$$_DocumentEditStateCopyWith<$Res> {
__$$_DocumentEditStateCopyWithImpl(
_$_DocumentEditState _value, $Res Function(_$_DocumentEditState) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? document = null,
Object? correspondents = null,
Object? documentTypes = null,
Object? storagePaths = null,
Object? tags = null,
}) {
return _then(_$_DocumentEditState(
document: null == document
? _value.document
: document // ignore: cast_nullable_to_non_nullable
as DocumentModel,
correspondents: null == correspondents
? _value._correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value._documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
storagePaths: null == storagePaths
? _value._storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
tags: null == tags
? _value._tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
));
}
}
/// @nodoc
class _$_DocumentEditState implements _DocumentEditState {
const _$_DocumentEditState(
{required this.document,
required final Map<int, Correspondent> correspondents,
required final Map<int, DocumentType> documentTypes,
required final Map<int, StoragePath> storagePaths,
required final Map<int, Tag> tags})
: _correspondents = correspondents,
_documentTypes = documentTypes,
_storagePaths = storagePaths,
_tags = tags;
@override
final DocumentModel document;
final Map<int, Correspondent> _correspondents;
@override
Map<int, Correspondent> get correspondents {
if (_correspondents is EqualUnmodifiableMapView) return _correspondents;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_correspondents);
}
final Map<int, DocumentType> _documentTypes;
@override
Map<int, DocumentType> get documentTypes {
if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_documentTypes);
}
final Map<int, StoragePath> _storagePaths;
@override
Map<int, StoragePath> get storagePaths {
if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_storagePaths);
}
final Map<int, Tag> _tags;
@override
Map<int, Tag> get tags {
if (_tags is EqualUnmodifiableMapView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_tags);
}
@override
String toString() {
return 'DocumentEditState(document: $document, correspondents: $correspondents, documentTypes: $documentTypes, storagePaths: $storagePaths, tags: $tags)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_DocumentEditState &&
(identical(other.document, document) ||
other.document == document) &&
const DeepCollectionEquality()
.equals(other._correspondents, _correspondents) &&
const DeepCollectionEquality()
.equals(other._documentTypes, _documentTypes) &&
const DeepCollectionEquality()
.equals(other._storagePaths, _storagePaths) &&
const DeepCollectionEquality().equals(other._tags, _tags));
}
@override
int get hashCode => Object.hash(
runtimeType,
document,
const DeepCollectionEquality().hash(_correspondents),
const DeepCollectionEquality().hash(_documentTypes),
const DeepCollectionEquality().hash(_storagePaths),
const DeepCollectionEquality().hash(_tags));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_DocumentEditStateCopyWith<_$_DocumentEditState> get copyWith =>
__$$_DocumentEditStateCopyWithImpl<_$_DocumentEditState>(
this, _$identity);
}
abstract class _DocumentEditState implements DocumentEditState {
const factory _DocumentEditState(
{required final DocumentModel document,
required final Map<int, Correspondent> correspondents,
required final Map<int, DocumentType> documentTypes,
required final Map<int, StoragePath> storagePaths,
required final Map<int, Tag> tags}) = _$_DocumentEditState;
@override
DocumentModel get document;
@override
Map<int, Correspondent> get correspondents;
@override
Map<int, DocumentType> get documentTypes;
@override
Map<int, StoragePath> get storagePaths;
@override
Map<int, Tag> get tags;
@override
@JsonKey(ignore: true)
_$$_DocumentEditStateCopyWith<_$_DocumentEditState> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1,43 +1,12 @@
part of 'document_edit_cubit.dart'; part of 'document_edit_cubit.dart';
class DocumentEditState extends Equatable { @freezed
final DocumentModel document; class DocumentEditState with _$DocumentEditState {
const factory DocumentEditState({
final Map<int, Correspondent> correspondents; required DocumentModel document,
final Map<int, DocumentType> documentTypes; required Map<int, Correspondent> correspondents,
final Map<int, StoragePath> storagePaths; required Map<int, DocumentType> documentTypes,
final Map<int, Tag> tags; required Map<int, StoragePath> storagePaths,
required Map<int, Tag> tags,
const DocumentEditState({ }) = _DocumentEditState;
required this.correspondents,
required this.documentTypes,
required this.storagePaths,
required this.tags,
required this.document,
});
@override
List<Object> get props => [
correspondents,
documentTypes,
storagePaths,
tags,
document,
];
DocumentEditState copyWith({
Map<int, Correspondent>? correspondents,
Map<int, DocumentType>? documentTypes,
Map<int, StoragePath>? storagePaths,
Map<int, Tag>? tags,
DocumentModel? document,
}) {
return DocumentEditState(
document: document ?? this.document,
correspondents: correspondents ?? this.correspondents,
documentTypes: documentTypes ?? this.documentTypes,
storagePaths: storagePaths ?? this.storagePaths,
tags: tags ?? this.tags,
);
}
} }
@@ -194,8 +194,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
LabelFormField<StoragePath>( LabelFormField<StoragePath>(
notAssignedSelectable: false, notAssignedSelectable: false,
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider( labelCreationWidgetBuilder: (initialValue) =>
create: (context) => context.read<LabelRepository<StoragePath>>(), RepositoryProvider.value(
value: context.read<LabelRepository>(),
child: AddStoragePathPage(initalName: initialValue), child: AddStoragePathPage(initalName: initialValue),
), ),
textFieldLabel: S.of(context)!.storagePath, textFieldLabel: S.of(context)!.storagePath,
@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
@@ -19,10 +20,18 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
@override @override
final PaperlessDocumentsApi api; final PaperlessDocumentsApi api;
final LabelRepository _labelRepository;
@override @override
final DocumentChangedNotifier notifier; final DocumentChangedNotifier notifier;
DocumentsCubit(this.api, this.notifier) : super(const DocumentsState()) { DocumentsCubit(this.api, this.notifier, this._labelRepository)
: super(DocumentsState(
correspondents: _labelRepository.state.correspondents,
documentTypes: _labelRepository.state.documentTypes,
storagePaths: _labelRepository.state.storagePaths,
tags: _labelRepository.state.tags,
)) {
notifier.subscribe( notifier.subscribe(
this, this,
onUpdated: (document) { onUpdated: (document) {
@@ -45,6 +54,17 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
); );
}, },
); );
_labelRepository.subscribe(
this,
onStateChanged: (labels) => emit(
state.copyWith(
correspondents: labels.correspondents,
documentTypes: labels.documentTypes,
storagePaths: labels.storagePaths,
tags: labels.tags,
),
),
);
} }
Future<void> bulkDelete(List<DocumentModel> documents) async { Future<void> bulkDelete(List<DocumentModel> documents) async {
@@ -101,6 +121,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
@override @override
Future<void> close() { Future<void> close() {
notifier.unsubscribe(this); notifier.unsubscribe(this);
_labelRepository.unsubscribe(this);
return super.close(); return super.close();
} }
@@ -1,10 +1,15 @@
part of 'documents_cubit.dart'; part of 'documents_cubit.dart';
@JsonSerializable() @JsonSerializable(ignoreUnannotated: true)
class DocumentsState extends DocumentPagingState { class DocumentsState extends DocumentPagingState {
@JsonKey(includeFromJson: false, includeToJson: false)
final List<DocumentModel> selection; final List<DocumentModel> selection;
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, Tag> tags;
final Map<int, StoragePath> storagePaths;
@JsonKey()
final ViewType viewType; final ViewType viewType;
const DocumentsState({ const DocumentsState({
@@ -14,6 +19,10 @@ class DocumentsState extends DocumentPagingState {
super.filter = const DocumentFilter(), super.filter = const DocumentFilter(),
super.hasLoaded = false, super.hasLoaded = false,
super.isLoading = false, super.isLoading = false,
this.correspondents = const {},
this.documentTypes = const {},
this.tags = const {},
this.storagePaths = const {},
}); });
List<int> get selectedIds => selection.map((e) => e.id).toList(); List<int> get selectedIds => selection.map((e) => e.id).toList();
@@ -25,6 +34,10 @@ class DocumentsState extends DocumentPagingState {
DocumentFilter? filter, DocumentFilter? filter,
List<DocumentModel>? selection, List<DocumentModel>? selection,
ViewType? viewType, ViewType? viewType,
Map<int, Correspondent>? correspondents,
Map<int, DocumentType>? documentTypes,
Map<int, Tag>? tags,
Map<int, StoragePath>? storagePaths,
}) { }) {
return DocumentsState( return DocumentsState(
hasLoaded: hasLoaded ?? this.hasLoaded, hasLoaded: hasLoaded ?? this.hasLoaded,
@@ -33,6 +46,10 @@ class DocumentsState extends DocumentPagingState {
filter: filter ?? this.filter, filter: filter ?? this.filter,
selection: selection ?? this.selection, selection: selection ?? this.selection,
viewType: viewType ?? this.viewType, viewType: viewType ?? this.viewType,
correspondents: correspondents ?? this.correspondents,
documentTypes: documentTypes ?? this.documentTypes,
tags: tags ?? this.tags,
storagePaths: storagePaths ?? this.storagePaths,
); );
} }
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_grid_loading_widget.dart'; import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_grid_loading_widget.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_detailed_item.dart'; import 'package:paperless_mobile/features/documents/view/widgets/items/document_detailed_item.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart'; import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart';
@@ -25,7 +24,13 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
final void Function(int? id)? onDocumentTypeSelected; final void Function(int? id)? onDocumentTypeSelected;
final void Function(int? id)? onStoragePathSelected; final void Function(int? id)? onStoragePathSelected;
bool get showLoadingPlaceholder => (!hasLoaded && isLoading); final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, Tag> tags;
final Map<int, StoragePath> storagePaths;
bool get showLoadingPlaceholder => !hasLoaded && isLoading;
const AdaptiveDocumentsView({ const AdaptiveDocumentsView({
super.key, super.key,
this.selectedDocumentIds = const [], this.selectedDocumentIds = const [],
@@ -42,6 +47,10 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
required this.isLoading, required this.isLoading,
required this.hasLoaded, required this.hasLoaded,
this.enableHeroAnimation = true, this.enableHeroAnimation = true,
required this.correspondents,
required this.documentTypes,
required this.tags,
required this.storagePaths,
}); });
AdaptiveDocumentsView.fromPagedState( AdaptiveDocumentsView.fromPagedState(
@@ -58,6 +67,10 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
required this.hasInternetConnection, required this.hasInternetConnection,
this.viewType = ViewType.list, this.viewType = ViewType.list,
this.selectedDocumentIds = const [], this.selectedDocumentIds = const [],
required this.correspondents,
required this.documentTypes,
required this.tags,
required this.storagePaths,
}) : documents = state.documents, }) : documents = state.documents,
isLoading = state.isLoading, isLoading = state.isLoading,
hasLoaded = state.hasLoaded; hasLoaded = state.hasLoaded;
@@ -80,6 +93,10 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
super.enableHeroAnimation, super.enableHeroAnimation,
required super.isLoading, required super.isLoading,
required super.hasLoaded, required super.hasLoaded,
required super.correspondents,
required super.documentTypes,
required super.tags,
required super.storagePaths,
}); });
@override @override
@@ -96,27 +113,29 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
Widget _buildListView() { Widget _buildListView() {
if (showLoadingPlaceholder) { if (showLoadingPlaceholder) {
return DocumentsListLoadingWidget.sliver(); return const DocumentsListLoadingWidget.sliver();
} }
return SliverList( return SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
childCount: documents.length, childCount: documents.length,
(context, index) { (context, index) {
final document = documents.elementAt(index); final document = documents.elementAt(index);
return LabelRepositoriesProvider( return DocumentListItem(
child: DocumentListItem( isLabelClickable: isLabelClickable,
isLabelClickable: isLabelClickable, document: document,
document: document, onTap: onTap,
onTap: onTap, isSelected: selectedDocumentIds.contains(document.id),
isSelected: selectedDocumentIds.contains(document.id), onSelected: onSelected,
onSelected: onSelected, isSelectionActive: selectedDocumentIds.isNotEmpty,
isSelectionActive: selectedDocumentIds.isNotEmpty, onTagSelected: onTagSelected,
onTagSelected: onTagSelected, onCorrespondentSelected: onCorrespondentSelected,
onCorrespondentSelected: onCorrespondentSelected, onDocumentTypeSelected: onDocumentTypeSelected,
onDocumentTypeSelected: onDocumentTypeSelected, onStoragePathSelected: onStoragePathSelected,
onStoragePathSelected: onStoragePathSelected, enableHeroAnimation: enableHeroAnimation,
enableHeroAnimation: enableHeroAnimation, correspondents: correspondents,
), documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
); );
}, },
), ),
@@ -133,21 +152,23 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
childCount: documents.length, childCount: documents.length,
(context, index) { (context, index) {
final document = documents.elementAt(index); final document = documents.elementAt(index);
return LabelRepositoriesProvider( return DocumentDetailedItem(
child: DocumentDetailedItem( isLabelClickable: isLabelClickable,
isLabelClickable: isLabelClickable, document: document,
document: document, onTap: onTap,
onTap: onTap, isSelected: selectedDocumentIds.contains(document.id),
isSelected: selectedDocumentIds.contains(document.id), onSelected: onSelected,
onSelected: onSelected, isSelectionActive: selectedDocumentIds.isNotEmpty,
isSelectionActive: selectedDocumentIds.isNotEmpty, onTagSelected: onTagSelected,
onTagSelected: onTagSelected, onCorrespondentSelected: onCorrespondentSelected,
onCorrespondentSelected: onCorrespondentSelected, onDocumentTypeSelected: onDocumentTypeSelected,
onDocumentTypeSelected: onDocumentTypeSelected, onStoragePathSelected: onStoragePathSelected,
onStoragePathSelected: onStoragePathSelected, enableHeroAnimation: enableHeroAnimation,
enableHeroAnimation: enableHeroAnimation, highlights: document.searchHit?.highlights,
highlights: document.searchHit?.highlights, correspondents: correspondents,
), documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
); );
}, },
), ),
@@ -180,6 +201,10 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
onDocumentTypeSelected: onDocumentTypeSelected, onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected, onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation, enableHeroAnimation: enableHeroAnimation,
correspondents: correspondents,
documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
); );
}, },
); );
@@ -205,6 +230,10 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
super.selectedDocumentIds, super.selectedDocumentIds,
super.viewType, super.viewType,
super.enableHeroAnimation = true, super.enableHeroAnimation = true,
required super.correspondents,
required super.documentTypes,
required super.tags,
required super.storagePaths,
}); });
@override @override
@@ -231,20 +260,22 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
itemCount: documents.length, itemCount: documents.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final document = documents.elementAt(index); final document = documents.elementAt(index);
return LabelRepositoriesProvider( return DocumentListItem(
child: DocumentListItem( isLabelClickable: isLabelClickable,
isLabelClickable: isLabelClickable, document: document,
document: document, onTap: onTap,
onTap: onTap, isSelected: selectedDocumentIds.contains(document.id),
isSelected: selectedDocumentIds.contains(document.id), onSelected: onSelected,
onSelected: onSelected, isSelectionActive: selectedDocumentIds.isNotEmpty,
isSelectionActive: selectedDocumentIds.isNotEmpty, onTagSelected: onTagSelected,
onTagSelected: onTagSelected, onCorrespondentSelected: onCorrespondentSelected,
onCorrespondentSelected: onCorrespondentSelected, onDocumentTypeSelected: onDocumentTypeSelected,
onDocumentTypeSelected: onDocumentTypeSelected, onStoragePathSelected: onStoragePathSelected,
onStoragePathSelected: onStoragePathSelected, enableHeroAnimation: enableHeroAnimation,
enableHeroAnimation: enableHeroAnimation, correspondents: correspondents,
), documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
); );
}, },
); );
@@ -252,7 +283,7 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
Widget _buildFullView() { Widget _buildFullView() {
if (showLoadingPlaceholder) { if (showLoadingPlaceholder) {
return DocumentsListLoadingWidget(); return const DocumentsListLoadingWidget();
} }
return ListView.builder( return ListView.builder(
@@ -263,20 +294,22 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
itemCount: documents.length, itemCount: documents.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final document = documents.elementAt(index); final document = documents.elementAt(index);
return LabelRepositoriesProvider( return DocumentDetailedItem(
child: DocumentDetailedItem( isLabelClickable: isLabelClickable,
isLabelClickable: isLabelClickable, document: document,
document: document, onTap: onTap,
onTap: onTap, isSelected: selectedDocumentIds.contains(document.id),
isSelected: selectedDocumentIds.contains(document.id), onSelected: onSelected,
onSelected: onSelected, isSelectionActive: selectedDocumentIds.isNotEmpty,
isSelectionActive: selectedDocumentIds.isNotEmpty, onTagSelected: onTagSelected,
onTagSelected: onTagSelected, onCorrespondentSelected: onCorrespondentSelected,
onCorrespondentSelected: onCorrespondentSelected, onDocumentTypeSelected: onDocumentTypeSelected,
onDocumentTypeSelected: onDocumentTypeSelected, onStoragePathSelected: onStoragePathSelected,
onStoragePathSelected: onStoragePathSelected, enableHeroAnimation: enableHeroAnimation,
enableHeroAnimation: enableHeroAnimation, correspondents: correspondents,
), documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
); );
}, },
); );
@@ -284,7 +317,7 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
Widget _buildGridView() { Widget _buildGridView() {
if (showLoadingPlaceholder) { if (showLoadingPlaceholder) {
return DocumentGridLoadingWidget(); return const DocumentGridLoadingWidget();
} }
return GridView.builder( return GridView.builder(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@@ -311,6 +344,10 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
onDocumentTypeSelected: onDocumentTypeSelected, onDocumentTypeSelected: onDocumentTypeSelected,
onStoragePathSelected: onStoragePathSelected, onStoragePathSelected: onStoragePathSelected,
enableHeroAnimation: enableHeroAnimation, enableHeroAnimation: enableHeroAnimation,
correspondents: correspondents,
documentTypes: documentTypes,
storagePaths: storagePaths,
tags: tags,
); );
}, },
); );
@@ -1,8 +1,10 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart'; import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart'; import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart'; import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
@@ -26,6 +28,10 @@ class DocumentDetailedItem extends DocumentItem {
super.onStoragePathSelected, super.onStoragePathSelected,
super.onTagSelected, super.onTagSelected,
super.onTap, super.onTap,
required super.tags,
required super.correspondents,
required super.documentTypes,
required super.storagePaths,
}); });
@override @override
@@ -113,7 +119,7 @@ class DocumentDetailedItem extends DocumentItem {
textStyle: Theme.of(context).textTheme.titleSmall?.apply( textStyle: Theme.of(context).textTheme.titleSmall?.apply(
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
correspondentId: document.correspondent, correspondent: context.read<DocumentsCubit>().correspondent,
), ),
], ],
).paddedLTRB(8, 0, 8, 4), ).paddedLTRB(8, 0, 8, 4),
@@ -21,6 +21,10 @@ class DocumentGridItem extends DocumentItem {
super.onTagSelected, super.onTagSelected,
super.onTap, super.onTap,
required super.enableHeroAnimation, required super.enableHeroAnimation,
required super.tags,
required super.correspondents,
required super.documentTypes,
required super.storagePaths,
}); });
@override @override
@@ -54,10 +58,10 @@ class DocumentGridItem extends DocumentItem {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
CorrespondentWidget( CorrespondentWidget(
correspondentId: document.correspondent, correspondent: correspondents[document.correspondent],
), ),
DocumentTypeWidget( DocumentTypeWidget(
documentTypeId: document.documentType, documentType: documentTypes[document.documentType],
), ),
Text( Text(
document.title, document.title,
@@ -67,7 +71,7 @@ class DocumentGridItem extends DocumentItem {
), ),
const Spacer(), const Spacer(),
TagsWidget( TagsWidget(
tagIds: document.tags, tags: document.tags.map((e) => tags[e]!).toList(),
isMultiLine: false, isMultiLine: false,
onTagSelected: onTagSelected, onTagSelected: onTagSelected,
), ),
@@ -10,6 +10,11 @@ abstract class DocumentItem extends StatelessWidget {
final bool isLabelClickable; final bool isLabelClickable;
final bool enableHeroAnimation; final bool enableHeroAnimation;
final Map<int, Tag> tags;
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, StoragePath> storagePaths;
final void Function(int tagId)? onTagSelected; final void Function(int tagId)? onTagSelected;
final void Function(int? correspondentId)? onCorrespondentSelected; final void Function(int? correspondentId)? onCorrespondentSelected;
final void Function(int? documentTypeId)? onDocumentTypeSelected; final void Function(int? documentTypeId)? onDocumentTypeSelected;
@@ -28,5 +33,9 @@ abstract class DocumentItem extends StatelessWidget {
this.onDocumentTypeSelected, this.onDocumentTypeSelected,
this.onStoragePathSelected, this.onStoragePathSelected,
required this.enableHeroAnimation, required this.enableHeroAnimation,
required this.tags,
required this.correspondents,
required this.documentTypes,
required this.storagePaths,
}); });
} }
@@ -5,7 +5,6 @@ import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart'; import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart'; import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/document_type_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart'; import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart'; import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
@@ -25,113 +24,109 @@ class DocumentListItem extends DocumentItem {
super.onTagSelected, super.onTagSelected,
super.onTap, super.onTap,
super.enableHeroAnimation = true, super.enableHeroAnimation = true,
required super.tags,
required super.correspondents,
required super.documentTypes,
required super.storagePaths,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DocumentTypeBlocProvider( return Material(
child: Material( child: ListTile(
child: ListTile( dense: true,
dense: true, selected: isSelected,
selected: isSelected, onTap: () => _onTap(),
onTap: () => _onTap(), selectedTileColor: Theme.of(context).colorScheme.inversePrimary,
selectedTileColor: Theme.of(context).colorScheme.inversePrimary, onLongPress: () => onSelected?.call(document),
onLongPress: () => onSelected?.call(document), title: Column(
title: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
children: [ Row(
Row( children: [
children: [ AbsorbPointer(
AbsorbPointer( absorbing: isSelectionActive,
absorbing: isSelectionActive, child: CorrespondentWidget(
child: CorrespondentWidget( isClickable: isLabelClickable,
isClickable: isLabelClickable, correspondent: correspondents[document.correspondent],
correspondentId: document.correspondent, onSelected: onCorrespondentSelected,
onSelected: onCorrespondentSelected,
),
), ),
],
),
Text(
document.title,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
AbsorbPointer(
absorbing: isSelectionActive,
child: TagsWidget(
isClickable: isLabelClickable,
tagIds: document.tags,
isMultiLine: false,
onTagSelected: (id) => onTagSelected?.call(id),
), ),
) ],
], ),
), Text(
subtitle: Padding( document.title,
padding: const EdgeInsets.symmetric(vertical: 4), overflow: TextOverflow.ellipsis,
child: BlocBuilder<LabelCubit<DocumentType>, maxLines: 1,
LabelState<DocumentType>>( ),
builder: (context, docTypes) { AbsorbPointer(
return RichText( absorbing: isSelectionActive,
maxLines: 1, child: TagsWidget(
overflow: TextOverflow.ellipsis, isClickable: isLabelClickable,
text: TextSpan( tags: document.tags.map((e) => tags[e]!).toList(),
text: DateFormat.yMMMd().format(document.created), isMultiLine: false,
style: Theme.of(context) onTagSelected: (id) => onTagSelected?.call(id),
.textTheme
.labelSmall
?.apply(color: Colors.grey),
children: document.documentType != null
? [
const TextSpan(text: '\u30FB'),
TextSpan(
text: docTypes
.labels[document.documentType]?.name,
),
]
: null,
),
);
},
)
// Row(
// children: [
// Text(
// DateFormat.yMMMd().format(document.created),
// style: Theme.of(context)
// .textTheme
// .bodySmall
// ?.apply(color: Colors.grey),
// ),
// if (document.documentType != null) ...[
// Text("\u30FB"),
// DocumentTypeWidget(
// documentTypeId: document.documentType,
// textStyle: Theme.of(context).textTheme.bodySmall?.apply(
// color: Colors.grey,
// overflow: TextOverflow.ellipsis,
// ),
// ),
// ],
// ],
// ),
),
isThreeLine: document.tags.isNotEmpty,
leading: AspectRatio(
aspectRatio: _a4AspectRatio,
child: GestureDetector(
child: DocumentPreview(
document: document,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
enableHero: enableHeroAnimation,
), ),
)
],
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: RichText(
maxLines: 1,
overflow: TextOverflow.ellipsis,
text: TextSpan(
text: DateFormat.yMMMd().format(document.created),
style: Theme.of(context)
.textTheme
.labelSmall
?.apply(color: Colors.grey),
children: document.documentType != null
? [
const TextSpan(text: '\u30FB'),
TextSpan(
text: documentTypes[document.documentType]?.name,
),
]
: null,
), ),
), ),
contentPadding: const EdgeInsets.all(8.0), // Row(
// children: [
// Text(
// DateFormat.yMMMd().format(document.created),
// style: Theme.of(context)
// .textTheme
// .bodySmall
// ?.apply(color: Colors.grey),
// ),
// if (document.documentType != null) ...[
// Text("\u30FB"),
// DocumentTypeWidget(
// documentTypeId: document.documentType,
// textStyle: Theme.of(context).textTheme.bodySmall?.apply(
// color: Colors.grey,
// overflow: TextOverflow.ellipsis,
// ),
// ),
// ],
// ],
// ),
), ),
isThreeLine: document.tags.isNotEmpty,
leading: AspectRatio(
aspectRatio: _a4AspectRatio,
child: GestureDetector(
child: DocumentPreview(
document: document,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
enableHero: enableHeroAnimation,
),
),
),
contentPadding: const EdgeInsets.all(8.0),
), ),
); );
} }
@@ -71,7 +71,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
.paddedOnly(left: 4, right: 4), .paddedOnly(left: 4, right: 4),
_buildBulkEditStoragePathChip(context) _buildBulkEditStoragePathChip(context)
.paddedOnly(left: 4, right: 4), .paddedOnly(left: 4, right: 4),
// _buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4), _buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4),
], ],
), ),
), ),
@@ -100,9 +100,6 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
builder: (_) { builder: (_) {
return BlocProvider( return BlocProvider(
create: (context) => DocumentBulkActionCubit( create: (context) => DocumentBulkActionCubit(
context.read(),
context.read(),
context.read(),
context.read(), context.read(),
context.read(), context.read(),
context.read(), context.read(),
@@ -112,8 +109,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
return BulkEditLabelBottomSheet<Correspondent>( return BulkEditLabelBottomSheet<Correspondent>(
initialValue: initialValue, initialValue: initialValue,
title: "Bulk edit correspondent", title: "Bulk edit correspondent",
availableOptionsSelector: (state) => availableOptionsSelector: (state) => state.correspondents,
state.correspondentOptions,
formFieldLabel: S.of(context)!.correspondent, formFieldLabel: S.of(context)!.correspondent,
formFieldPrefixIcon: const Icon(Icons.person_outline), formFieldPrefixIcon: const Icon(Icons.person_outline),
onSubmit: (selectedId) async { onSubmit: (selectedId) async {
@@ -152,9 +148,6 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
builder: (_) { builder: (_) {
return BlocProvider( return BlocProvider(
create: (context) => DocumentBulkActionCubit( create: (context) => DocumentBulkActionCubit(
context.read(),
context.read(),
context.read(),
context.read(), context.read(),
context.read(), context.read(),
context.read(), context.read(),
@@ -164,8 +157,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
return BulkEditLabelBottomSheet<DocumentType>( return BulkEditLabelBottomSheet<DocumentType>(
initialValue: initialValue, initialValue: initialValue,
title: "Bulk edit document type", title: "Bulk edit document type",
availableOptionsSelector: (state) => availableOptionsSelector: (state) => state.documentTypes,
state.documentTypeOptions,
formFieldLabel: S.of(context)!.documentType, formFieldLabel: S.of(context)!.documentType,
formFieldPrefixIcon: const Icon(Icons.person_outline), formFieldPrefixIcon: const Icon(Icons.person_outline),
onSubmit: (selectedId) async { onSubmit: (selectedId) async {
@@ -204,9 +196,6 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
builder: (_) { builder: (_) {
return BlocProvider( return BlocProvider(
create: (context) => DocumentBulkActionCubit( create: (context) => DocumentBulkActionCubit(
context.read(),
context.read(),
context.read(),
context.read(), context.read(),
context.read(), context.read(),
context.read(), context.read(),
@@ -216,7 +205,7 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
return BulkEditLabelBottomSheet<StoragePath>( return BulkEditLabelBottomSheet<StoragePath>(
initialValue: initialValue, initialValue: initialValue,
title: "Bulk edit storage path", title: "Bulk edit storage path",
availableOptionsSelector: (state) => state.storagePathOptions, availableOptionsSelector: (state) => state.storagePaths,
formFieldLabel: S.of(context)!.storagePath, formFieldLabel: S.of(context)!.storagePath,
formFieldPrefixIcon: const Icon(Icons.folder_open_outlined), formFieldPrefixIcon: const Icon(Icons.folder_open_outlined),
onSubmit: (selectedId) async { onSubmit: (selectedId) async {
@@ -246,14 +235,11 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
topRight: Radius.circular(16), topRight: Radius.circular(16),
), ),
), ),
isScrollControlled: false, isScrollControlled: true,
context: context, context: context,
builder: (_) { builder: (_) {
return BlocProvider( return BlocProvider(
create: (context) => DocumentBulkActionCubit( create: (context) => DocumentBulkActionCubit(
context.read(),
context.read(),
context.read(),
context.read(), context.read(),
context.read(), context.read(),
context.read(), context.read(),
@@ -43,14 +43,7 @@ class SortDocumentsButton extends StatelessWidget {
child: MultiBlocProvider( child: MultiBlocProvider(
providers: [ providers: [
BlocProvider( BlocProvider(
create: (context) => LabelCubit<DocumentType>( create: (context) => LabelCubit(context.read()),
context.read<LabelRepository<DocumentType>>(),
),
),
BlocProvider(
create: (context) => LabelCubit<Correspondent>(
context.read<LabelRepository<Correspondent>>(),
),
), ),
], ],
child: SortFieldSelectionBottomSheet( child: SortFieldSelectionBottomSheet(
@@ -1,34 +1,44 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'edit_label_state.dart'; part 'edit_label_state.dart';
part 'edit_label_cubit.freezed.dart';
class EditLabelCubit<T extends Label> extends Cubit<EditLabelState<T>> { class EditLabelCubit extends Cubit<EditLabelState> with LabelCubitMixin {
final LabelRepository<T> _repository; @override
final LabelRepository labelRepository;
StreamSubscription? _subscription; EditLabelCubit(this.labelRepository) : super(const EditLabelState()) {
labelRepository.subscribe(
EditLabelCubit(LabelRepository<T> repository) this,
: _repository = repository, onChanged: (labels) => state.copyWith(
super(EditLabelState<T>(labels: repository.current?.values ?? {})) { correspondents: labels.correspondents,
_subscription = repository.values.listen( documentTypes: labels.documentTypes,
(event) => emit(EditLabelState(labels: event?.values ?? {})), tags: labels.tags,
storagePaths: labels.storagePaths,
),
); );
} }
Future<T> create(T label) => _repository.create(label);
Future<T> update(T label) => _repository.update(label);
Future<void> delete(T label) => _repository.delete(label);
@override @override
Future<void> close() { Future<void> close() {
_subscription?.cancel(); labelRepository.unsubscribe(this);
return super.close(); return super.close();
} }
@override
Map<int, Correspondent> get correspondents => state.correspondents;
@override
Map<int, DocumentType> get documentTypes => state.documentTypes;
@override
Map<int, StoragePath> get storagePaths => state.storagePaths;
@override
Map<int, Tag> get tags => state.tags;
} }
@@ -0,0 +1,237 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'edit_label_cubit.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
mixin _$EditLabelState {
Map<int, Correspondent> get correspondents =>
throw _privateConstructorUsedError;
Map<int, DocumentType> get documentTypes =>
throw _privateConstructorUsedError;
Map<int, Tag> get tags => throw _privateConstructorUsedError;
Map<int, StoragePath> get storagePaths => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$EditLabelStateCopyWith<EditLabelState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $EditLabelStateCopyWith<$Res> {
factory $EditLabelStateCopyWith(
EditLabelState value, $Res Function(EditLabelState) then) =
_$EditLabelStateCopyWithImpl<$Res, EditLabelState>;
@useResult
$Res call(
{Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, Tag> tags,
Map<int, StoragePath> storagePaths});
}
/// @nodoc
class _$EditLabelStateCopyWithImpl<$Res, $Val extends EditLabelState>
implements $EditLabelStateCopyWith<$Res> {
_$EditLabelStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? correspondents = null,
Object? documentTypes = null,
Object? tags = null,
Object? storagePaths = null,
}) {
return _then(_value.copyWith(
correspondents: null == correspondents
? _value.correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value.documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
tags: null == tags
? _value.tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
storagePaths: null == storagePaths
? _value.storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
) as $Val);
}
}
/// @nodoc
abstract class _$$_EditLabelStateCopyWith<$Res>
implements $EditLabelStateCopyWith<$Res> {
factory _$$_EditLabelStateCopyWith(
_$_EditLabelState value, $Res Function(_$_EditLabelState) then) =
__$$_EditLabelStateCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, Tag> tags,
Map<int, StoragePath> storagePaths});
}
/// @nodoc
class __$$_EditLabelStateCopyWithImpl<$Res>
extends _$EditLabelStateCopyWithImpl<$Res, _$_EditLabelState>
implements _$$_EditLabelStateCopyWith<$Res> {
__$$_EditLabelStateCopyWithImpl(
_$_EditLabelState _value, $Res Function(_$_EditLabelState) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? correspondents = null,
Object? documentTypes = null,
Object? tags = null,
Object? storagePaths = null,
}) {
return _then(_$_EditLabelState(
correspondents: null == correspondents
? _value._correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value._documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
tags: null == tags
? _value._tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
storagePaths: null == storagePaths
? _value._storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
));
}
}
/// @nodoc
class _$_EditLabelState implements _EditLabelState {
const _$_EditLabelState(
{final Map<int, Correspondent> correspondents = const {},
final Map<int, DocumentType> documentTypes = const {},
final Map<int, Tag> tags = const {},
final Map<int, StoragePath> storagePaths = const {}})
: _correspondents = correspondents,
_documentTypes = documentTypes,
_tags = tags,
_storagePaths = storagePaths;
final Map<int, Correspondent> _correspondents;
@override
@JsonKey()
Map<int, Correspondent> get correspondents {
if (_correspondents is EqualUnmodifiableMapView) return _correspondents;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_correspondents);
}
final Map<int, DocumentType> _documentTypes;
@override
@JsonKey()
Map<int, DocumentType> get documentTypes {
if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_documentTypes);
}
final Map<int, Tag> _tags;
@override
@JsonKey()
Map<int, Tag> get tags {
if (_tags is EqualUnmodifiableMapView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_tags);
}
final Map<int, StoragePath> _storagePaths;
@override
@JsonKey()
Map<int, StoragePath> get storagePaths {
if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_storagePaths);
}
@override
String toString() {
return 'EditLabelState(correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_EditLabelState &&
const DeepCollectionEquality()
.equals(other._correspondents, _correspondents) &&
const DeepCollectionEquality()
.equals(other._documentTypes, _documentTypes) &&
const DeepCollectionEquality().equals(other._tags, _tags) &&
const DeepCollectionEquality()
.equals(other._storagePaths, _storagePaths));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_correspondents),
const DeepCollectionEquality().hash(_documentTypes),
const DeepCollectionEquality().hash(_tags),
const DeepCollectionEquality().hash(_storagePaths));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_EditLabelStateCopyWith<_$_EditLabelState> get copyWith =>
__$$_EditLabelStateCopyWithImpl<_$_EditLabelState>(this, _$identity);
}
abstract class _EditLabelState implements EditLabelState {
const factory _EditLabelState(
{final Map<int, Correspondent> correspondents,
final Map<int, DocumentType> documentTypes,
final Map<int, Tag> tags,
final Map<int, StoragePath> storagePaths}) = _$_EditLabelState;
@override
Map<int, Correspondent> get correspondents;
@override
Map<int, DocumentType> get documentTypes;
@override
Map<int, Tag> get tags;
@override
Map<int, StoragePath> get storagePaths;
@override
@JsonKey(ignore: true)
_$$_EditLabelStateCopyWith<_$_EditLabelState> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1,10 +1,11 @@
part of 'edit_label_cubit.dart'; part of 'edit_label_cubit.dart';
class EditLabelState<T> extends Equatable { @freezed
final Map<int, T> labels; class EditLabelState with _$EditLabelState {
const factory EditLabelState({
const EditLabelState({this.labels = const {}}); @Default({}) Map<int, Correspondent> correspondents,
@Default({}) Map<int, DocumentType> documentTypes,
@override @Default({}) Map<int, Tag> tags,
List<Object> get props => [labels]; @Default({}) Map<int, StoragePath> storagePaths,
}) = _EditLabelState;
} }
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/label_form.dart'; import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
@@ -12,6 +11,7 @@ class AddLabelPage<T extends Label> extends StatelessWidget {
final Widget pageTitle; final Widget pageTitle;
final T Function(Map<String, dynamic> json) fromJsonT; final T Function(Map<String, dynamic> json) fromJsonT;
final List<Widget> additionalFields; final List<Widget> additionalFields;
final Future<T> Function(BuildContext context, T label) onSubmit;
const AddLabelPage({ const AddLabelPage({
super.key, super.key,
@@ -19,19 +19,21 @@ class AddLabelPage<T extends Label> extends StatelessWidget {
required this.pageTitle, required this.pageTitle,
required this.fromJsonT, required this.fromJsonT,
this.additionalFields = const [], this.additionalFields = const [],
required this.onSubmit,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => EditLabelCubit( create: (context) => EditLabelCubit(
context.read<LabelRepository<T>>(), context.read<LabelRepository>(),
), ),
child: AddLabelFormWidget( child: AddLabelFormWidget(
pageTitle: pageTitle, pageTitle: pageTitle,
label: initialName != null ? fromJsonT({'name': initialName}) : null, label: initialName != null ? fromJsonT({'name': initialName}) : null,
additionalFields: additionalFields, additionalFields: additionalFields,
fromJsonT: fromJsonT, fromJsonT: fromJsonT,
onSubmit: onSubmit,
), ),
); );
} }
@@ -41,6 +43,7 @@ class AddLabelFormWidget<T extends Label> extends StatelessWidget {
final T? label; final T? label;
final T Function(Map<String, dynamic> json) fromJsonT; final T Function(Map<String, dynamic> json) fromJsonT;
final List<Widget> additionalFields; final List<Widget> additionalFields;
final Future<T> Function(BuildContext context, T label) onSubmit;
final Widget pageTitle; final Widget pageTitle;
const AddLabelFormWidget({ const AddLabelFormWidget({
@@ -49,6 +52,7 @@ class AddLabelFormWidget<T extends Label> extends StatelessWidget {
required this.fromJsonT, required this.fromJsonT,
required this.additionalFields, required this.additionalFields,
required this.pageTitle, required this.pageTitle,
required this.onSubmit,
}); });
@override @override
@@ -63,7 +67,7 @@ class AddLabelFormWidget<T extends Label> extends StatelessWidget {
submitButtonConfig: SubmitButtonConfig<T>( submitButtonConfig: SubmitButtonConfig<T>(
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: Text(S.of(context)!.create), label: Text(S.of(context)!.create),
onSubmit: context.read<EditLabelCubit<T>>().create, onSubmit: (label) => onSubmit(context, label),
), ),
additionalFields: additionalFields, additionalFields: additionalFields,
), ),
@@ -1,40 +1,43 @@
import 'dart:developer'; import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/indexed_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/label_form.dart'; import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/constants.dart';
class EditLabelPage<T extends Label> extends StatelessWidget { class EditLabelPage<T extends Label> extends StatelessWidget {
final T label; final T label;
final T Function(Map<String, dynamic> json) fromJsonT; final T Function(Map<String, dynamic> json) fromJsonT;
final List<Widget> additionalFields; final List<Widget> additionalFields;
final Future<T> Function(BuildContext context, T label) onSubmit;
final Future<void> Function(BuildContext context, T label) onDelete;
const EditLabelPage({ const EditLabelPage({
super.key, super.key,
required this.label, required this.label,
required this.fromJsonT, required this.fromJsonT,
this.additionalFields = const [], this.additionalFields = const [],
required this.onSubmit,
required this.onDelete,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => EditLabelCubit( create: (context) => EditLabelCubit(
context.read<LabelRepository<T>>(), context.read<LabelRepository>(),
), ),
child: EditLabelForm( child: EditLabelForm(
label: label, label: label,
additionalFields: additionalFields, additionalFields: additionalFields,
fromJsonT: fromJsonT, fromJsonT: fromJsonT,
onSubmit: onSubmit,
onDelete: onDelete,
), ),
); );
} }
@@ -44,12 +47,16 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
final T label; final T label;
final T Function(Map<String, dynamic> json) fromJsonT; final T Function(Map<String, dynamic> json) fromJsonT;
final List<Widget> additionalFields; final List<Widget> additionalFields;
final Future<T> Function(BuildContext context, T label) onSubmit;
final Future<void> Function(BuildContext context, T label) onDelete;
const EditLabelForm({ const EditLabelForm({
super.key, super.key,
required this.label, required this.label,
required this.fromJsonT, required this.fromJsonT,
required this.additionalFields, required this.additionalFields,
required this.onSubmit,
required this.onDelete,
}); });
@override @override
@@ -70,7 +77,7 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
submitButtonConfig: SubmitButtonConfig<T>( submitButtonConfig: SubmitButtonConfig<T>(
icon: const Icon(Icons.save), icon: const Icon(Icons.save),
label: Text(S.of(context)!.saveChanges), label: Text(S.of(context)!.saveChanges),
onSubmit: context.read<EditLabelCubit<T>>().update, onSubmit: (label) => onSubmit(context, label),
), ),
additionalFields: additionalFields, additionalFields: additionalFields,
), ),
@@ -107,7 +114,7 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
false; false;
if (shouldDelete) { if (shouldDelete) {
try { try {
context.read<EditLabelCubit<T>>().delete(label); onDelete(context, label);
} on PaperlessServerException catch (error) { } on PaperlessServerException catch (error) {
showErrorMessage(context, error); showErrorMessage(context, error);
} catch (error, stackTrace) { } catch (error, stackTrace) {
@@ -116,7 +123,7 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
Navigator.pop(context); Navigator.pop(context);
} }
} else { } else {
context.read<EditLabelCubit<T>>().delete(label); onDelete(context, label);
Navigator.pop(context); Navigator.pop(context);
} }
} }
@@ -1,8 +1,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart'; import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
@@ -14,13 +12,15 @@ class AddCorrespondentPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => EditLabelCubit<Correspondent>( create: (context) => EditLabelCubit(
context.read<LabelRepository<Correspondent>>(), context.read(),
), ),
child: AddLabelPage<Correspondent>( child: AddLabelPage<Correspondent>(
pageTitle: Text(S.of(context)!.addCorrespondent), pageTitle: Text(S.of(context)!.addCorrespondent),
fromJsonT: Correspondent.fromJson, fromJsonT: Correspondent.fromJson,
initialName: initialName, initialName: initialName,
onSubmit: (context, label) =>
context.read<EditLabelCubit>().addCorrespondent(label),
), ),
); );
} }
@@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart'; import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
@@ -17,13 +15,15 @@ class AddDocumentTypePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => EditLabelCubit<DocumentType>( create: (context) => EditLabelCubit(
context.read<LabelRepository<DocumentType>>(), context.read(),
), ),
child: AddLabelPage<DocumentType>( child: AddLabelPage<DocumentType>(
pageTitle: Text(S.of(context)!.addDocumentType), pageTitle: Text(S.of(context)!.addDocumentType),
fromJsonT: DocumentType.fromJson, fromJsonT: DocumentType.fromJson,
initialName: initialName, initialName: initialName,
onSubmit: (context, label) =>
context.read<EditLabelCubit>().addDocumentType(label),
), ),
); );
} }
@@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart'; import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart'; import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
@@ -15,13 +13,15 @@ class AddStoragePathPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => EditLabelCubit<StoragePath>( create: (context) => EditLabelCubit(
context.read<LabelRepository<StoragePath>>(), context.read(),
), ),
child: AddLabelPage<StoragePath>( child: AddLabelPage<StoragePath>(
pageTitle: Text(S.of(context)!.addStoragePath), pageTitle: Text(S.of(context)!.addStoragePath),
fromJsonT: StoragePath.fromJson, fromJsonT: StoragePath.fromJson,
initialName: initalName, initialName: initalName,
onSubmit: (context, label) =>
context.read<EditLabelCubit>().addStoragePath(label),
additionalFields: const [ additionalFields: const [
StoragePathAutofillFormBuilderField(name: StoragePath.pathKey), StoragePathAutofillFormBuilderField(name: StoragePath.pathKey),
SizedBox(height: 120.0), SizedBox(height: 120.0),
@@ -4,8 +4,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart'; import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart'; import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
@@ -18,13 +16,15 @@ class AddTagPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => EditLabelCubit<Tag>( create: (context) => EditLabelCubit(
context.read<LabelRepository<Tag>>(), context.read(),
), ),
child: AddLabelPage<Tag>( child: AddLabelPage<Tag>(
pageTitle: Text(S.of(context)!.addTag), pageTitle: Text(S.of(context)!.addTag),
fromJsonT: Tag.fromJson, fromJsonT: Tag.fromJson,
initialName: initialValue, initialName: initialValue,
onSubmit: (context, label) =>
context.read<EditLabelCubit>().addTag(label),
additionalFields: [ additionalFields: [
FormBuilderColorPickerField( FormBuilderColorPickerField(
name: Tag.colorKey, name: Tag.colorKey,
@@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart'; import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
@@ -13,12 +11,16 @@ class EditCorrespondentPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => EditLabelCubit<Correspondent>( create: (context) => EditLabelCubit(
context.read<LabelRepository<Correspondent>>(), context.read(),
), ),
child: EditLabelPage<Correspondent>( child: EditLabelPage<Correspondent>(
label: correspondent, label: correspondent,
fromJsonT: Correspondent.fromJson, fromJsonT: Correspondent.fromJson,
onSubmit: (context, label) =>
context.read<EditLabelCubit>().addCorrespondent(label),
onDelete: (context, label) =>
context.read<EditLabelCubit>().removeCorrespondent(label),
), ),
); );
} }
@@ -2,7 +2,6 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart'; import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
@@ -13,12 +12,16 @@ class EditDocumentTypePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => EditLabelCubit<DocumentType>( create: (context) => EditLabelCubit(
context.read<LabelRepository<DocumentType>>(), context.read(),
), ),
child: EditLabelPage<DocumentType>( child: EditLabelPage<DocumentType>(
label: documentType, label: documentType,
fromJsonT: DocumentType.fromJson, fromJsonT: DocumentType.fromJson,
onSubmit: (context, label) =>
context.read<EditLabelCubit>().addDocumentType(label),
onDelete: (context, label) =>
context.read<EditLabelCubit>().removeDocumentType(label),
), ),
); );
} }
@@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart'; import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart'; import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
@@ -14,12 +12,16 @@ class EditStoragePathPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => EditLabelCubit<StoragePath>( create: (context) => EditLabelCubit(
context.read<LabelRepository<StoragePath>>(), context.read(),
), ),
child: EditLabelPage<StoragePath>( child: EditLabelPage<StoragePath>(
label: storagePath, label: storagePath,
fromJsonT: StoragePath.fromJson, fromJsonT: StoragePath.fromJson,
onSubmit: (context, label) =>
context.read<EditLabelCubit>().addStoragePath(label),
onDelete: (context, label) =>
context.read<EditLabelCubit>().removeStoragePath(label),
additionalFields: [ additionalFields: [
StoragePathAutofillFormBuilderField( StoragePathAutofillFormBuilderField(
name: StoragePath.pathKey, name: StoragePath.pathKey,
@@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart'; import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart'; import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart'; import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
@@ -17,12 +15,16 @@ class EditTagPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => EditLabelCubit<Tag>( create: (context) => EditLabelCubit(
context.read<LabelRepository<Tag>>(), context.read(),
), ),
child: EditLabelPage<Tag>( child: EditLabelPage<Tag>(
label: tag, label: tag,
fromJsonT: Tag.fromJson, fromJsonT: Tag.fromJson,
onSubmit: (context, label) =>
context.read<EditLabelCubit>().addTag(label),
onDelete: (context, label) =>
context.read<EditLabelCubit>().removeTag(label),
additionalFields: [ additionalFields: [
FormBuilderColorPickerField( FormBuilderColorPickerField(
initialValue: tag.color, initialValue: tag.color,
+9 -24
View File
@@ -31,9 +31,7 @@ import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart'; import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/file_helpers.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:path/path.dart' as p;
import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:responsive_builder/responsive_builder.dart'; import 'package:responsive_builder/responsive_builder.dart';
@@ -60,8 +58,6 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
context.read(), context.read(),
context.read(), context.read(),
context.read(), context.read(),
context.read(),
context.read(),
); );
_listenToInboxChanges(); _listenToInboxChanges();
@@ -260,17 +256,8 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
MultiBlocProvider( MultiBlocProvider(
providers: [ providers: [
BlocProvider( BlocProvider(
create: (context) => LabelCubit<Correspondent>(context.read()), create: (context) => LabelCubit(context.read()),
), )
BlocProvider(
create: (context) => LabelCubit<DocumentType>(context.read()),
),
BlocProvider(
create: (context) => LabelCubit<Tag>(context.read()),
),
BlocProvider(
create: (context) => LabelCubit<StoragePath>(context.read()),
),
], ],
child: const LabelsPage(), child: const LabelsPage(),
), ),
@@ -345,15 +332,13 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
} }
void _initializeData(BuildContext context) { void _initializeData(BuildContext context) {
try { Future.wait([
context.read<LabelRepository<Tag>>().findAll(); context.read<LabelRepository>().initialize(),
context.read<LabelRepository<Correspondent>>().findAll(); context.read<SavedViewRepository>().findAll(),
context.read<LabelRepository<DocumentType>>().findAll(); context.read<PaperlessServerInformationCubit>().updateInformtion(),
context.read<LabelRepository<StoragePath>>().findAll(); ]).onError<PaperlessServerException>((error, stackTrace) {
context.read<SavedViewRepository>().findAll();
context.read<PaperlessServerInformationCubit>().updateInformtion();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} throw error;
});
} }
} }
@@ -65,10 +65,7 @@ class VerifyIdentityPage extends StatelessWidget {
void _logout(BuildContext context) { void _logout(BuildContext context) {
context.read<AuthenticationCubit>().logout(); context.read<AuthenticationCubit>().logout();
context.read<LabelRepository<Tag>>().clear(); context.read<LabelRepository>().clear();
context.read<LabelRepository<Correspondent>>().clear();
context.read<LabelRepository<DocumentType>>().clear();
context.read<LabelRepository<StoragePath>>().clear();
context.read<SavedViewRepository>().clear(); context.read<SavedViewRepository>().clear();
HydratedBloc.storage.clear(); HydratedBloc.storage.clear();
} }
+12 -44
View File
@@ -5,6 +5,7 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/label_repository_state.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
@@ -13,9 +14,7 @@ part 'inbox_state.dart';
class InboxCubit extends HydratedCubit<InboxState> class InboxCubit extends HydratedCubit<InboxState>
with DocumentPagingBlocMixin { with DocumentPagingBlocMixin {
final LabelRepository<Tag> _tagsRepository; final LabelRepository _labelRepository;
final LabelRepository<Correspondent> _correspondentRepository;
final LabelRepository<DocumentType> _documentTypeRepository;
final PaperlessDocumentsApi _documentsApi; final PaperlessDocumentsApi _documentsApi;
@@ -24,27 +23,15 @@ class InboxCubit extends HydratedCubit<InboxState>
final PaperlessServerStatsApi _statsApi; final PaperlessServerStatsApi _statsApi;
final List<StreamSubscription> _subscriptions = [];
@override @override
PaperlessDocumentsApi get api => _documentsApi; PaperlessDocumentsApi get api => _documentsApi;
InboxCubit( InboxCubit(
this._tagsRepository,
this._documentsApi, this._documentsApi,
this._correspondentRepository,
this._documentTypeRepository,
this._statsApi, this._statsApi,
this._labelRepository,
this.notifier, this.notifier,
) : super( ) : super(InboxState(labels: _labelRepository.state)) {
InboxState(
availableCorrespondents:
_correspondentRepository.current?.values ?? {},
availableDocumentTypes:
_documentTypeRepository.current?.values ?? {},
availableTags: _tagsRepository.current?.values ?? {},
),
) {
notifier.subscribe( notifier.subscribe(
this, this,
onDeleted: remove, onDeleted: remove,
@@ -60,28 +47,11 @@ class InboxCubit extends HydratedCubit<InboxState>
} }
}, },
); );
_subscriptions.add( _labelRepository.subscribe(
_tagsRepository.values.listen((event) { this,
if (event?.hasLoaded ?? false) { onChanged: (labels) {
emit(state.copyWith(availableTags: event!.values)); emit(state.copyWith(labels: labels));
} },
}),
);
_subscriptions.add(
_correspondentRepository.values.listen((event) {
if (event?.hasLoaded ?? false) {
emit(state.copyWith(
availableCorrespondents: event!.values,
));
}
}),
);
_subscriptions.add(
_documentTypeRepository.values.listen((event) {
if (event?.hasLoaded ?? false) {
emit(state.copyWith(availableDocumentTypes: event!.values));
}
}),
); );
refreshItemsInInboxCount(false); refreshItemsInInboxCount(false);
@@ -105,7 +75,7 @@ class InboxCubit extends HydratedCubit<InboxState>
/// Fetches inbox tag ids and loads the inbox items (documents). /// Fetches inbox tag ids and loads the inbox items (documents).
/// ///
Future<void> loadInbox() async { Future<void> loadInbox() async {
final inboxTags = await _tagsRepository.findAll().then( final inboxTags = await _labelRepository.findAllTags().then(
(tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!), (tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!),
); );
@@ -133,7 +103,7 @@ class InboxCubit extends HydratedCubit<InboxState>
/// ///
Future<void> reloadInbox() async { Future<void> reloadInbox() async {
emit(state.copyWith(hasLoaded: false, isLoading: true)); emit(state.copyWith(hasLoaded: false, isLoading: true));
final inboxTags = await _tagsRepository.findAll().then( final inboxTags = await _labelRepository.findAllTags().then(
(tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!), (tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!),
); );
@@ -239,9 +209,7 @@ class InboxCubit extends HydratedCubit<InboxState>
@override @override
Future<void> close() { Future<void> close() {
for (var sub in _subscriptions) { _labelRepository.unsubscribe(this);
sub.cancel();
}
return super.close(); return super.close();
} }
} }
+5 -19
View File
@@ -4,11 +4,7 @@ part of 'inbox_cubit.dart';
class InboxState extends DocumentPagingState { class InboxState extends DocumentPagingState {
final Iterable<int> inboxTags; final Iterable<int> inboxTags;
final Map<int, Tag> availableTags; final LabelRepositoryState labels;
final Map<int, DocumentType> availableDocumentTypes;
final Map<int, Correspondent> availableCorrespondents;
final int itemsInInboxCount; final int itemsInInboxCount;
@@ -22,10 +18,8 @@ class InboxState extends DocumentPagingState {
super.filter = const DocumentFilter(), super.filter = const DocumentFilter(),
this.inboxTags = const [], this.inboxTags = const [],
this.isHintAcknowledged = false, this.isHintAcknowledged = false,
this.availableTags = const {},
this.availableDocumentTypes = const {},
this.availableCorrespondents = const {},
this.itemsInInboxCount = 0, this.itemsInInboxCount = 0,
this.labels = const LabelRepositoryState(),
}); });
@override @override
@@ -37,10 +31,8 @@ class InboxState extends DocumentPagingState {
inboxTags, inboxTags,
documents, documents,
isHintAcknowledged, isHintAcknowledged,
availableTags,
availableDocumentTypes,
availableCorrespondents,
itemsInInboxCount, itemsInInboxCount,
labels,
]; ];
InboxState copyWith({ InboxState copyWith({
@@ -50,9 +42,7 @@ class InboxState extends DocumentPagingState {
List<PagedSearchResult<DocumentModel>>? value, List<PagedSearchResult<DocumentModel>>? value,
DocumentFilter? filter, DocumentFilter? filter,
bool? isHintAcknowledged, bool? isHintAcknowledged,
Map<int, Tag>? availableTags, LabelRepositoryState? labels,
Map<int, Correspondent>? availableCorrespondents,
Map<int, DocumentType>? availableDocumentTypes,
Map<int, FieldSuggestions>? suggestions, Map<int, FieldSuggestions>? suggestions,
int? itemsInInboxCount, int? itemsInInboxCount,
}) { }) {
@@ -62,11 +52,7 @@ class InboxState extends DocumentPagingState {
value: value ?? super.value, value: value ?? super.value,
inboxTags: inboxTags ?? this.inboxTags, inboxTags: inboxTags ?? this.inboxTags,
isHintAcknowledged: isHintAcknowledged ?? this.isHintAcknowledged, isHintAcknowledged: isHintAcknowledged ?? this.isHintAcknowledged,
availableCorrespondents: labels: labels ?? this.labels,
availableCorrespondents ?? this.availableCorrespondents,
availableDocumentTypes:
availableDocumentTypes ?? this.availableDocumentTypes,
availableTags: availableTags ?? this.availableTags,
filter: filter ?? super.filter, filter: filter ?? super.filter,
itemsInInboxCount: itemsInInboxCount ?? this.itemsInInboxCount, itemsInInboxCount: itemsInInboxCount ?? this.itemsInInboxCount,
); );
+89 -89
View File
@@ -32,60 +32,98 @@ class _InboxItemState extends State<InboxItem> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return BlocBuilder<InboxCubit, InboxState>(
behavior: HitTestBehavior.translucent, builder: (context, state) {
onTap: () async { return GestureDetector(
Navigator.pushNamed( behavior: HitTestBehavior.translucent,
context, onTap: () async {
DocumentDetailsRoute.routeName, Navigator.pushNamed(
arguments: DocumentDetailsRouteArguments( context,
document: widget.document, DocumentDetailsRoute.routeName,
isLabelClickable: false, arguments: DocumentDetailsRouteArguments(
document: widget.document,
isLabelClickable: false,
),
);
},
child: SizedBox(
height: 200,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: Row(
children: [
AspectRatio(
aspectRatio: InboxItem.a4AspectRatio,
child: DocumentPreview(
document: widget.document,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
enableHero: false,
),
).padded(),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTitle().paddedOnly(left: 8, right: 8, top: 8),
const Spacer(),
_buildTextWithLeadingIcon(
Icon(
Icons.person_outline,
size: Theme.of(context)
.textTheme
.bodyMedium
?.fontSize,
),
LabelText<Correspondent>(
label: state.labels.correspondents[
widget.document.correspondent],
style: Theme.of(context).textTheme.bodyMedium,
placeholder: "-",
),
).paddedSymmetrically(horizontal: 8),
_buildTextWithLeadingIcon(
Icon(
Icons.description_outlined,
size: Theme.of(context)
.textTheme
.bodyMedium
?.fontSize,
),
LabelText<DocumentType>(
label: state.labels.documentTypes[
widget.document.documentType],
style: Theme.of(context).textTheme.bodyMedium,
placeholder: "-",
),
).paddedSymmetrically(horizontal: 8),
const Spacer(),
TagsWidget(
tags: widget.document.tags
.map((e) => state.labels.tags[e]!)
.toList(),
isMultiLine: false,
isClickable: false,
showShortNames: true,
dense: true,
).paddedOnly(left: 8, bottom: 8),
],
),
),
],
),
),
SizedBox(
height: 56,
child: _buildActions(context),
),
],
).paddedOnly(left: 8, top: 8, bottom: 8),
), ),
); );
}, },
child: SizedBox(
height: 200,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: Row(
children: [
AspectRatio(
aspectRatio: InboxItem.a4AspectRatio,
child: DocumentPreview(
document: widget.document,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
enableHero: false,
),
).padded(),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTitle().paddedOnly(left: 8, right: 8, top: 8),
const Spacer(),
_buildCorrespondent(context)
.paddedSymmetrically(horizontal: 8),
_buildDocumentType(context)
.paddedSymmetrically(horizontal: 8),
const Spacer(),
_buildTags().paddedOnly(left: 8, bottom: 8),
],
),
),
],
),
),
SizedBox(
height: 56,
child: _buildActions(context),
),
],
).paddedOnly(left: 8, top: 8, bottom: 8),
),
); );
} }
@@ -211,44 +249,6 @@ class _InboxItemState extends State<InboxItem> {
); );
} }
TagsWidget _buildTags() {
return TagsWidget(
tagIds: widget.document.tags,
isMultiLine: false,
isClickable: false,
showShortNames: true,
dense: true,
);
}
Row _buildDocumentType(BuildContext context) {
return _buildTextWithLeadingIcon(
Icon(
Icons.description_outlined,
size: Theme.of(context).textTheme.bodyMedium?.fontSize,
),
LabelText<DocumentType>(
id: widget.document.documentType,
style: Theme.of(context).textTheme.bodyMedium,
placeholder: "-",
),
);
}
Row _buildCorrespondent(BuildContext context) {
return _buildTextWithLeadingIcon(
Icon(
Icons.person_outline,
size: Theme.of(context).textTheme.bodyMedium?.fontSize,
),
LabelText<Correspondent>(
id: widget.document.correspondent,
style: Theme.of(context).textTheme.bodyMedium,
placeholder: "-",
),
);
}
Text _buildTitle() { Text _buildTitle() {
return Text( return Text(
widget.document.title, widget.document.title,
@@ -1,11 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/correspondent_bloc_provider.dart';
class CorrespondentWidget extends StatelessWidget { class CorrespondentWidget extends StatelessWidget {
final int? correspondentId; final Correspondent? correspondent;
final void Function(int? id)? onSelected; final void Function(int? id)? onSelected;
final Color? textColor; final Color? textColor;
final bool isClickable; final bool isClickable;
@@ -13,7 +10,7 @@ class CorrespondentWidget extends StatelessWidget {
const CorrespondentWidget({ const CorrespondentWidget({
Key? key, Key? key,
required this.correspondentId, required this.correspondent,
this.textColor, this.textColor,
this.isClickable = true, this.isClickable = true,
this.textStyle, this.textStyle,
@@ -22,25 +19,18 @@ class CorrespondentWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CorrespondentBlocProvider( return AbsorbPointer(
child: AbsorbPointer( absorbing: !isClickable,
absorbing: !isClickable, child: GestureDetector(
child: onTap: () => onSelected?.call(correspondent?.id),
BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>( child: Text(
builder: (context, state) { correspondent?.name ?? "-",
return GestureDetector( maxLines: 1,
onTap: () => onSelected?.call(correspondentId!), overflow: TextOverflow.ellipsis,
child: Text( style:
(state.getLabel(correspondentId)?.name) ?? "-", (textStyle ?? Theme.of(context).textTheme.bodyMedium)?.copyWith(
maxLines: 1, color: textColor ?? Theme.of(context).colorScheme.primary,
overflow: TextOverflow.ellipsis, ),
style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
?.copyWith(
color: textColor ?? Theme.of(context).colorScheme.primary,
),
),
);
},
), ),
), ),
); );
+31 -54
View File
@@ -1,68 +1,45 @@
import 'dart:async'; import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart';
part 'label_state.dart'; part 'label_state.dart';
part 'label_cubit.freezed.dart';
class LabelCubit<T extends Label> extends Cubit<LabelState<T>> { class LabelCubit extends Cubit<LabelState> with LabelCubitMixin<LabelState> {
final LabelRepository<T> _repository; @override
final LabelRepository labelRepository;
late StreamSubscription _subscription; LabelCubit(this.labelRepository) : super(const LabelState()) {
labelRepository.subscribe(
LabelCubit(LabelRepository<T> repository) this,
: _repository = repository, onChanged: (labels) {
super(LabelState( emit(state.copyWith(
isLoaded: repository.isInitialized, correspondents: labels.correspondents,
labels: repository.current?.values ?? {}, documentTypes: labels.documentTypes,
)) { storagePaths: labels.storagePaths,
_subscription = _repository.values.listen( tags: labels.tags,
(event) { ));
if (event == null) {
emit(LabelState());
}
emit(
LabelState(isLoaded: event!.hasLoaded, labels: event.values ?? {}));
}, },
); );
} }
/// @override
/// Adds [item] to the current state. A new state is automatically pushed Future<void> close() {
/// due to the subscription to the repository, which updates the state on labelRepository.unsubscribe(this);
/// operation. return super.close();
///
Future<T> add(T item) async {
assert(item.id == null);
final addedItem = await _repository.create(item);
return addedItem;
}
Future<void> reload() {
return _repository.findAll();
}
Future<T> replace(T item) async {
assert(item.id != null);
final updatedItem = await _repository.update(item);
return updatedItem;
}
Future<void> remove(T item) async {
assert(item.id != null);
if (state.labels.containsKey(item.id)) {
await _repository.delete(item);
}
}
void reset() {
emit(LabelState(isLoaded: false, labels: {}));
} }
@override @override
Future<void> close() { Map<int, Correspondent> get correspondents => state.correspondents;
_subscription.cancel();
return super.close(); @override
} Map<int, DocumentType> get documentTypes => state.documentTypes;
@override
Map<int, StoragePath> get storagePaths => state.storagePaths;
@override
Map<int, Tag> get tags => state.tags;
} }
@@ -0,0 +1,237 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'label_cubit.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
mixin _$LabelState {
Map<int, Correspondent> get correspondents =>
throw _privateConstructorUsedError;
Map<int, DocumentType> get documentTypes =>
throw _privateConstructorUsedError;
Map<int, Tag> get tags => throw _privateConstructorUsedError;
Map<int, StoragePath> get storagePaths => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$LabelStateCopyWith<LabelState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $LabelStateCopyWith<$Res> {
factory $LabelStateCopyWith(
LabelState value, $Res Function(LabelState) then) =
_$LabelStateCopyWithImpl<$Res, LabelState>;
@useResult
$Res call(
{Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, Tag> tags,
Map<int, StoragePath> storagePaths});
}
/// @nodoc
class _$LabelStateCopyWithImpl<$Res, $Val extends LabelState>
implements $LabelStateCopyWith<$Res> {
_$LabelStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? correspondents = null,
Object? documentTypes = null,
Object? tags = null,
Object? storagePaths = null,
}) {
return _then(_value.copyWith(
correspondents: null == correspondents
? _value.correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value.documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
tags: null == tags
? _value.tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
storagePaths: null == storagePaths
? _value.storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
) as $Val);
}
}
/// @nodoc
abstract class _$$_LabelStateCopyWith<$Res>
implements $LabelStateCopyWith<$Res> {
factory _$$_LabelStateCopyWith(
_$_LabelState value, $Res Function(_$_LabelState) then) =
__$$_LabelStateCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{Map<int, Correspondent> correspondents,
Map<int, DocumentType> documentTypes,
Map<int, Tag> tags,
Map<int, StoragePath> storagePaths});
}
/// @nodoc
class __$$_LabelStateCopyWithImpl<$Res>
extends _$LabelStateCopyWithImpl<$Res, _$_LabelState>
implements _$$_LabelStateCopyWith<$Res> {
__$$_LabelStateCopyWithImpl(
_$_LabelState _value, $Res Function(_$_LabelState) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? correspondents = null,
Object? documentTypes = null,
Object? tags = null,
Object? storagePaths = null,
}) {
return _then(_$_LabelState(
correspondents: null == correspondents
? _value._correspondents
: correspondents // ignore: cast_nullable_to_non_nullable
as Map<int, Correspondent>,
documentTypes: null == documentTypes
? _value._documentTypes
: documentTypes // ignore: cast_nullable_to_non_nullable
as Map<int, DocumentType>,
tags: null == tags
? _value._tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<int, Tag>,
storagePaths: null == storagePaths
? _value._storagePaths
: storagePaths // ignore: cast_nullable_to_non_nullable
as Map<int, StoragePath>,
));
}
}
/// @nodoc
class _$_LabelState implements _LabelState {
const _$_LabelState(
{final Map<int, Correspondent> correspondents = const {},
final Map<int, DocumentType> documentTypes = const {},
final Map<int, Tag> tags = const {},
final Map<int, StoragePath> storagePaths = const {}})
: _correspondents = correspondents,
_documentTypes = documentTypes,
_tags = tags,
_storagePaths = storagePaths;
final Map<int, Correspondent> _correspondents;
@override
@JsonKey()
Map<int, Correspondent> get correspondents {
if (_correspondents is EqualUnmodifiableMapView) return _correspondents;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_correspondents);
}
final Map<int, DocumentType> _documentTypes;
@override
@JsonKey()
Map<int, DocumentType> get documentTypes {
if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_documentTypes);
}
final Map<int, Tag> _tags;
@override
@JsonKey()
Map<int, Tag> get tags {
if (_tags is EqualUnmodifiableMapView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_tags);
}
final Map<int, StoragePath> _storagePaths;
@override
@JsonKey()
Map<int, StoragePath> get storagePaths {
if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_storagePaths);
}
@override
String toString() {
return 'LabelState(correspondents: $correspondents, documentTypes: $documentTypes, tags: $tags, storagePaths: $storagePaths)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_LabelState &&
const DeepCollectionEquality()
.equals(other._correspondents, _correspondents) &&
const DeepCollectionEquality()
.equals(other._documentTypes, _documentTypes) &&
const DeepCollectionEquality().equals(other._tags, _tags) &&
const DeepCollectionEquality()
.equals(other._storagePaths, _storagePaths));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_correspondents),
const DeepCollectionEquality().hash(_documentTypes),
const DeepCollectionEquality().hash(_tags),
const DeepCollectionEquality().hash(_storagePaths));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$_LabelStateCopyWith<_$_LabelState> get copyWith =>
__$$_LabelStateCopyWithImpl<_$_LabelState>(this, _$identity);
}
abstract class _LabelState implements LabelState {
const factory _LabelState(
{final Map<int, Correspondent> correspondents,
final Map<int, DocumentType> documentTypes,
final Map<int, Tag> tags,
final Map<int, StoragePath> storagePaths}) = _$_LabelState;
@override
Map<int, Correspondent> get correspondents;
@override
Map<int, DocumentType> get documentTypes;
@override
Map<int, Tag> get tags;
@override
Map<int, StoragePath> get storagePaths;
@override
@JsonKey(ignore: true)
_$$_LabelStateCopyWith<_$_LabelState> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -0,0 +1,104 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
mixin LabelCubitMixin<T> on BlocBase<T> {
LabelRepository get labelRepository;
Map<int, Correspondent> get correspondents;
Map<int, DocumentType> get documentTypes;
Map<int, Tag> get tags;
Map<int, StoragePath> get storagePaths;
Future<Correspondent> addCorrespondent(Correspondent item) async {
assert(item.id == null);
final addedItem = await labelRepository.createCorrespondent(item);
return addedItem;
}
Future<void> reloadCorrespondents() {
return labelRepository.findAllCorrespondents();
}
Future<Correspondent> replaceCorrespondent(Correspondent item) async {
assert(item.id != null);
final updatedItem = await labelRepository.updateCorrespondent(item);
return updatedItem;
}
Future<void> removeCorrespondent(Correspondent item) async {
assert(item.id != null);
if (correspondents.containsKey(item.id)) {
await labelRepository.deleteCorrespondent(item);
}
}
Future<DocumentType> addDocumentType(DocumentType item) async {
assert(item.id == null);
final addedItem = await labelRepository.createDocumentType(item);
return addedItem;
}
Future<void> reloadDocumentTypes() {
return labelRepository.findAllDocumentTypes();
}
Future<DocumentType> replaceDocumentType(DocumentType item) async {
assert(item.id != null);
final updatedItem = await labelRepository.updateDocumentType(item);
return updatedItem;
}
Future<void> removeDocumentType(DocumentType item) async {
assert(item.id != null);
if (documentTypes.containsKey(item.id)) {
await labelRepository.deleteDocumentType(item);
}
}
Future<StoragePath> addStoragePath(StoragePath item) async {
assert(item.id == null);
final addedItem = await labelRepository.createStoragePath(item);
return addedItem;
}
Future<void> reloadStoragePaths() {
return labelRepository.findAllStoragePaths();
}
Future<StoragePath> replaceStoragePath(StoragePath item) async {
assert(item.id != null);
final updatedItem = await labelRepository.updateStoragePath(item);
return updatedItem;
}
Future<void> removeStoragePath(StoragePath item) async {
assert(item.id != null);
if (storagePaths.containsKey(item.id)) {
await labelRepository.deleteStoragePath(item);
}
}
Future<Tag> addTag(Tag item) async {
assert(item.id == null);
final addedItem = await labelRepository.createTag(item);
return addedItem;
}
Future<void> reloadTags() {
return labelRepository.findAllTags();
}
Future<Tag> replaceTag(Tag item) async {
assert(item.id != null);
final updatedItem = await labelRepository.updateTag(item);
return updatedItem;
}
Future<void> removeTag(Tag item) async {
assert(item.id != null);
if (tags.containsKey(item.id)) {
await labelRepository.deleteTag(item);
}
}
}
+8 -16
View File
@@ -1,19 +1,11 @@
part of 'label_cubit.dart'; part of 'label_cubit.dart';
class LabelState<T extends Label> { @freezed
LabelState.initial() : this(isLoaded: false, labels: {}); class LabelState with _$LabelState {
final bool isLoaded; const factory LabelState({
final Map<int, T> labels; @Default({}) Map<int, Correspondent> correspondents,
@Default({}) Map<int, DocumentType> documentTypes,
LabelState({ @Default({}) Map<int, Tag> tags,
this.isLoaded = false, @Default({}) Map<int, StoragePath> storagePaths,
this.labels = const {}, }) = _LabelState;
});
T? getLabel(int? key) {
if (isLoaded) {
return labels[key];
}
return null;
}
} }
@@ -1,24 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class CorrespondentBlocProvider extends StatelessWidget {
final Widget child;
const CorrespondentBlocProvider({
super.key,
required this.child,
});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LabelCubit<Correspondent>(
context.read<LabelRepository<Correspondent>>(),
),
child: child,
);
}
}
@@ -1,21 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class DocumentTypeBlocProvider extends StatelessWidget {
final Widget child;
const DocumentTypeBlocProvider({super.key, required this.child});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LabelCubit<DocumentType>(
context.read<LabelRepository<DocumentType>>(),
),
child: child,
);
}
}
@@ -1,43 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class LabelsBlocProvider extends StatelessWidget {
final Widget child;
const LabelsBlocProvider({super.key, required this.child});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<LabelCubit<StoragePath>>(
create: (context) => LabelCubit<StoragePath>(
context.read<LabelRepository<StoragePath>>(),
),
),
BlocProvider<LabelCubit<Correspondent>>(
create: (context) => LabelCubit<Correspondent>(
context.read<LabelRepository<Correspondent>>(),
),
),
BlocProvider<LabelCubit<DocumentType>>(
create: (context) => LabelCubit<DocumentType>(
context.read<LabelRepository<DocumentType>>(),
),
),
BlocProvider<LabelCubit<Tag>>(
create: (context) => LabelCubit<Tag>(
context.read<LabelRepository<Tag>>(),
),
),
],
child: child,
);
}
}
@@ -1,21 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class StoragePathBlocProvider extends StatelessWidget {
final Widget child;
const StoragePathBlocProvider({super.key, required this.child});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LabelCubit<StoragePath>(
context.read<LabelRepository<StoragePath>>(),
),
child: child,
);
}
}
@@ -1,21 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class TagBlocProvider extends StatelessWidget {
final Widget child;
const TagBlocProvider({super.key, required this.child});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LabelCubit<Tag>(
context.read<LabelRepository<Tag>>(),
),
child: child,
);
}
}
@@ -1,17 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/document_type_bloc_provider.dart';
class DocumentTypeWidget extends StatelessWidget { class DocumentTypeWidget extends StatelessWidget {
final int? documentTypeId; final DocumentType? documentType;
final bool isClickable; final bool isClickable;
final TextStyle? textStyle; final TextStyle? textStyle;
final void Function(int? id)? onSelected; final void Function(int? id)? onSelected;
const DocumentTypeWidget({ const DocumentTypeWidget({
Key? key, Key? key,
required this.documentTypeId, required this.documentType,
this.isClickable = true, this.isClickable = true,
this.textStyle, this.textStyle,
this.onSelected, this.onSelected,
@@ -19,23 +16,16 @@ class DocumentTypeWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DocumentTypeBlocProvider( return AbsorbPointer(
child: AbsorbPointer( absorbing: !isClickable,
absorbing: !isClickable, child: GestureDetector(
child: GestureDetector( onTap: () => onSelected?.call(documentType?.id),
onTap: () => onSelected?.call(documentTypeId), child: Text(
child: documentType?.toString() ?? "-",
BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>( style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
builder: (context, state) { ?.copyWith(color: Theme.of(context).colorScheme.tertiary),
return Text( overflow: TextOverflow.ellipsis,
state.labels[documentTypeId]?.toString() ?? "-", maxLines: 1,
style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
?.copyWith(color: Theme.of(context).colorScheme.tertiary),
overflow: TextOverflow.ellipsis,
maxLines: 1,
);
},
),
), ),
), ),
); );
@@ -1,18 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/storage_path_bloc_provider.dart';
class StoragePathWidget extends StatelessWidget { class StoragePathWidget extends StatelessWidget {
final int? pathId; final StoragePath? storagePath;
final Color? textColor; final Color? textColor;
final bool isClickable; final bool isClickable;
final void Function(int? id)? onSelected; final void Function(int? id)? onSelected;
const StoragePathWidget({ const StoragePathWidget({
Key? key, Key? key,
this.pathId, this.storagePath,
this.textColor, this.textColor,
this.isClickable = true, this.isClickable = true,
this.onSelected, this.onSelected,
@@ -20,23 +17,17 @@ class StoragePathWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StoragePathBlocProvider( return AbsorbPointer(
child: AbsorbPointer( absorbing: !isClickable,
absorbing: !isClickable, child: GestureDetector(
child: BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>( onTap: () => onSelected?.call(storagePath?.id),
builder: (context, state) { child: Text(
return GestureDetector( storagePath?.name ?? "-",
onTap: () => onSelected?.call(pathId), maxLines: 1,
child: Text( overflow: TextOverflow.ellipsis,
state.getLabel(pathId)?.name ?? "-", style: Theme.of(context).textTheme.bodyMedium?.copyWith(
maxLines: 1, color: textColor ?? Theme.of(context).colorScheme.primary,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: textColor ?? Theme.of(context).colorScheme.primary,
),
), ),
);
},
), ),
), ),
); );
@@ -6,7 +6,6 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/core/workarounds/colored_chip.dart'; import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart';
@@ -21,6 +20,8 @@ class TagFormField extends StatefulWidget {
final bool excludeAllowed; final bool excludeAllowed;
final Map<int, Tag> selectableOptions; final Map<int, Tag> selectableOptions;
final Widget? suggestions; final Widget? suggestions;
final String? labelText;
final String? hintText;
const TagFormField({ const TagFormField({
super.key, super.key,
@@ -32,6 +33,8 @@ class TagFormField extends StatefulWidget {
this.excludeAllowed = true, this.excludeAllowed = true,
required this.selectableOptions, required this.selectableOptions,
this.suggestions, this.suggestions,
this.labelText,
this.hintText,
}); });
@override @override
@@ -94,8 +97,8 @@ class _TagFormFieldState extends State<TagFormField> {
Icons.label_outline, Icons.label_outline,
), ),
suffixIcon: _buildSuffixIcon(context, field), suffixIcon: _buildSuffixIcon(context, field),
labelText: S.of(context)!.tags, labelText: widget.labelText ?? S.of(context)!.tags,
hintText: S.of(context)!.filterTags, hintText: widget.hintText ?? S.of(context)!.filterTags,
), ),
controller: _textEditingController, controller: _textEditingController,
), ),
@@ -240,8 +243,8 @@ class _TagFormFieldState extends State<TagFormField> {
void _onAddTag(BuildContext context, FormFieldState<TagsQuery> field) async { void _onAddTag(BuildContext context, FormFieldState<TagsQuery> field) async {
final Tag? tag = await Navigator.of(context).push<Tag>( final Tag? tag = await Navigator.of(context).push<Tag>(
MaterialPageRoute( MaterialPageRoute(
builder: (_) => RepositoryProvider( builder: (_) => RepositoryProvider.value(
create: (context) => context.read<LabelRepository<Tag>>(), value: context.read<LabelRepository>(),
child: AddTagPage(initialValue: _textEditingController.text), child: AddTagPage(initialValue: _textEditingController.text),
), ),
), ),
@@ -1,12 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/tag_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart'; import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart';
class TagsWidget extends StatelessWidget { class TagsWidget extends StatelessWidget {
final Iterable<int> tagIds; final List<Tag> tags;
final bool isMultiLine; final bool isMultiLine;
final void Function(int tagId)? onTagSelected; final void Function(int tagId)? onTagSelected;
final bool isClickable; final bool isClickable;
@@ -15,7 +12,7 @@ class TagsWidget extends StatelessWidget {
const TagsWidget({ const TagsWidget({
Key? key, Key? key,
required this.tagIds, required this.tags,
this.isMultiLine = true, this.isMultiLine = true,
this.isClickable = true, this.isClickable = true,
this.onTagSelected, this.onTagSelected,
@@ -25,36 +22,33 @@ class TagsWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TagBlocProvider( return Builder(
child: BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>( builder: (context) {
builder: (context, state) { final children = tags
final children = tagIds .map(
.where((id) => state.labels.containsKey(id)) (tag) => TagWidget(
.map( tag: tag,
(id) => TagWidget( isClickable: isClickable,
tag: state.getLabel(id)!, onSelected: () => onTagSelected?.call(tag.id!),
isClickable: isClickable, showShortName: showShortNames,
onSelected: () => onTagSelected?.call(id), dense: dense,
showShortName: showShortNames, ),
dense: dense, )
), .toList();
) if (isMultiLine) {
.toList(); return Wrap(
if (isMultiLine) { runAlignment: WrapAlignment.start,
return Wrap( children: children,
runAlignment: WrapAlignment.start, runSpacing: 4,
children: children, spacing: 4,
runSpacing: 4, );
spacing: 4, } else {
); return SingleChildScrollView(
} else { scrollDirection: Axis.horizontal,
return SingleChildScrollView( child: Row(children: children),
scrollDirection: Axis.horizontal, );
child: Row(children: children), }
); },
}
},
),
); );
} }
} }
+36 -22
View File
@@ -6,7 +6,6 @@ import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_he
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/widgets/material/search/colored_tab_bar.dart'; import 'package:paperless_mobile/core/widgets/material/search/colored_tab_bar.dart';
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart'; import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart'; import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
@@ -17,6 +16,7 @@ import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_typ
import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart'; import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
@@ -141,12 +141,12 @@ class _LabelsPageState extends State<LabelsPage>
notificationPredicate: (notification) => notificationPredicate: (notification) =>
connectedState.isConnected, connectedState.isConnected,
onRefresh: () => [ onRefresh: () => [
context.read<LabelCubit<Correspondent>>(), context.read<LabelCubit>().reloadCorrespondents,
context.read<LabelCubit<DocumentType>>(), context.read<LabelCubit>().reloadDocumentTypes,
context.read<LabelCubit<Tag>>(), context.read<LabelCubit>().reloadTags,
context.read<LabelCubit<StoragePath>>(), context.read<LabelCubit>().reloadStoragePaths,
][_currentIndex] ][_currentIndex]
.reload(), .call(),
child: TabBarView( child: TabBarView(
controller: _tabController, controller: _tabController,
children: [ children: [
@@ -157,6 +157,10 @@ class _LabelsPageState extends State<LabelsPage>
SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: searchBarHandle),
SliverOverlapInjector(handle: tabBarHandle), SliverOverlapInjector(handle: tabBarHandle),
LabelTabView<Correspondent>( LabelTabView<Correspondent>(
labels: context
.watch<LabelCubit>()
.state
.correspondents,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
correspondent: correspondent:
IdQueryParameter.fromId(label.id), IdQueryParameter.fromId(label.id),
@@ -180,6 +184,10 @@ class _LabelsPageState extends State<LabelsPage>
SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: searchBarHandle),
SliverOverlapInjector(handle: tabBarHandle), SliverOverlapInjector(handle: tabBarHandle),
LabelTabView<DocumentType>( LabelTabView<DocumentType>(
labels: context
.watch<LabelCubit>()
.state
.documentTypes,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
documentType: documentType:
IdQueryParameter.fromId(label.id), IdQueryParameter.fromId(label.id),
@@ -203,6 +211,8 @@ class _LabelsPageState extends State<LabelsPage>
SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: searchBarHandle),
SliverOverlapInjector(handle: tabBarHandle), SliverOverlapInjector(handle: tabBarHandle),
LabelTabView<Tag>( LabelTabView<Tag>(
labels:
context.watch<LabelCubit>().state.tags,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
tags: IdsTagsQuery.fromIds([label.id!]), tags: IdsTagsQuery.fromIds([label.id!]),
pageSize: label.documentCount ?? 0, pageSize: label.documentCount ?? 0,
@@ -234,6 +244,10 @@ class _LabelsPageState extends State<LabelsPage>
SliverOverlapInjector(handle: searchBarHandle), SliverOverlapInjector(handle: searchBarHandle),
SliverOverlapInjector(handle: tabBarHandle), SliverOverlapInjector(handle: tabBarHandle),
LabelTabView<StoragePath>( LabelTabView<StoragePath>(
labels: context
.watch<LabelCubit>()
.state
.storagePaths,
onEdit: _openEditStoragePathPage, onEdit: _openEditStoragePathPage,
filterBuilder: (label) => DocumentFilter( filterBuilder: (label) => DocumentFilter(
storagePath: storagePath:
@@ -267,8 +281,8 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => RepositoryProvider( builder: (_) => RepositoryProvider.value(
create: (context) => context.read<LabelRepository<Correspondent>>(), value: context.read<LabelRepository>(),
child: EditCorrespondentPage(correspondent: correspondent), child: EditCorrespondentPage(correspondent: correspondent),
), ),
), ),
@@ -279,8 +293,8 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => RepositoryProvider( builder: (_) => RepositoryProvider.value(
create: (context) => context.read<LabelRepository<DocumentType>>(), value: context.read<LabelRepository>(),
child: EditDocumentTypePage(documentType: docType), child: EditDocumentTypePage(documentType: docType),
), ),
), ),
@@ -291,8 +305,8 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => RepositoryProvider( builder: (_) => RepositoryProvider.value(
create: (context) => context.read<LabelRepository<Tag>>(), value: context.read<LabelRepository>(),
child: EditTagPage(tag: tag), child: EditTagPage(tag: tag),
), ),
), ),
@@ -303,8 +317,8 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => RepositoryProvider( builder: (_) => RepositoryProvider.value(
create: (context) => context.read<LabelRepository<StoragePath>>(), value: context.read<LabelRepository>(),
child: EditStoragePathPage( child: EditStoragePathPage(
storagePath: path, storagePath: path,
), ),
@@ -317,8 +331,8 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => RepositoryProvider( builder: (_) => RepositoryProvider.value(
create: (context) => context.read<LabelRepository<Correspondent>>(), value: context.read<LabelRepository>(),
child: const AddCorrespondentPage(), child: const AddCorrespondentPage(),
), ),
), ),
@@ -329,8 +343,8 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => RepositoryProvider( builder: (_) => RepositoryProvider.value(
create: (context) => context.read<LabelRepository<DocumentType>>(), value: context.read<LabelRepository>(),
child: const AddDocumentTypePage(), child: const AddDocumentTypePage(),
), ),
), ),
@@ -341,8 +355,8 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => RepositoryProvider( builder: (_) => RepositoryProvider.value(
create: (context) => context.read<LabelRepository<Tag>>(), value: context.read<LabelRepository>(),
child: const AddTagPage(), child: const AddTagPage(),
), ),
), ),
@@ -353,8 +367,8 @@ class _LabelsPageState extends State<LabelsPage>
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => RepositoryProvider( builder: (_) => RepositoryProvider.value(
create: (context) => context.read<LabelRepository<StoragePath>>(), value: context.read<LabelRepository>(),
child: const AddStoragePathPage(), child: const AddStoragePathPage(),
), ),
), ),
@@ -51,6 +51,7 @@ class LabelItem<T extends Label> extends StatelessWidget {
filter, filter,
context.read(), context.read(),
context.read(), context.read(),
context.read(),
), ),
child: const LinkedDocumentsPage(), child: const LinkedDocumentsPage(),
), ),
@@ -3,12 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart'; import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart'; import 'package:paperless_mobile/core/widgets/offline_widget.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart'; import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
class LabelTabView<T extends Label> extends StatelessWidget { class LabelTabView<T extends Label> extends StatelessWidget {
final Map<int, T> labels;
final DocumentFilter Function(Label) filterBuilder; final DocumentFilter Function(Label) filterBuilder;
final void Function(T) onEdit; final void Function(T) onEdit;
final void Function() onAddNew; final void Function() onAddNew;
@@ -32,69 +32,61 @@ class LabelTabView<T extends Label> extends StatelessWidget {
required this.emptyStateDescription, required this.emptyStateDescription,
required this.onAddNew, required this.onAddNew,
required this.emptyStateActionButtonLabel, required this.emptyStateActionButtonLabel,
required this.labels,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocBuilder<ConnectivityCubit, ConnectivityState>(
create: (context) => LabelCubit<T>( builder: (context, connectivityState) {
context.read(), if (!connectivityState.isConnected) {
), return const OfflineWidget();
child: BlocBuilder<ConnectivityCubit, ConnectivityState>( }
builder: (context, connectivityState) { final sortedLabels = labels.values.toList()..sort();
return BlocBuilder<LabelCubit<T>, LabelState<T>>( if (labels.isEmpty) {
builder: (context, state) { return SliverFillRemaining(
if (!state.isLoaded && !connectivityState.isConnected) { child: Center(
return const OfflineWidget(); child: Column(
} crossAxisAlignment: CrossAxisAlignment.center,
final labels = state.labels.values.toList()..sort(); children: [
if (labels.isEmpty) { Text(
return SliverFillRemaining( emptyStateDescription,
child: Center( textAlign: TextAlign.center,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
emptyStateDescription,
textAlign: TextAlign.center,
),
TextButton(
onPressed: onAddNew,
child: Text(emptyStateActionButtonLabel),
),
].padded(),
),
), ),
); TextButton(
} onPressed: onAddNew,
return SliverList( child: Text(emptyStateActionButtonLabel),
delegate: SliverChildBuilderDelegate( ),
(context, index) { ].padded(),
final l = labels.elementAt(index); ),
return LabelItem<T>( ),
name: l.name, );
content: contentBuilder?.call(l) ?? }
Text( return SliverList(
translateMatchingAlgorithmName( delegate: SliverChildBuilderDelegate(
context, l.matchingAlgorithm) + (context, index) {
((l.match?.isNotEmpty ?? false) final l = sortedLabels.elementAt(index);
? ": ${l.match}" return LabelItem<T>(
: ""), name: l.name,
maxLines: 2, content: contentBuilder?.call(l) ??
), Text(
onOpenEditPage: onEdit, translateMatchingAlgorithmName(
filterBuilder: filterBuilder, context, l.matchingAlgorithm) +
leading: leadingBuilder?.call(l), ((l.match?.isNotEmpty ?? false)
label: l, ? ": ${l.match}"
); : ""),
}, maxLines: 2,
childCount: labels.length, ),
), onOpenEditPage: onEdit,
filterBuilder: filterBuilder,
leading: leadingBuilder?.call(l),
label: l,
); );
}, },
); childCount: labels.length,
}, ),
), );
},
); );
} }
} }
@@ -1,37 +1,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
class LabelText<T extends Label> extends StatelessWidget { class LabelText<T extends Label> extends StatelessWidget {
final int? id; final T? label;
final String placeholder; final String placeholder;
final TextStyle? style; final TextStyle? style;
const LabelText({ const LabelText({
super.key, super.key,
this.style, this.style,
this.id,
this.placeholder = "", this.placeholder = "",
required this.label,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return Text(
create: (context) => LabelCubit<T>( label?.toString() ?? placeholder,
context.read<LabelRepository<T>>(), style: style,
), maxLines: 1,
child: BlocBuilder<LabelCubit<T>, LabelState<T>>( overflow: TextOverflow.ellipsis,
builder: (context, state) {
return Text(
state.labels[id]?.toString() ?? placeholder,
style: style,
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
},
),
); );
; ;
} }
@@ -2,6 +2,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart';
@@ -17,12 +18,26 @@ class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
@override @override
final DocumentChangedNotifier notifier; final DocumentChangedNotifier notifier;
final LabelRepository _labelRepository;
LinkedDocumentsCubit( LinkedDocumentsCubit(
DocumentFilter filter, DocumentFilter filter,
this.api, this.api,
this.notifier, this.notifier,
this._labelRepository,
) : super(const LinkedDocumentsState()) { ) : super(const LinkedDocumentsState()) {
updateFilter(filter: filter); updateFilter(filter: filter);
_labelRepository.subscribe(
this,
onChanged: (labels) {
emit(state.copyWith(
correspondents: labels.correspondents,
documentTypes: labels.documentTypes,
tags: labels.tags,
storagePaths: labels.storagePaths,
));
},
);
notifier.subscribe( notifier.subscribe(
this, this,
onUpdated: replace, onUpdated: replace,
@@ -4,12 +4,22 @@ part of 'linked_documents_cubit.dart';
class LinkedDocumentsState extends DocumentPagingState { class LinkedDocumentsState extends DocumentPagingState {
@JsonKey() @JsonKey()
final ViewType viewType; final ViewType viewType;
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, StoragePath> storagePaths;
final Map<int, Tag> tags;
const LinkedDocumentsState({ const LinkedDocumentsState({
this.viewType = ViewType.list, this.viewType = ViewType.list,
super.filter, super.filter,
super.isLoading, super.isLoading,
super.hasLoaded, super.hasLoaded,
super.value, super.value,
this.correspondents = const {},
this.documentTypes = const {},
this.storagePaths = const {},
this.tags = const {},
}); });
LinkedDocumentsState copyWith({ LinkedDocumentsState copyWith({
@@ -18,6 +28,10 @@ class LinkedDocumentsState extends DocumentPagingState {
bool? hasLoaded, bool? hasLoaded,
List<PagedSearchResult<DocumentModel>>? value, List<PagedSearchResult<DocumentModel>>? value,
ViewType? viewType, ViewType? viewType,
Map<int, Correspondent>? correspondents,
Map<int, DocumentType>? documentTypes,
Map<int, StoragePath>? storagePaths,
Map<int, Tag>? tags,
}) { }) {
return LinkedDocumentsState( return LinkedDocumentsState(
filter: filter ?? this.filter, filter: filter ?? this.filter,
@@ -25,6 +39,10 @@ class LinkedDocumentsState extends DocumentPagingState {
hasLoaded: hasLoaded ?? this.hasLoaded, hasLoaded: hasLoaded ?? this.hasLoaded,
value: value ?? this.value, value: value ?? this.value,
viewType: viewType ?? this.viewType, viewType: viewType ?? this.viewType,
correspondents: correspondents ?? this.correspondents,
documentTypes: documentTypes ?? this.documentTypes,
storagePaths: storagePaths ?? this.storagePaths,
tags: tags ?? this.tags,
); );
} }
@@ -61,6 +61,10 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage>
), ),
); );
}, },
correspondents: state.correspondents,
documentTypes: state.documentTypes,
storagePaths: state.storagePaths,
tags: state.tags,
), ),
], ],
); );
@@ -2,42 +2,72 @@ import 'dart:async';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
part 'saved_view_state.dart'; part 'saved_view_state.dart';
part 'saved_view_cubit.freezed.dart';
class SavedViewCubit extends Cubit<SavedViewState> { class SavedViewCubit extends Cubit<SavedViewState> {
final SavedViewRepository _repository; final SavedViewRepository _savedViewRepository;
StreamSubscription? _subscription; final LabelRepository _labelRepository;
SavedViewCubit(this._repository) : super(const SavedViewState()) { SavedViewCubit(this._savedViewRepository, this._labelRepository)
_subscription = _repository.values.listen( : super(SavedViewState.initial(
(savedViews) { correspondents: _labelRepository.state.correspondents,
if (savedViews?.hasLoaded ?? false) { documentTypes: _labelRepository.state.documentTypes,
emit(state.copyWith(value: savedViews?.values, hasLoaded: true)); storagePaths: _labelRepository.state.storagePaths,
} else { tags: _labelRepository.state.tags,
emit(state.copyWith(hasLoaded: false)); )) {
} _labelRepository.subscribe(
this,
onChanged: (labels) {
emit(
state.copyWith(
correspondents: labels.correspondents,
documentTypes: labels.documentTypes,
tags: labels.tags,
storagePaths: labels.storagePaths,
),
);
}, },
); );
_savedViewRepository.subscribe(this, (views) {
emit(
state.maybeWhen(
loaded:
(savedViews, correspondents, documentTypes, tags, storagePaths) =>
(state as _SavedViewLoadedState).copyWith(
savedViews: views,
),
orElse: () => state,
),
);
});
} }
Future<SavedView> add(SavedView view) async { Future<SavedView> add(SavedView view) async {
final savedView = await _repository.create(view); return _savedViewRepository.create(view);
emit(state.copyWith(value: {...state.value, savedView.id!: savedView}));
return savedView;
} }
Future<int> remove(SavedView view) { Future<int> remove(SavedView view) {
return _repository.delete(view); return _savedViewRepository.delete(view);
} }
Future<void> initialize() async { Future<void> initialize() async {
final views = await _repository.findAll(); final views = await _savedViewRepository.findAll();
final values = {for (var element in views) element.id!: element}; final values = {for (var element in views) element.id!: element};
if (!isClosed) { if (!isClosed) {
emit(SavedViewState(value: values, hasLoaded: true)); emit(SavedViewState.loaded(
savedViews: values,
correspondents: state.correspondents,
documentTypes: state.documentTypes,
storagePaths: state.storagePaths,
tags: state.tags,
));
} }
} }
@@ -45,7 +75,8 @@ class SavedViewCubit extends Cubit<SavedViewState> {
@override @override
Future<void> close() { Future<void> close() {
_subscription?.cancel(); _savedViewRepository.unsubscribe(this);
_labelRepository.unsubscribe(this);
return super.close(); return super.close();
} }
} }
File diff suppressed because it is too large Load Diff
@@ -1,29 +1,33 @@
part of 'saved_view_cubit.dart'; part of 'saved_view_cubit.dart';
class SavedViewState extends Equatable { @freezed
final bool hasLoaded; class SavedViewState with _$SavedViewState {
final Map<int, SavedView> value; const factory SavedViewState.initial({
required Map<int, Correspondent> correspondents,
required Map<int, DocumentType> documentTypes,
required Map<int, Tag> tags,
required Map<int, StoragePath> storagePaths,
}) = _SavedViewIntialState;
const SavedViewState({ const factory SavedViewState.loading({
this.value = const {}, required Map<int, Correspondent> correspondents,
this.hasLoaded = false, required Map<int, DocumentType> documentTypes,
}); required Map<int, Tag> tags,
required Map<int, StoragePath> storagePaths,
}) = _SavedViewLoadingState;
@override const factory SavedViewState.loaded({
List<Object?> get props => [ required Map<int, SavedView> savedViews,
hasLoaded, required Map<int, Correspondent> correspondents,
value, required Map<int, DocumentType> documentTypes,
]; required Map<int, Tag> tags,
required Map<int, StoragePath> storagePaths,
}) = _SavedViewLoadedState;
SavedViewState copyWith({ const factory SavedViewState.error({
Map<int, SavedView>? value, required Map<int, Correspondent> correspondents,
int? selectedSavedViewId, required Map<int, DocumentType> documentTypes,
bool overwriteSelectedSavedViewId = false, required Map<int, Tag> tags,
bool? hasLoaded, required Map<int, StoragePath> storagePaths,
}) { }) = _SavedViewErrorState;
return SavedViewState(
value: value ?? this.value,
hasLoaded: hasLoaded ?? this.hasLoaded,
);
}
} }
@@ -62,7 +62,7 @@ class _AddSavedViewPageState extends State<AddSavedViewPage> {
], ],
), ),
), ),
Divider(), const Divider(),
Text( Text(
"Review filter", "Review filter",
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
@@ -80,42 +80,6 @@ class _AddSavedViewPageState extends State<AddSavedViewPage> {
); );
} }
Padding _buildOld(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
FormBuilder(
key: _savedViewFormKey,
child: Expanded(
child: ListView(
children: [
FormBuilderTextField(
name: fkName,
validator: FormBuilderValidators.required(),
decoration: InputDecoration(
label: Text(S.of(context)!.name),
),
),
FormBuilderCheckbox(
name: fkShowOnDashboard,
initialValue: false,
title: Text(S.of(context)!.showOnDashboard),
),
FormBuilderCheckbox(
name: fkShowInSidebar,
initialValue: false,
title: Text(S.of(context)!.showInSidebar),
),
],
),
),
),
],
),
);
}
void _onCreate(BuildContext context) { void _onCreate(BuildContext context) {
if (_savedViewFormKey.currentState?.saveAndValidate() ?? false) { if (_savedViewFormKey.currentState?.saveAndValidate() ?? false) {
Navigator.pop( Navigator.pop(
@@ -12,52 +12,70 @@ class SavedViewList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final savedViewCubit = context.read<SavedViewCubit>();
return BlocBuilder<ConnectivityCubit, ConnectivityState>( return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivity) { builder: (context, connectivity) {
return BlocBuilder<SavedViewCubit, SavedViewState>( return BlocBuilder<SavedViewCubit, SavedViewState>(
builder: (context, state) { builder: (context, state) {
if (state.value.isEmpty) { return state.when(
return SliverToBoxAdapter( initial: (correspondents, documentTypes, tags, storagePaths) =>
child: HintCard( Container(),
hintText: loading: (correspondents, documentTypes, tags, storagePaths) =>
S.of(context)!.createViewsToQuicklyFilterYourDocuments, Center(
), child: Text("Saved views loading..."),
); ),
} loaded: (savedViews, correspondents, documentTypes, tags,
return SliverList( storagePaths) {
delegate: SliverChildBuilderDelegate( if (savedViews.isEmpty) {
(context, index) { return SliverToBoxAdapter(
final view = state.value.values.elementAt(index); child: HintCard(
return ListTile( hintText: S
enabled: connectivity.isConnected, .of(context)!
title: Text(view.name), .createViewsToQuicklyFilterYourDocuments,
subtitle: Text(
S.of(context)!.nFiltersSet(view.filterRules.length),
), ),
onTap: () { );
Navigator.of(context).push( }
MaterialPageRoute( return SliverList(
builder: (context) => MultiBlocProvider( delegate: SliverChildBuilderDelegate(
providers: [ (context, index) {
BlocProvider( final view = savedViews.values.elementAt(index);
create: (context) => SavedViewDetailsCubit( return ListTile(
context.read(), enabled: connectivity.isConnected,
context.read(), title: Text(view.name),
savedView: view, subtitle: Text(
S.of(context)!.nFiltersSet(view.filterRules.length),
),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => SavedViewDetailsCubit(
context.read(),
context.read(),
savedView: view,
),
),
],
child: SavedViewDetailsPage(
onDelete:
context.read<SavedViewCubit>().remove,
), ),
), ),
],
child: SavedViewDetailsPage(
onDelete: savedViewCubit.remove,
), ),
), );
), },
); );
}, },
); childCount: savedViews.length,
}, ),
childCount: state.value.length, );
},
error: (correspondents, documentTypes, tags, storagePaths) =>
Center(
child: Text(
"An error occurred while trying to load the saved views.",
),
), ),
); );
}, },
@@ -82,10 +82,7 @@ class AccountSettingsDialog extends StatelessWidget {
try { try {
await context.read<AuthenticationCubit>().logout(); await context.read<AuthenticationCubit>().logout();
await context.read<ApplicationSettingsCubit>().clear(); await context.read<ApplicationSettingsCubit>().clear();
await context.read<LabelRepository<Tag>>().clear(); await context.read<LabelRepository>().clear();
await context.read<LabelRepository<Correspondent>>().clear();
await context.read<LabelRepository<DocumentType>>().clear();
await context.read<LabelRepository<StoragePath>>().clear();
await context.read<SavedViewRepository>().clear(); await context.read<SavedViewRepository>().clear();
await HydratedBloc.storage.clear(); await HydratedBloc.storage.clear();
} on PaperlessServerException catch (error, stackTrace) { } on PaperlessServerException catch (error, stackTrace) {
+3 -20
View File
@@ -21,17 +21,12 @@ import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.da
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart'; import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart'; import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/impl/correspondent_repository_impl.dart';
import 'package:paperless_mobile/core/repository/impl/document_type_repository_impl.dart';
import 'package:paperless_mobile/core/repository/impl/saved_view_repository_impl.dart'; import 'package:paperless_mobile/core/repository/impl/saved_view_repository_impl.dart';
import 'package:paperless_mobile/core/repository/impl/storage_path_repository_impl.dart';
import 'package:paperless_mobile/core/repository/impl/tag_repository_impl.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/security/session_manager.dart'; import 'package:paperless_mobile/core/security/session_manager.dart';
import 'package:paperless_mobile/core/service/connectivity_status_service.dart'; import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
import 'package:paperless_mobile/core/service/dio_file_service.dart'; import 'package:paperless_mobile/core/service/dio_file_service.dart';
import 'package:paperless_mobile/core/service/file_service.dart';
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart'; import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
import 'package:paperless_mobile/features/home/view/home_page.dart'; import 'package:paperless_mobile/features/home/view/home_page.dart';
import 'package:paperless_mobile/features/home/view/widget/verify_identity_page.dart'; import 'package:paperless_mobile/features/home/view/widget/verify_identity_page.dart';
@@ -105,10 +100,7 @@ void main() async {
await connectivityCubit.initialize(); await connectivityCubit.initialize();
// Create repositories // Create repositories
final tagRepository = TagRepositoryImpl(labelsApi); final labelRepository = LabelRepository(labelsApi);
final correspondentRepository = CorrespondentRepositoryImpl(labelsApi);
final documentTypeRepository = DocumentTypeRepositoryImpl(labelsApi);
final storagePathRepository = StoragePathRepositoryImpl(labelsApi);
final savedViewRepository = SavedViewRepositoryImpl(savedViewsApi); final savedViewRepository = SavedViewRepositoryImpl(savedViewsApi);
//Create cubits/blocs //Create cubits/blocs
@@ -163,17 +155,8 @@ void main() async {
], ],
child: MultiRepositoryProvider( child: MultiRepositoryProvider(
providers: [ providers: [
RepositoryProvider<LabelRepository<Tag>>.value( RepositoryProvider<LabelRepository>.value(
value: tagRepository, value: labelRepository,
),
RepositoryProvider<LabelRepository<Correspondent>>.value(
value: correspondentRepository,
),
RepositoryProvider<LabelRepository<DocumentType>>.value(
value: documentTypeRepository,
),
RepositoryProvider<LabelRepository<StoragePath>>.value(
value: storagePathRepository,
), ),
RepositoryProvider<SavedViewRepository>.value( RepositoryProvider<SavedViewRepository>.value(
value: savedViewRepository, value: savedViewRepository,
+3 -2
View File
@@ -1,7 +1,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart'; import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
@@ -19,9 +18,11 @@ class DocumentDetailsRoute extends StatelessWidget {
context.read(), context.read(),
context.read(), context.read(),
context.read(), context.read(),
context.read(),
initialDocument: args.document, initialDocument: args.document,
), ),
child: LabelRepositoriesProvider( child: RepositoryProvider.value(
value: context.read(),
child: DocumentDetailsPage( child: DocumentDetailsPage(
allowEdit: args.allowEdit, allowEdit: args.allowEdit,
isLabelClickable: args.isLabelClickable, isLabelClickable: args.isLabelClickable,
@@ -1,7 +1,7 @@
import 'package:paperless_api/src/models/saved_view_model.dart'; import 'package:paperless_api/src/models/saved_view_model.dart';
abstract class PaperlessSavedViewsApi { abstract class PaperlessSavedViewsApi {
Future<SavedView> find(int id); Future<SavedView?> find(int id);
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]); Future<Iterable<SavedView>> findAll([Iterable<int>? ids]);
Future<SavedView> save(SavedView view); Future<SavedView> save(SavedView view);
@@ -60,7 +60,7 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
} }
@override @override
Future<SavedView> find(int id) { Future<SavedView?> find(int id) {
return getSingleResult( return getSingleResult(
"/api/saved_views/$id/", "/api/saved_views/$id/",
SavedView.fromJson, SavedView.fromJson,
@@ -4,7 +4,7 @@ import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:paperless_api/src/models/paperless_server_exception.dart'; import 'package:paperless_api/src/models/paperless_server_exception.dart';
Future<T> getSingleResult<T>( Future<T?> getSingleResult<T>(
String url, String url,
T Function(Map<String, dynamic>) fromJson, T Function(Map<String, dynamic>) fromJson,
ErrorCode errorCode, { ErrorCode errorCode, {
+16
View File
@@ -731,6 +731,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.4.0" version: "8.4.0"
freezed:
dependency: "direct dev"
description:
name: freezed
sha256: e819441678f1679b719008ff2ff0ef045d66eed9f9ec81166ca0d9b02a187454
url: "https://pub.dev"
source: hosted
version: "2.3.2"
freezed_annotation:
dependency: "direct main"
description:
name: freezed_annotation
sha256: aeac15850ef1b38ee368d4c53ba9a847e900bb2c53a4db3f6881cbb3cb684338
url: "https://pub.dev"
source: hosted
version: "2.2.0"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
+2
View File
@@ -91,6 +91,7 @@ dependencies:
dynamic_color: ^1.5.4 dynamic_color: ^1.5.4
flutter_html: ^3.0.0-alpha.6 flutter_html: ^3.0.0-alpha.6
in_app_review: ^2.0.6 in_app_review: ^2.0.6
freezed_annotation: ^2.2.0
dev_dependencies: dev_dependencies:
integration_test: integration_test:
@@ -105,6 +106,7 @@ dev_dependencies:
json_serializable: ^6.5.4 json_serializable: ^6.5.4
dart_code_metrics: ^5.4.0 dart_code_metrics: ^5.4.0
auto_route_generator: ^5.0.3 auto_route_generator: ^5.0.3
freezed: ^2.3.2
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec