mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 15:15:50 -06:00
WIP - More decoupling of data layer from ui layer
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
||||
|
||||
class BlocChangesObserver extends BlocObserver {
|
||||
@override
|
||||
|
||||
67
lib/core/repository/impl/correspondent_repository_impl.dart
Normal file
67
lib/core/repository/impl/correspondent_repository_impl.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
||||
|
||||
class CorrespondentRepositoryImpl implements LabelRepository<Correspondent> {
|
||||
final PaperlessLabelsApi _api;
|
||||
|
||||
final _subject = BehaviorSubject<Map<int, Correspondent>>.seeded(const {});
|
||||
|
||||
CorrespondentRepositoryImpl(this._api);
|
||||
@override
|
||||
Stream<Map<int, Correspondent>> get labels =>
|
||||
_subject.stream.asBroadcastStream();
|
||||
|
||||
@override
|
||||
Future<Correspondent> create(Correspondent correspondent) async {
|
||||
final created = await _api.saveCorrespondent(correspondent);
|
||||
final updatedState = {..._subject.value}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
_subject.add(updatedState);
|
||||
return created;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(Correspondent correspondent) async {
|
||||
await _api.deleteCorrespondent(correspondent);
|
||||
final updatedState = {..._subject.value}
|
||||
..removeWhere((k, v) => k == correspondent.id);
|
||||
_subject.add(updatedState);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Correspondent?> find(int id) async {
|
||||
final correspondent = await _api.getCorrespondent(id);
|
||||
if (correspondent != null) {
|
||||
final updatedState = {..._subject.value}..[id] = correspondent;
|
||||
_subject.add(updatedState);
|
||||
return correspondent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Iterable<Correspondent>> findAll([Iterable<int>? ids]) async {
|
||||
final correspondents = await _api.getCorrespondents(ids);
|
||||
final updatedState = {..._subject.value}
|
||||
..addEntries(correspondents.map((e) => MapEntry(e.id!, e)));
|
||||
_subject.add(updatedState);
|
||||
return correspondents;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Correspondent> update(Correspondent correspondent) async {
|
||||
final updated = await _api.updateCorrespondent(correspondent);
|
||||
final updatedState = {..._subject.value}
|
||||
..update(updated.id!, (_) => updated);
|
||||
_subject.add(updatedState);
|
||||
return updated;
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
_subject.add(const {});
|
||||
}
|
||||
}
|
||||
66
lib/core/repository/impl/document_type_repository_impl.dart
Normal file
66
lib/core/repository/impl/document_type_repository_impl.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
||||
|
||||
class DocumentTypeRepositoryImpl implements LabelRepository<DocumentType> {
|
||||
final PaperlessLabelsApi _api;
|
||||
|
||||
final _subject = BehaviorSubject<Map<int, DocumentType>>.seeded(const {});
|
||||
|
||||
DocumentTypeRepositoryImpl(this._api);
|
||||
|
||||
@override
|
||||
Stream<Map<int, DocumentType>> get labels =>
|
||||
_subject.stream.asBroadcastStream();
|
||||
|
||||
@override
|
||||
Future<DocumentType> create(DocumentType documentType) async {
|
||||
final created = await _api.saveDocumentType(documentType);
|
||||
final updatedState = {..._subject.value}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
_subject.add(updatedState);
|
||||
return created;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(DocumentType documentType) async {
|
||||
await _api.deleteDocumentType(documentType);
|
||||
final updatedState = {..._subject.value}
|
||||
..removeWhere((k, v) => k == documentType.id);
|
||||
_subject.add(updatedState);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DocumentType?> find(int id) async {
|
||||
final documentType = await _api.getDocumentType(id);
|
||||
if (documentType != null) {
|
||||
final updatedState = {..._subject.value}..[id] = documentType;
|
||||
_subject.add(updatedState);
|
||||
return documentType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Iterable<DocumentType>> findAll([Iterable<int>? ids]) async {
|
||||
final documentTypes = await _api.getDocumentTypes(ids);
|
||||
final updatedState = {..._subject.value}
|
||||
..addEntries(documentTypes.map((e) => MapEntry(e.id!, e)));
|
||||
_subject.add(updatedState);
|
||||
return documentTypes;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DocumentType> update(DocumentType documentType) async {
|
||||
final updated = await _api.updateDocumentType(documentType);
|
||||
final updatedState = {..._subject.value}
|
||||
..update(updated.id!, (_) => updated);
|
||||
_subject.add(updatedState);
|
||||
return updated;
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
_subject.add(const {});
|
||||
}
|
||||
}
|
||||
57
lib/core/repository/impl/saved_view_repository_impl.dart
Normal file
57
lib/core/repository/impl/saved_view_repository_impl.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_api/src/models/saved_view_model.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
class SavedViewRepositoryImpl implements SavedViewRepository {
|
||||
final PaperlessSavedViewsApi _api;
|
||||
|
||||
SavedViewRepositoryImpl(this._api);
|
||||
|
||||
final BehaviorSubject<Map<int, SavedView>> _subject =
|
||||
BehaviorSubject.seeded({});
|
||||
|
||||
@override
|
||||
Stream<Map<int, SavedView>> get savedViews =>
|
||||
_subject.stream.asBroadcastStream();
|
||||
|
||||
@override
|
||||
void clear() {}
|
||||
|
||||
@override
|
||||
Future<SavedView> create(SavedView view) async {
|
||||
final created = await _api.save(view);
|
||||
final updatedState = {..._subject.value}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
_subject.add(updatedState);
|
||||
return created;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> delete(SavedView view) async {
|
||||
await _api.delete(view);
|
||||
final updatedState = {..._subject.value}..remove(view.id);
|
||||
_subject.add(updatedState);
|
||||
return view.id!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SavedView?> find(int id) async {
|
||||
final found = await _api.find(id);
|
||||
final updatedState = {..._subject.value}
|
||||
..update(id, (_) => found, ifAbsent: () => found);
|
||||
_subject.add(updatedState);
|
||||
return found;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
|
||||
final found = await _api.findAll(ids);
|
||||
final updatedState = {
|
||||
..._subject.value,
|
||||
...{for (final view in found) view.id!: view},
|
||||
};
|
||||
_subject.add(updatedState);
|
||||
return found;
|
||||
}
|
||||
}
|
||||
66
lib/core/repository/impl/storage_path_repository_impl.dart
Normal file
66
lib/core/repository/impl/storage_path_repository_impl.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
||||
|
||||
class StoragePathRepositoryImpl implements LabelRepository<StoragePath> {
|
||||
final PaperlessLabelsApi _api;
|
||||
|
||||
final _subject = BehaviorSubject<Map<int, StoragePath>>.seeded(const {});
|
||||
|
||||
StoragePathRepositoryImpl(this._api);
|
||||
|
||||
@override
|
||||
Stream<Map<int, StoragePath>> get labels =>
|
||||
_subject.stream.asBroadcastStream();
|
||||
|
||||
@override
|
||||
Future<StoragePath> create(StoragePath storagePath) async {
|
||||
final created = await _api.saveStoragePath(storagePath);
|
||||
final updatedState = {..._subject.value}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
_subject.add(updatedState);
|
||||
return created;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(StoragePath storagePath) async {
|
||||
await _api.deleteStoragePath(storagePath);
|
||||
final updatedState = {..._subject.value}
|
||||
..removeWhere((k, v) => k == storagePath.id);
|
||||
_subject.add(updatedState);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StoragePath?> find(int id) async {
|
||||
final storagePath = await _api.getStoragePath(id);
|
||||
if (storagePath != null) {
|
||||
final updatedState = {..._subject.value}..[id] = storagePath;
|
||||
_subject.add(updatedState);
|
||||
return storagePath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Iterable<StoragePath>> findAll([Iterable<int>? ids]) async {
|
||||
final storagePaths = await _api.getStoragePaths(ids);
|
||||
final updatedState = {..._subject.value}
|
||||
..addEntries(storagePaths.map((e) => MapEntry(e.id!, e)));
|
||||
_subject.add(updatedState);
|
||||
return storagePaths;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StoragePath> update(StoragePath storagePath) async {
|
||||
final updated = await _api.updateStoragePath(storagePath);
|
||||
final updatedState = {..._subject.value}
|
||||
..update(updated.id!, (_) => updated);
|
||||
_subject.add(updatedState);
|
||||
return updated;
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
_subject.add(const {});
|
||||
}
|
||||
}
|
||||
65
lib/core/repository/impl/tag_repository_impl.dart
Normal file
65
lib/core/repository/impl/tag_repository_impl.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:rxdart/rxdart.dart' show BehaviorSubject;
|
||||
|
||||
class TagRepositoryImpl implements LabelRepository<Tag> {
|
||||
final PaperlessLabelsApi _api;
|
||||
|
||||
final _subject = BehaviorSubject<Map<int, Tag>>.seeded(const {});
|
||||
|
||||
TagRepositoryImpl(this._api);
|
||||
|
||||
@override
|
||||
Stream<Map<int, Tag>> get labels => _subject.stream.asBroadcastStream();
|
||||
|
||||
@override
|
||||
Future<Tag> create(Tag tag) async {
|
||||
final created = await _api.saveTag(tag);
|
||||
final updatedState = {..._subject.value}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
_subject.add(updatedState);
|
||||
return created;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(Tag tag) async {
|
||||
await _api.deleteTag(tag);
|
||||
final updatedState = {..._subject.value}
|
||||
..removeWhere((k, v) => k == tag.id);
|
||||
_subject.add(updatedState);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Tag?> find(int id) async {
|
||||
final tag = await _api.getTag(id);
|
||||
if (tag != null) {
|
||||
final updatedState = {..._subject.value}..[id] = tag;
|
||||
_subject.add(updatedState);
|
||||
return tag;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Iterable<Tag>> findAll([Iterable<int>? ids]) async {
|
||||
final tags = await _api.getTags(ids);
|
||||
final updatedState = {..._subject.value}
|
||||
..addEntries(tags.map((e) => MapEntry(e.id!, e)));
|
||||
_subject.add(updatedState);
|
||||
return tags;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Tag> update(Tag tag) async {
|
||||
final updated = await _api.updateTag(tag);
|
||||
final updatedState = {..._subject.value}
|
||||
..update(updated.id!, (_) => updated);
|
||||
_subject.add(updatedState);
|
||||
return updated;
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() {
|
||||
_subject.add(const {});
|
||||
}
|
||||
}
|
||||
13
lib/core/repository/label_repository.dart
Normal file
13
lib/core/repository/label_repository.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
abstract class LabelRepository<T extends Label> {
|
||||
Stream<Map<int, T>> get labels;
|
||||
|
||||
Future<T> create(T label);
|
||||
Future<T?> find(int id);
|
||||
Future<Iterable<T>> findAll([Iterable<int>? ids]);
|
||||
Future<T> update(T label);
|
||||
Future<void> delete(T label);
|
||||
|
||||
void clear();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/src/widgets/container.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
|
||||
class LabelRepositoriesProvider extends StatelessWidget {
|
||||
final Widget child;
|
||||
const LabelRepositoriesProvider({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiRepositoryProvider(
|
||||
providers: [
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
12
lib/core/repository/saved_view_repository.dart
Normal file
12
lib/core/repository/saved_view_repository.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
abstract class SavedViewRepository {
|
||||
Stream<Map<int, SavedView>> get savedViews;
|
||||
|
||||
Future<SavedView> create(SavedView view);
|
||||
Future<SavedView?> find(int id);
|
||||
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]);
|
||||
Future<int> delete(SavedView view);
|
||||
|
||||
void clear();
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
@@ -17,7 +18,6 @@ import 'package:paperless_mobile/features/documents/view/pages/document_edit_pag
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_widget.dart';
|
||||
@@ -213,7 +213,29 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
Navigator.push<bool>(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
builder: (_) => MultiRepositoryProvider(
|
||||
providers: [
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Tag>>(
|
||||
context,
|
||||
),
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(
|
||||
context,
|
||||
),
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: DocumentEditPage(
|
||||
document: cubit.state.document!,
|
||||
onEdit: (updatedDocument) {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
|
||||
part 'document_upload_state.dart';
|
||||
|
||||
class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
||||
final LocalVault _localVault;
|
||||
final PaperlessDocumentsApi _documentApi;
|
||||
|
||||
final LabelRepository<Tag> _tagRepository;
|
||||
final LabelRepository<Correspondent> _correspondentRepository;
|
||||
final LabelRepository<DocumentType> _documentTypeRepository;
|
||||
|
||||
final List<StreamSubscription> _subs = const [];
|
||||
|
||||
DocumentUploadCubit({
|
||||
required LocalVault localVault,
|
||||
required PaperlessDocumentsApi documentApi,
|
||||
required LabelRepository<Tag> tagRepository,
|
||||
required LabelRepository<Correspondent> correspondentRepository,
|
||||
required LabelRepository<DocumentType> documentTypeRepository,
|
||||
}) : _documentApi = documentApi,
|
||||
_tagRepository = tagRepository,
|
||||
_correspondentRepository = correspondentRepository,
|
||||
_documentTypeRepository = documentTypeRepository,
|
||||
_localVault = localVault,
|
||||
super(
|
||||
const DocumentUploadState(
|
||||
tags: {},
|
||||
correspondents: {},
|
||||
documentTypes: {},
|
||||
),
|
||||
) {
|
||||
_subs.add(_tagRepository.labels.listen(
|
||||
(tags) => emit(state.copyWith(tags: tags)),
|
||||
));
|
||||
_subs.add(_correspondentRepository.labels.listen(
|
||||
(correspondents) => emit(state.copyWith(correspondents: correspondents)),
|
||||
));
|
||||
_subs.add(_documentTypeRepository.labels.listen(
|
||||
(documentTypes) => emit(state.copyWith(documentTypes: documentTypes)),
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> upload(
|
||||
Uint8List bytes, {
|
||||
required String filename,
|
||||
required String title,
|
||||
required void Function(DocumentModel document)? onConsumptionFinished,
|
||||
int? documentType,
|
||||
int? correspondent,
|
||||
Iterable<int> tags = const [],
|
||||
DateTime? createdAt,
|
||||
}) async {
|
||||
final auth = await _localVault.loadAuthenticationInformation();
|
||||
if (auth == null || !auth.isValid) {
|
||||
throw const PaperlessServerException(ErrorCode.notAuthenticated);
|
||||
}
|
||||
await _documentApi.create(
|
||||
bytes,
|
||||
filename: filename,
|
||||
title: title,
|
||||
correspondent: correspondent,
|
||||
documentType: documentType,
|
||||
tags: tags,
|
||||
createdAt: createdAt,
|
||||
authToken: auth.token!,
|
||||
serverUrl: auth.serverUrl,
|
||||
);
|
||||
if (onConsumptionFinished != null) {
|
||||
_documentApi
|
||||
.waitForConsumptionFinished(filename, title)
|
||||
.then((value) => onConsumptionFinished(value));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
for (final sub in _subs) {
|
||||
await sub.cancel();
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
part of 'document_upload_cubit.dart';
|
||||
|
||||
@immutable
|
||||
class DocumentUploadState extends Equatable {
|
||||
final Map<int, Tag> tags;
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
|
||||
const DocumentUploadState({
|
||||
required this.tags,
|
||||
required this.correspondents,
|
||||
required this.documentTypes,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
|
||||
DocumentUploadState copyWith({
|
||||
Map<int, Tag>? tags,
|
||||
Map<int, Correspondent>? correspondents,
|
||||
Map<int, DocumentType>? documentTypes,
|
||||
}) {
|
||||
return DocumentUploadState(
|
||||
tags: tags ?? this.tags,
|
||||
correspondents: correspondents ?? this.correspondents,
|
||||
documentTypes: documentTypes ?? this.documentTypes,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.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/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class DocumentUploadPreparationPage extends StatefulWidget {
|
||||
final Uint8List fileBytes;
|
||||
final String? title;
|
||||
final String? filename;
|
||||
final void Function(DocumentModel)? onSuccessfullyConsumed;
|
||||
|
||||
const DocumentUploadPreparationPage({
|
||||
Key? key,
|
||||
required this.fileBytes,
|
||||
this.title,
|
||||
this.filename,
|
||||
this.onSuccessfullyConsumed,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DocumentUploadPreparationPage> createState() =>
|
||||
_DocumentUploadPreparationPageState();
|
||||
}
|
||||
|
||||
class _DocumentUploadPreparationPageState
|
||||
extends State<DocumentUploadPreparationPage> {
|
||||
static const fkFileName = "filename";
|
||||
static final fileNameDateFormat = DateFormat("yyyy_MM_ddTHH_mm_ss");
|
||||
|
||||
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
|
||||
|
||||
PaperlessValidationErrors _errors = {};
|
||||
bool _isUploadLoading = false;
|
||||
late bool _syncTitleAndFilename;
|
||||
final _now = DateTime.now();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_syncTitleAndFilename = widget.filename == null && widget.title == null;
|
||||
initializeDateFormatting();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).documentsUploadPageTitle),
|
||||
bottom: _isUploadLoading
|
||||
? const PreferredSize(
|
||||
child: LinearProgressIndicator(),
|
||||
preferredSize: Size.fromHeight(4.0))
|
||||
: null,
|
||||
),
|
||||
floatingActionButton: Visibility(
|
||||
visible: MediaQuery.of(context).viewInsets.bottom == 0,
|
||||
child: FloatingActionButton.extended(
|
||||
onPressed: _onSubmit,
|
||||
label: Text(S.of(context).genericActionUploadLabel),
|
||||
icon: const Icon(Icons.upload),
|
||||
),
|
||||
),
|
||||
body: BlocBuilder<DocumentUploadCubit, DocumentUploadState>(
|
||||
builder: (context, state) {
|
||||
return FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
FormBuilderTextField(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
name: DocumentModel.titleKey,
|
||||
initialValue:
|
||||
widget.title ?? "scan_${fileNameDateFormat.format(_now)}",
|
||||
validator: FormBuilderValidators.required(),
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context).documentTitlePropertyLabel,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
_formKey.currentState?.fields[DocumentModel.titleKey]
|
||||
?.didChange("");
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]
|
||||
?.didChange("");
|
||||
}
|
||||
},
|
||||
),
|
||||
errorText: _errors[DocumentModel.titleKey],
|
||||
),
|
||||
onChanged: (value) {
|
||||
final String transformedValue =
|
||||
_formatFilename(value ?? '');
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]
|
||||
?.didChange(transformedValue);
|
||||
}
|
||||
},
|
||||
),
|
||||
FormBuilderTextField(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
readOnly: _syncTitleAndFilename,
|
||||
enabled: !_syncTitleAndFilename,
|
||||
name: fkFileName,
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context).documentUploadFileNameLabel,
|
||||
suffixText: ".pdf",
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () => _formKey.currentState?.fields[fkFileName]
|
||||
?.didChange(''),
|
||||
),
|
||||
),
|
||||
initialValue: widget.filename ??
|
||||
"scan_${fileNameDateFormat.format(_now)}",
|
||||
),
|
||||
SwitchListTile(
|
||||
value: _syncTitleAndFilename,
|
||||
onChanged: (value) {
|
||||
setState(
|
||||
() => _syncTitleAndFilename = value,
|
||||
);
|
||||
if (_syncTitleAndFilename) {
|
||||
final String transformedValue = _formatFilename(_formKey
|
||||
.currentState
|
||||
?.fields[DocumentModel.titleKey]
|
||||
?.value as String);
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]
|
||||
?.didChange(transformedValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
title: Text(S
|
||||
.of(context)
|
||||
.documentUploadPageSynchronizeTitleAndFilenameLabel), //TODO: INTL
|
||||
),
|
||||
FormBuilderDateTimePicker(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
format: DateFormat("dd. MMMM yyyy"), //TODO: INTL
|
||||
inputType: InputType.date,
|
||||
name: DocumentModel.createdKey,
|
||||
initialValue: null,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.calendar_month_outlined),
|
||||
labelText:
|
||||
S.of(context).documentCreatedPropertyLabel + " *",
|
||||
),
|
||||
),
|
||||
LabelFormField<DocumentType, DocumentTypeQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialName) =>
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
child: AddDocumentTypePage(initialName: initialName),
|
||||
),
|
||||
label: S.of(context).documentDocumentTypePropertyLabel + " *",
|
||||
name: DocumentModel.documentTypeKey,
|
||||
state: state.documentTypes,
|
||||
queryParameterIdBuilder: DocumentTypeQuery.fromId,
|
||||
queryParameterNotAssignedBuilder:
|
||||
DocumentTypeQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
),
|
||||
LabelFormField<Correspondent, CorrespondentQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialName) =>
|
||||
RepositoryProvider.value(
|
||||
value:
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
child: AddCorrespondentPage(initialName: initialName),
|
||||
),
|
||||
label:
|
||||
S.of(context).documentCorrespondentPropertyLabel + " *",
|
||||
name: DocumentModel.correspondentKey,
|
||||
state: state.correspondents,
|
||||
queryParameterIdBuilder: CorrespondentQuery.fromId,
|
||||
queryParameterNotAssignedBuilder:
|
||||
CorrespondentQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
),
|
||||
const TagFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
notAssignedSelectable: false,
|
||||
anyAssignedSelectable: false,
|
||||
excludeAllowed: false,
|
||||
//Label: "Tags" + " *",
|
||||
),
|
||||
Text(
|
||||
"* " +
|
||||
S
|
||||
.of(context)
|
||||
.uploadPageAutomaticallInferredFieldsHintText,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
].padded(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSubmit() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final cubit = BlocProvider.of<DocumentUploadCubit>(context);
|
||||
try {
|
||||
setState(() => _isUploadLoading = true);
|
||||
|
||||
final fv = _formKey.currentState!.value;
|
||||
|
||||
final createdAt = fv[DocumentModel.createdKey] as DateTime?;
|
||||
final title = fv[DocumentModel.titleKey] as String;
|
||||
final docType = fv[DocumentModel.documentTypeKey] as IdQueryParameter;
|
||||
final tags = fv[DocumentModel.tagsKey] as IdsTagsQuery;
|
||||
final correspondent =
|
||||
fv[DocumentModel.correspondentKey] as IdQueryParameter;
|
||||
|
||||
await cubit.upload(
|
||||
widget.fileBytes,
|
||||
filename:
|
||||
_padWithPdfExtension(_formKey.currentState?.value[fkFileName]),
|
||||
title: title,
|
||||
onConsumptionFinished: widget.onSuccessfullyConsumed,
|
||||
documentType: docType.id,
|
||||
correspondent: correspondent.id,
|
||||
tags: tags.ids,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
showSnackBar(context, S.of(context).documentUploadSuccessText);
|
||||
Navigator.pop(context, true);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} on PaperlessValidationErrors catch (PaperlessServerExceptions) {
|
||||
setState(() => _errors = PaperlessServerExceptions);
|
||||
} catch (unknownError, stackTrace) {
|
||||
showErrorMessage(
|
||||
context, const PaperlessServerException.unknown(), stackTrace);
|
||||
} finally {
|
||||
setState(() {
|
||||
_isUploadLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _padWithPdfExtension(String source) {
|
||||
return source.endsWith(".pdf") ? source : '$source.pdf';
|
||||
}
|
||||
|
||||
String _formatFilename(String source) {
|
||||
return source.replaceAll(RegExp(r"[\W_]"), "_");
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@lazySingleton
|
||||
part 'documents_state.dart';
|
||||
|
||||
class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
final PaperlessDocumentsApi _api;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
part of 'documents_cubit.dart';
|
||||
|
||||
class DocumentsState extends Equatable {
|
||||
final bool isLoaded;
|
||||
|
||||
@@ -7,15 +7,15 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/view/pages/add_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/view/pages/add_document_type_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_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/labels_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/pages/add_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
@@ -57,10 +57,133 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
return LabelsBlocProvider(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () async {
|
||||
onPressed: _onSubmit,
|
||||
icon: const Icon(Icons.save),
|
||||
label: Text(S.of(context).genericActionSaveLabel),
|
||||
),
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).documentEditPageTitle),
|
||||
bottom: _isSubmitLoading
|
||||
? const PreferredSize(
|
||||
preferredSize: Size.fromHeight(4),
|
||||
child: LinearProgressIndicator(),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
extendBody: true,
|
||||
body: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
top: 8,
|
||||
left: 8,
|
||||
right: 8,
|
||||
),
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(children: [
|
||||
_buildTitleFormField().padded(),
|
||||
_buildCreatedAtFormField().padded(),
|
||||
_buildDocumentTypeFormField().padded(),
|
||||
_buildCorrespondentFormField().padded(),
|
||||
_buildStoragePathFormField().padded(),
|
||||
TagFormField(
|
||||
initialValue: IdsTagsQuery.included(widget.document.tags),
|
||||
notAssignedSelectable: false,
|
||||
anyAssignedSelectable: false,
|
||||
excludeAllowed: false,
|
||||
name: fkTags,
|
||||
).padded(),
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>
|
||||
_buildStoragePathFormField() {
|
||||
return BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<StoragePath, StoragePathQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
child: AddStoragePathPage(initalValue: initialValue),
|
||||
),
|
||||
label: S.of(context).documentStoragePathPropertyLabel,
|
||||
state: state.labels,
|
||||
initialValue: StoragePathQuery.fromId(widget.document.storagePath),
|
||||
name: fkStoragePath,
|
||||
queryParameterIdBuilder: StoragePathQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: StoragePathQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>
|
||||
_buildCorrespondentFormField() {
|
||||
return BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<Correspondent, CorrespondentQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
child: AddCorrespondentPage(initialName: initialValue),
|
||||
),
|
||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||
state: state.labels,
|
||||
initialValue:
|
||||
CorrespondentQuery.fromId(widget.document.correspondent),
|
||||
name: fkCorrespondent,
|
||||
queryParameterIdBuilder: CorrespondentQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>
|
||||
_buildDocumentTypeFormField() {
|
||||
return BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<DocumentType, DocumentTypeQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (currentInput) =>
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
child: AddDocumentTypePage(
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
label: S.of(context).documentDocumentTypePropertyLabel,
|
||||
initialValue: DocumentTypeQuery.fromId(widget.document.documentType),
|
||||
state: state.labels,
|
||||
name: fkDocumentType,
|
||||
queryParameterIdBuilder: DocumentTypeQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSubmit() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final values = _formKey.currentState!.value;
|
||||
var updatedDocument = widget.document.copyWith(
|
||||
@@ -91,111 +214,6 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.save),
|
||||
label: Text(S.of(context).genericActionSaveLabel),
|
||||
),
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).documentEditPageTitle),
|
||||
bottom: _isSubmitLoading
|
||||
? const PreferredSize(
|
||||
preferredSize: Size.fromHeight(4),
|
||||
child: LinearProgressIndicator(),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
extendBody: true,
|
||||
body: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
top: 8,
|
||||
left: 8,
|
||||
right: 8,
|
||||
),
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(children: [
|
||||
_buildTitleFormField().padded(),
|
||||
_buildCreatedAtFormField().padded(),
|
||||
BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<DocumentType, DocumentTypeQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (currentInput) =>
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentTypeCubit>(context),
|
||||
child: AddDocumentTypePage(
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
label: S.of(context).documentDocumentTypePropertyLabel,
|
||||
initialValue:
|
||||
DocumentTypeQuery.fromId(widget.document.documentType),
|
||||
state: state.labels,
|
||||
name: fkDocumentType,
|
||||
queryParameterIdBuilder: DocumentTypeQuery.fromId,
|
||||
queryParameterNotAssignedBuilder:
|
||||
DocumentTypeQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
);
|
||||
},
|
||||
).padded(),
|
||||
BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<Correspondent, CorrespondentQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<CorrespondentCubit>(context),
|
||||
child: AddCorrespondentPage(initalValue: initialValue),
|
||||
),
|
||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||
state: state.labels,
|
||||
initialValue:
|
||||
CorrespondentQuery.fromId(widget.document.correspondent),
|
||||
name: fkCorrespondent,
|
||||
queryParameterIdBuilder: CorrespondentQuery.fromId,
|
||||
queryParameterNotAssignedBuilder:
|
||||
CorrespondentQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
);
|
||||
},
|
||||
).padded(),
|
||||
BlocBuilder<StoragePathCubit, LabelState<StoragePath>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<StoragePath, StoragePathQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<StoragePathCubit>(context),
|
||||
child: AddStoragePathPage(initalValue: initialValue),
|
||||
),
|
||||
label: S.of(context).documentStoragePathPropertyLabel,
|
||||
state: state.labels,
|
||||
initialValue:
|
||||
StoragePathQuery.fromId(widget.document.storagePath),
|
||||
name: fkStoragePath,
|
||||
queryParameterIdBuilder: StoragePathQuery.fromId,
|
||||
queryParameterNotAssignedBuilder:
|
||||
StoragePathQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
);
|
||||
},
|
||||
).padded(),
|
||||
TagFormField(
|
||||
initialValue: IdsTagsQuery.included(widget.document.tags),
|
||||
notAssignedSelectable: false,
|
||||
anyAssignedSelectable: false,
|
||||
excludeAllowed: false,
|
||||
name: fkTags,
|
||||
).padded(),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTitleFormField() {
|
||||
|
||||
@@ -3,11 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/grid/document_grid.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list.dart';
|
||||
@@ -15,11 +16,10 @@ import 'package:paperless_mobile/features/documents/view/widgets/search/document
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/documents_page_app_bar.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/labels_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
@@ -132,9 +132,20 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
),
|
||||
body: _buildBody(connectivityState),
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
panelBuilder: (scrollController) => DocumentFilterPanel(
|
||||
panelBuilder: (scrollController) =>
|
||||
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
return LabelsBlocProvider(
|
||||
child: DocumentFilterPanel(
|
||||
panelController: _filterPanelController,
|
||||
scrollController: scrollController,
|
||||
initialFilter: state.filter,
|
||||
onFilterChanged: (filter) =>
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateFilter(filter: filter),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -192,7 +203,29 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
onRefresh: _onRefresh,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
DocumentsPageAppBar(
|
||||
BlocProvider(
|
||||
create: (context) => SavedViewCubit(
|
||||
RepositoryProvider.of<SavedViewRepository>(context)),
|
||||
child: BlocListener<SavedViewCubit, SavedViewState>(
|
||||
listener: (context, state) {
|
||||
final documentsCubit =
|
||||
BlocProvider.of<DocumentsCubit>(context);
|
||||
try {
|
||||
if (state.selectedSavedViewId == null) {
|
||||
documentsCubit.updateFilter();
|
||||
} else {
|
||||
final newFilter = state
|
||||
.value[state.selectedSavedViewId]
|
||||
?.toDocumentFilter();
|
||||
if (newFilter != null) {
|
||||
documentsCubit.updateFilter(filter: newFilter);
|
||||
}
|
||||
}
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
},
|
||||
child: DocumentsPageAppBar(
|
||||
actions: [
|
||||
const SortDocumentsButton(),
|
||||
IconButton(
|
||||
@@ -202,12 +235,15 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
: Icons.grid_view,
|
||||
),
|
||||
onPressed: () =>
|
||||
BlocProvider.of<ApplicationSettingsCubit>(context)
|
||||
BlocProvider.of<ApplicationSettingsCubit>(
|
||||
context)
|
||||
.setViewType(
|
||||
settings.preferredViewType.toggle()),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
child,
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
@@ -233,29 +269,11 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
MaterialPageRoute<DocumentModel?> _buildDetailsPageRoute(
|
||||
DocumentModel document) {
|
||||
return MaterialPageRoute(
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentsCubit>(context),
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: DocumentDetailsCubit(getIt<PaperlessDocumentsApi>(), document),
|
||||
child: const LabelRepositoriesProvider(
|
||||
child: DocumentDetailsPage(),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<CorrespondentCubit>(context),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentTypeCubit>(context),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<TagCubit>(context),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<StoragePathCubit>(context),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value:
|
||||
DocumentDetailsCubit(getIt<PaperlessDocumentsApi>(), document),
|
||||
),
|
||||
],
|
||||
child: const DocumentDetailsPage(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/widgets/empty_state.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class DocumentsEmptyState extends StatelessWidget {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/grid/document_grid_item.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
|
||||
@@ -34,7 +36,8 @@ class DocumentListView extends StatelessWidget {
|
||||
builderDelegate: PagedChildBuilderDelegate(
|
||||
animateTransitions: true,
|
||||
itemBuilder: (context, document, index) {
|
||||
return DocumentListItem(
|
||||
return LabelRepositoriesProvider(
|
||||
child: DocumentListItem(
|
||||
isLabelClickable: isLabelClickable,
|
||||
document: document,
|
||||
onTap: onTap,
|
||||
@@ -49,6 +52,7 @@ class DocumentListView extends StatelessWidget {
|
||||
: false;
|
||||
},
|
||||
onTagSelected: onTagSelected,
|
||||
),
|
||||
);
|
||||
},
|
||||
noItemsFoundIndicatorBuilder: (context) => hasInternetConnection
|
||||
|
||||
@@ -4,15 +4,12 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/search/query_type_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
@@ -24,10 +21,15 @@ class DocumentFilterPanel extends StatefulWidget {
|
||||
final PanelController panelController;
|
||||
final ScrollController scrollController;
|
||||
|
||||
final DocumentFilter initialFilter;
|
||||
|
||||
final void Function(DocumentFilter filter) onFilterChanged;
|
||||
const DocumentFilterPanel({
|
||||
Key? key,
|
||||
required this.panelController,
|
||||
required this.scrollController,
|
||||
required this.onFilterChanged,
|
||||
required this.initialFilter,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -63,13 +65,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
child: BlocConsumer<DocumentsCubit, DocumentsState>(
|
||||
listener: (context, state) {
|
||||
// Set initial values, otherwise they would not automatically update.
|
||||
_patchFromFilter(state.filter);
|
||||
},
|
||||
builder: (context, state) {
|
||||
return FormBuilder(
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -81,8 +77,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
alignment: Alignment.topRight,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: Text(
|
||||
S.of(context).documentsFilterPageResetFilterLabel),
|
||||
label:
|
||||
Text(S.of(context).documentsFilterPageResetFilterLabel),
|
||||
onPressed: () => _resetFilter(context),
|
||||
),
|
||||
),
|
||||
@@ -100,8 +96,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _onApplyFilter,
|
||||
child: Text(
|
||||
S.of(context).documentsFilterPageApplyFilterLabel),
|
||||
child:
|
||||
Text(S.of(context).documentsFilterPageApplyFilterLabel),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
@@ -119,23 +115,22 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
S.of(context).documentsFilterPageSearchLabel),
|
||||
child: Text(S.of(context).documentsFilterPageSearchLabel),
|
||||
).padded(const EdgeInsets.only(left: 8.0)),
|
||||
_buildQueryFormField(state),
|
||||
_buildQueryFormField(),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
S.of(context).documentsFilterPageAdvancedLabel),
|
||||
child:
|
||||
Text(S.of(context).documentsFilterPageAdvancedLabel),
|
||||
).padded(const EdgeInsets.only(left: 8.0, top: 8.0)),
|
||||
_buildCreatedDateRangePickerFormField(state).padded(),
|
||||
_buildAddedDateRangePickerFormField(state).padded(),
|
||||
_buildCorrespondentFormField(state).padded(),
|
||||
_buildDocumentTypeFormField(state).padded(),
|
||||
_buildStoragePathFormField(state).padded(),
|
||||
_buildCreatedDateRangePickerFormField().padded(),
|
||||
_buildAddedDateRangePickerFormField().padded(),
|
||||
_buildCorrespondentFormField().padded(),
|
||||
_buildDocumentTypeFormField().padded(),
|
||||
_buildStoragePathFormField().padded(),
|
||||
TagFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
initialValue: state.filter.tags,
|
||||
initialValue: widget.initialFilter.tags,
|
||||
allowCreation: false,
|
||||
).padded(),
|
||||
// Required in order for the storage path field to be visible when typing
|
||||
@@ -148,8 +143,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -163,15 +156,16 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildDocumentTypeFormField(DocumentsState docState) {
|
||||
return BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
|
||||
//TODO: Check if the blocs can be found in the context, otherwise just provide repository and create new bloc inside LabelFormField!
|
||||
Widget _buildDocumentTypeFormField() {
|
||||
return BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<DocumentType, DocumentTypeQuery>(
|
||||
formBuilderState: _formKey.currentState,
|
||||
name: fkDocumentType,
|
||||
state: state.labels,
|
||||
label: S.of(context).documentDocumentTypePropertyLabel,
|
||||
initialValue: docState.filter.documentType,
|
||||
initialValue: widget.initialFilter.documentType,
|
||||
queryParameterIdBuilder: DocumentTypeQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
@@ -180,15 +174,15 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCorrespondentFormField(DocumentsState docState) {
|
||||
return BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
|
||||
Widget _buildCorrespondentFormField() {
|
||||
return BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<Correspondent, CorrespondentQuery>(
|
||||
formBuilderState: _formKey.currentState,
|
||||
name: fkCorrespondent,
|
||||
state: state.labels,
|
||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||
initialValue: docState.filter.correspondent,
|
||||
initialValue: widget.initialFilter.correspondent,
|
||||
queryParameterIdBuilder: CorrespondentQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
@@ -197,15 +191,15 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStoragePathFormField(DocumentsState docState) {
|
||||
return BlocBuilder<StoragePathCubit, LabelState<StoragePath>>(
|
||||
Widget _buildStoragePathFormField() {
|
||||
return BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
|
||||
builder: (context, state) {
|
||||
return LabelFormField<StoragePath, StoragePathQuery>(
|
||||
formBuilderState: _formKey.currentState,
|
||||
name: fkStoragePath,
|
||||
state: state.labels,
|
||||
label: S.of(context).documentStoragePathPropertyLabel,
|
||||
initialValue: docState.filter.storagePath,
|
||||
initialValue: widget.initialFilter.storagePath,
|
||||
queryParameterIdBuilder: StoragePathQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: StoragePathQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
@@ -214,7 +208,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQueryFormField(DocumentsState state) {
|
||||
Widget _buildQueryFormField() {
|
||||
final queryType =
|
||||
_formKey.currentState?.getRawValue(QueryTypeFormField.fkQueryType) ??
|
||||
QueryType.titleAndContent;
|
||||
@@ -239,16 +233,15 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
prefixIcon: const Icon(Icons.search_outlined),
|
||||
labelText: label,
|
||||
suffixIcon: QueryTypeFormField(
|
||||
initialValue: state.filter.queryType,
|
||||
initialValue: widget.initialFilter.queryType,
|
||||
afterSelected: (queryType) => setState(() {}),
|
||||
),
|
||||
),
|
||||
initialValue: state.filter.queryText,
|
||||
initialValue: widget.initialFilter.queryText,
|
||||
).padded();
|
||||
}
|
||||
|
||||
Widget _buildDateRangePickerHelper(
|
||||
DocumentsState state, String formFieldKey) {
|
||||
Widget _buildDateRangePickerHelper(String formFieldKey) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
@@ -328,13 +321,13 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCreatedDateRangePickerFormField(DocumentsState state) {
|
||||
Widget _buildCreatedDateRangePickerFormField() {
|
||||
return Column(
|
||||
children: [
|
||||
FormBuilderDateRangePicker(
|
||||
initialValue: _dateTimeRangeOfNullable(
|
||||
state.filter.createdDateAfter,
|
||||
state.filter.createdDateBefore,
|
||||
widget.initialFilter.createdDateAfter,
|
||||
widget.initialFilter.createdDateBefore,
|
||||
),
|
||||
// Workaround for theme data not being correctly passed to daterangepicker, see
|
||||
// https://github.com/flutter/flutter/issues/87580
|
||||
@@ -371,18 +364,18 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
_buildDateRangePickerHelper(state, fkCreatedAt),
|
||||
_buildDateRangePickerHelper(fkCreatedAt),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAddedDateRangePickerFormField(DocumentsState state) {
|
||||
Widget _buildAddedDateRangePickerFormField() {
|
||||
return Column(
|
||||
children: [
|
||||
FormBuilderDateRangePicker(
|
||||
initialValue: _dateTimeRangeOfNullable(
|
||||
state.filter.addedDateAfter,
|
||||
state.filter.addedDateBefore,
|
||||
widget.initialFilter.addedDateAfter,
|
||||
widget.initialFilter.addedDateBefore,
|
||||
),
|
||||
// Workaround for theme data not being correctly passed to daterangepicker, see
|
||||
// https://github.com/flutter/flutter/issues/87580
|
||||
@@ -419,7 +412,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
_buildDateRangePickerHelper(state, fkAddedAt),
|
||||
_buildDateRangePickerHelper(fkAddedAt),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class SortFieldSelectionBottomSheet extends StatefulWidget {
|
||||
const SortFieldSelectionBottomSheet({super.key});
|
||||
final SortOrder initialSortOrder;
|
||||
final SortField initialSortField;
|
||||
|
||||
final Future Function(SortField field, SortOrder order) onSubmit;
|
||||
|
||||
const SortFieldSelectionBottomSheet({
|
||||
super.key,
|
||||
required this.initialSortOrder,
|
||||
required this.initialSortField,
|
||||
required this.onSubmit,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SortFieldSelectionBottomSheet> createState() =>
|
||||
@@ -17,81 +23,60 @@ class SortFieldSelectionBottomSheet extends StatefulWidget {
|
||||
|
||||
class _SortFieldSelectionBottomSheetState
|
||||
extends State<SortFieldSelectionBottomSheet> {
|
||||
SortField? _selectedFieldLoading;
|
||||
SortOrder? _selectedOrderLoading;
|
||||
late SortField _currentSortField;
|
||||
late SortOrder _currentSortOrder;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentSortField = widget.initialSortField;
|
||||
_currentSortOrder = widget.initialSortOrder;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
child: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
bloc: getIt<DocumentsCubit>(),
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).documentsPageOrderByLabel,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
textAlign: TextAlign.start,
|
||||
).padded(
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 16)),
|
||||
Column(
|
||||
children: SortField.values
|
||||
.map(
|
||||
(e) => _buildSortOption(
|
||||
e,
|
||||
state.filter.sortOrder,
|
||||
state.filter.sortField == e,
|
||||
_selectedFieldLoading == e,
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(S.of(context).documentsFilterPageApplyFilterLabel),
|
||||
onPressed: () => widget.onSubmit(
|
||||
_currentSortField,
|
||||
_currentSortOrder,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Column(
|
||||
children: SortField.values.map(_buildSortOption).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSortOption(
|
||||
SortField field,
|
||||
SortOrder order,
|
||||
bool isCurrentlySelected,
|
||||
bool isNextSelected,
|
||||
) {
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
title: Text(
|
||||
_localizedSortField(field),
|
||||
),
|
||||
trailing: isNextSelected
|
||||
? (_buildOrderIcon(_selectedOrderLoading!))
|
||||
: (_selectedOrderLoading == null && isCurrentlySelected
|
||||
? _buildOrderIcon(order)
|
||||
: null),
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_selectedFieldLoading = field;
|
||||
_selectedOrderLoading =
|
||||
isCurrentlySelected ? order.toggle() : SortOrder.descending;
|
||||
});
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateCurrentFilter((filter) => filter.copyWith(
|
||||
sortOrder: isCurrentlySelected
|
||||
? order.toggle()
|
||||
: SortOrder.descending,
|
||||
sortField: field,
|
||||
))
|
||||
.whenComplete(() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_selectedFieldLoading = null;
|
||||
_selectedOrderLoading = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
trailing: _currentSortField == field
|
||||
? _buildOrderIcon(_currentSortOrder)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class BulkDeleteConfirmationDialog extends StatelessWidget {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/view/saved_view_selection_widget.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
@@ -79,7 +80,6 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
//TODO: replace with sorting stuff...
|
||||
SavedViewSelectionWidget(height: 48, enabled: enabled),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/search/sort_field_selection_bottom_sheet.dart';
|
||||
|
||||
@@ -33,11 +32,19 @@ class _SortDocumentsButtonState extends State<SortDocumentsButton> {
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
child: const FractionallySizedBox(
|
||||
builder: (context) => FractionallySizedBox(
|
||||
heightFactor: .6,
|
||||
child: SortFieldSelectionBottomSheet(),
|
||||
child: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
return SortFieldSelectionBottomSheet(
|
||||
initialSortField: state.filter.sortField,
|
||||
initialSortOrder: state.filter.sortOrder,
|
||||
onSubmit: (field, order) =>
|
||||
BlocProvider.of<DocumentsCubit>(context).updateCurrentFilter(
|
||||
(filter) => filter.copyWith(sortField: field, sortOrder: order),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
31
lib/features/edit_label/cubit/edit_label_cubit.dart
Normal file
31
lib/features/edit_label/cubit/edit_label_cubit.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_state.dart';
|
||||
|
||||
class EditLabelCubit<T extends Label> extends Cubit<EditLabelState<T>> {
|
||||
final LabelRepository<T> _repository;
|
||||
|
||||
StreamSubscription<Map<int, T>>? _subscription;
|
||||
|
||||
EditLabelCubit(LabelRepository<T> repository)
|
||||
: _repository = repository,
|
||||
super(const EditLabelInitial()) {
|
||||
_subscription = _repository.labels
|
||||
.listen((labels) => emit(EditLabelState(labels: labels)));
|
||||
}
|
||||
|
||||
Future<void> create(T label) => _repository.create(label);
|
||||
|
||||
Future<void> update(T label) => _repository.update(label);
|
||||
|
||||
Future<void> delete(T label) => _repository.delete(label);
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_subscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
16
lib/features/edit_label/cubit/edit_label_state.dart
Normal file
16
lib/features/edit_label/cubit/edit_label_state.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
@immutable
|
||||
class EditLabelState<T> extends Equatable {
|
||||
final Map<int, T> labels;
|
||||
|
||||
const EditLabelState({required this.labels});
|
||||
|
||||
@override
|
||||
List<Object> get props => [labels];
|
||||
}
|
||||
|
||||
class EditLabelInitial<T> extends EditLabelState<T> {
|
||||
const EditLabelInitial() : super(labels: const {});
|
||||
}
|
||||
71
lib/features/edit_label/view/add_label_page.dart
Normal file
71
lib/features/edit_label/view/add_label_page.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'package:flutter/material.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/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class AddLabelPage<T extends Label> extends StatelessWidget {
|
||||
final String? initialName;
|
||||
final Widget pageTitle;
|
||||
final T Function(Map<String, dynamic> json) fromJsonT;
|
||||
final List<Widget> additionalFields;
|
||||
|
||||
const AddLabelPage({
|
||||
super.key,
|
||||
this.initialName,
|
||||
required this.pageTitle,
|
||||
required this.fromJsonT,
|
||||
this.additionalFields = const [],
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
RepositoryProvider.of<LabelRepository<T>>(context),
|
||||
),
|
||||
child: AddLabelFormWidget(
|
||||
pageTitle: pageTitle,
|
||||
label: fromJsonT({'name': initialName}),
|
||||
additionalFields: additionalFields,
|
||||
fromJsonT: fromJsonT,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AddLabelFormWidget<T extends Label> extends StatelessWidget {
|
||||
final T? label;
|
||||
final T Function(Map<String, dynamic> json) fromJsonT;
|
||||
final List<Widget> additionalFields;
|
||||
|
||||
final Widget pageTitle;
|
||||
const AddLabelFormWidget({
|
||||
super.key,
|
||||
this.label,
|
||||
required this.fromJsonT,
|
||||
required this.additionalFields,
|
||||
required this.pageTitle,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: pageTitle,
|
||||
),
|
||||
body: LabelForm<T>(
|
||||
initialValue: label,
|
||||
fromJsonT: fromJsonT,
|
||||
submitButtonConfig: SubmitButtonConfig<T>(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(S.of(context).genericActionCreateLabel),
|
||||
onSubmit: BlocProvider.of<EditLabelCubit<T>>(context).create,
|
||||
),
|
||||
additionalFields: additionalFields,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
105
lib/features/edit_label/view/edit_label_page.dart
Normal file
105
lib/features/edit_label/view/edit_label_page.dart
Normal file
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.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/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class EditLabelPage<T extends Label> extends StatelessWidget {
|
||||
final T label;
|
||||
final T Function(Map<String, dynamic> json) fromJsonT;
|
||||
final List<Widget> additionalFields;
|
||||
|
||||
const EditLabelPage({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.fromJsonT,
|
||||
this.additionalFields = const [],
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
RepositoryProvider.of<LabelRepository<T>>(context),
|
||||
),
|
||||
child: EditLabelForm(
|
||||
label: label,
|
||||
additionalFields: additionalFields,
|
||||
fromJsonT: fromJsonT,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EditLabelForm<T extends Label> extends StatelessWidget {
|
||||
final T label;
|
||||
final T Function(Map<String, dynamic> json) fromJsonT;
|
||||
final List<Widget> additionalFields;
|
||||
|
||||
const EditLabelForm({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.fromJsonT,
|
||||
required this.additionalFields,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).genericActionEditLabel),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => _onDelete(context),
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: LabelForm<T>(
|
||||
initialValue: label,
|
||||
fromJsonT: fromJsonT,
|
||||
submitButtonConfig: SubmitButtonConfig<T>(
|
||||
icon: const Icon(Icons.update),
|
||||
label: Text(S.of(context).genericActionUpdateLabel),
|
||||
onSubmit: BlocProvider.of<EditLabelCubit<T>>(context).update,
|
||||
),
|
||||
additionalFields: additionalFields,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onDelete(BuildContext context) {
|
||||
if ((label.documentCount ?? 0) > 0) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(S.of(context).editLabelPageConfirmDeletionDialogTitle),
|
||||
content: Text(
|
||||
S.of(context).editLabelPageDeletionDialogText,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(S.of(context).genericActionCancelLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
BlocProvider.of<EditLabelCubit<T>>(context).delete(label);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
S.of(context).genericActionDeleteLabel,
|
||||
style: TextStyle(color: Theme.of(context).errorColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
BlocProvider.of<EditLabelCubit<T>>(context).delete(label);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class AddCorrespondentPage extends StatelessWidget {
|
||||
final String? initialName;
|
||||
const AddCorrespondentPage({Key? key, this.initialName}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit<Correspondent>(
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
),
|
||||
child: AddLabelPage<Correspondent>(
|
||||
pageTitle: Text(S.of(context).addCorrespondentPageTitle),
|
||||
fromJsonT: Correspondent.fromJson,
|
||||
initialName: initialName,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.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/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class AddDocumentTypePage extends StatelessWidget {
|
||||
final String? initialName;
|
||||
const AddDocumentTypePage({
|
||||
super.key,
|
||||
this.initialName,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit<DocumentType>(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
child: AddLabelPage<DocumentType>(
|
||||
pageTitle: Text(S.of(context).addDocumentTypePageTitle),
|
||||
fromJsonT: DocumentType.fromJson,
|
||||
initialName: initialName,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
31
lib/features/edit_label/view/impl/add_storage_path_page.dart
Normal file
31
lib/features/edit_label/view/impl/add_storage_path_page.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.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/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/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class AddStoragePathPage extends StatelessWidget {
|
||||
final String? initalValue;
|
||||
const AddStoragePathPage({Key? key, this.initalValue}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit<StoragePath>(
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
child: AddLabelPage<StoragePath>(
|
||||
pageTitle: Text(S.of(context).addStoragePathPageTitle),
|
||||
fromJsonT: StoragePath.fromJson,
|
||||
initialName: initalValue,
|
||||
additionalFields: const [
|
||||
StoragePathAutofillFormBuilderField(name: StoragePath.pathKey),
|
||||
SizedBox(height: 120.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
43
lib/features/edit_label/view/impl/add_tag_page.dart
Normal file
43
lib/features/edit_label/view/impl/add_tag_page.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_extra_fields/form_builder_extra_fields.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.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/generated/l10n.dart';
|
||||
|
||||
class AddTagPage extends StatelessWidget {
|
||||
final String? initialValue;
|
||||
const AddTagPage({Key? key, this.initialValue}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit<Tag>(
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
),
|
||||
child: AddLabelPage<Tag>(
|
||||
pageTitle: Text(S.of(context).addTagPageTitle),
|
||||
fromJsonT: Tag.fromJson,
|
||||
initialName: initialValue,
|
||||
additionalFields: [
|
||||
FormBuilderColorPickerField(
|
||||
name: Tag.colorKey,
|
||||
valueTransformer: (color) => "#${color?.value.toRadixString(16)}",
|
||||
decoration: InputDecoration(
|
||||
label: Text(S.of(context).tagColorPropertyLabel),
|
||||
),
|
||||
colorPickerType: ColorPickerType.materialPicker,
|
||||
initialValue: null,
|
||||
),
|
||||
FormBuilderCheckbox(
|
||||
name: Tag.isInboxTagKey,
|
||||
title: Text(S.of(context).tagInboxTagPropertyLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.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/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/labels/bloc/label_cubit.dart';
|
||||
|
||||
class EditCorrespondentPage extends StatelessWidget {
|
||||
final Correspondent correspondent;
|
||||
const EditCorrespondentPage({super.key, required this.correspondent});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit<Correspondent>(
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
),
|
||||
child: EditLabelPage<Correspondent>(
|
||||
label: correspondent,
|
||||
fromJsonT: Correspondent.fromJson,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
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/features/edit_label/view/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
|
||||
class EditDocumentTypePage extends StatelessWidget {
|
||||
final DocumentType documentType;
|
||||
const EditDocumentTypePage({super.key, required this.documentType});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
child: EditLabelPage<DocumentType>(
|
||||
label: documentType,
|
||||
fromJsonT: DocumentType.fromJson,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.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/features/edit_label/view/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
|
||||
|
||||
class EditStoragePathPage extends StatelessWidget {
|
||||
final StoragePath storagePath;
|
||||
const EditStoragePathPage({super.key, required this.storagePath});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<StoragePath>(
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
child: EditLabelPage<StoragePath>(
|
||||
label: storagePath,
|
||||
fromJsonT: StoragePath.fromJson,
|
||||
additionalFields: [
|
||||
StoragePathAutofillFormBuilderField(
|
||||
name: StoragePath.pathKey,
|
||||
initialValue: storagePath.path,
|
||||
),
|
||||
const SizedBox(height: 120.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
43
lib/features/edit_label/view/impl/edit_tag_page.dart
Normal file
43
lib/features/edit_label/view/impl/edit_tag_page.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_extra_fields/form_builder_extra_fields.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class EditTagPage extends StatelessWidget {
|
||||
final Tag tag;
|
||||
|
||||
const EditTagPage({super.key, required this.tag});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<Tag>(
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
),
|
||||
child: EditLabelPage<Tag>(
|
||||
label: tag,
|
||||
fromJsonT: Tag.fromJson,
|
||||
additionalFields: [
|
||||
FormBuilderColorPickerField(
|
||||
initialValue: tag.color,
|
||||
name: Tag.colorKey,
|
||||
decoration: InputDecoration(
|
||||
label: Text(S.of(context).tagColorPropertyLabel),
|
||||
),
|
||||
colorPickerType: ColorPickerType.blockPicker,
|
||||
),
|
||||
FormBuilderCheckbox(
|
||||
initialValue: tag.isInboxTag,
|
||||
name: Tag.isInboxTagKey,
|
||||
title: Text(S.of(context).tagInboxTagPropertyLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
@@ -9,27 +7,42 @@ import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class EditLabelPage<T extends Label> extends StatefulWidget {
|
||||
final T label;
|
||||
class SubmitButtonConfig<T extends Label> {
|
||||
final Widget icon;
|
||||
final Widget label;
|
||||
final Future<void> Function(T) onSubmit;
|
||||
final Future<void> Function(T) onDelete;
|
||||
final T Function(JSON) fromJson;
|
||||
|
||||
SubmitButtonConfig({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.onSubmit,
|
||||
});
|
||||
}
|
||||
|
||||
class LabelForm<T extends Label> extends StatefulWidget {
|
||||
final T? initialValue;
|
||||
|
||||
final SubmitButtonConfig submitButtonConfig;
|
||||
|
||||
/// FromJson method to parse the form field values into a label instance.
|
||||
final T Function(Map<String, dynamic> json) fromJsonT;
|
||||
|
||||
/// List of additionally rendered form fields.
|
||||
final List<Widget> additionalFields;
|
||||
|
||||
const EditLabelPage({
|
||||
const LabelForm({
|
||||
Key? key,
|
||||
required this.label,
|
||||
required this.fromJson,
|
||||
required this.onSubmit,
|
||||
required this.onDelete,
|
||||
required this.initialValue,
|
||||
required this.fromJsonT,
|
||||
this.additionalFields = const [],
|
||||
required this.submitButtonConfig,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<EditLabelPage> createState() => _EditLabelPageState<T>();
|
||||
State<LabelForm> createState() => _LabelFormState<T>();
|
||||
}
|
||||
|
||||
class _EditLabelPageState<T extends Label> extends State<EditLabelPage<T>> {
|
||||
class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
PaperlessValidationErrors _errors = {};
|
||||
@@ -38,18 +51,9 @@ class _EditLabelPageState<T extends Label> extends State<EditLabelPage<T>> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).genericActionEditLabel),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _onDelete,
|
||||
icon: const Icon(Icons.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.update),
|
||||
label: Text(S.of(context).genericActionUpdateLabel),
|
||||
icon: widget.submitButtonConfig.icon,
|
||||
label: widget.submitButtonConfig.label,
|
||||
onPressed: _onSubmit,
|
||||
),
|
||||
body: FormBuilder(
|
||||
@@ -63,7 +67,7 @@ class _EditLabelPageState<T extends Label> extends State<EditLabelPage<T>> {
|
||||
errorText: _errors[Label.nameKey],
|
||||
),
|
||||
validator: FormBuilderValidators.required(),
|
||||
initialValue: widget.label.name,
|
||||
initialValue: widget.initialValue?.name,
|
||||
onChanged: (val) => setState(() => _errors = {}),
|
||||
),
|
||||
FormBuilderTextField(
|
||||
@@ -72,12 +76,13 @@ class _EditLabelPageState<T extends Label> extends State<EditLabelPage<T>> {
|
||||
labelText: S.of(context).labelMatchPropertyLabel,
|
||||
errorText: _errors[Label.matchKey],
|
||||
),
|
||||
initialValue: widget.label.match,
|
||||
initialValue: widget.initialValue?.match,
|
||||
onChanged: (val) => setState(() => _errors = {}),
|
||||
),
|
||||
FormBuilderDropdown<int?>(
|
||||
//TODO: Extract to own widget.
|
||||
name: Label.matchingAlgorithmKey,
|
||||
initialValue: widget.label.matchingAlgorithm?.value ??
|
||||
initialValue: widget.initialValue?.matchingAlgorithm?.value ??
|
||||
MatchingAlgorithm.allWords.value,
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context).labelMatchingAlgorithmPropertyLabel,
|
||||
@@ -95,7 +100,7 @@ class _EditLabelPageState<T extends Label> extends State<EditLabelPage<T>> {
|
||||
),
|
||||
FormBuilderCheckbox(
|
||||
name: Label.isInsensitiveKey,
|
||||
initialValue: widget.label.isInsensitive,
|
||||
initialValue: widget.initialValue?.isInsensitive,
|
||||
title: Text(S.of(context).labelIsInsensivitePropertyLabel),
|
||||
),
|
||||
...widget.additionalFields,
|
||||
@@ -105,46 +110,14 @@ class _EditLabelPageState<T extends Label> extends State<EditLabelPage<T>> {
|
||||
);
|
||||
}
|
||||
|
||||
void _onDelete() {
|
||||
if ((widget.label.documentCount ?? 0) > 0) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(S.of(context).editLabelPageConfirmDeletionDialogTitle),
|
||||
content: Text(
|
||||
S.of(context).editLabelPageDeletionDialogText,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(S.of(context).genericActionCancelLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
widget.onDelete(widget.label);
|
||||
},
|
||||
child: Text(
|
||||
S.of(context).genericActionDeleteLabel,
|
||||
style: TextStyle(color: Theme.of(context).errorColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
widget.onDelete(widget.label);
|
||||
}
|
||||
}
|
||||
|
||||
void _onSubmit() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
try {
|
||||
final mergedJson = {
|
||||
...widget.label.toJson(),
|
||||
...widget.initialValue?.toJson() ?? {},
|
||||
..._formKey.currentState!.value
|
||||
};
|
||||
await widget.onSubmit(widget.fromJson(mergedJson));
|
||||
await widget.submitButtonConfig.onSubmit(widget.fromJsonT(mergedJson));
|
||||
Navigator.pop(context);
|
||||
} on PaperlessValidationErrors catch (errorMessages) {
|
||||
setState(() => _errors = errorMessages);
|
||||
@@ -3,18 +3,16 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.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/widgets/offline_banner.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/bottom_navigation_bar.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
@@ -59,17 +57,17 @@ class _HomePageState extends State<HomePage> {
|
||||
MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
value: DocumentsCubit(getIt<PaperlessDocumentsApi>()),
|
||||
),
|
||||
],
|
||||
child: const DocumentsPage(),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: getIt<DocumentScannerCubit>(),
|
||||
value: DocumentScannerCubit(),
|
||||
child: const ScannerPage(),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
value: DocumentsCubit(getIt<PaperlessDocumentsApi>()),
|
||||
child: const LabelsPage(),
|
||||
),
|
||||
][_currentIndex],
|
||||
@@ -78,20 +76,17 @@ class _HomePageState extends State<HomePage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _initializeData(BuildContext context) {
|
||||
void _initializeData(BuildContext context) {
|
||||
try {
|
||||
return Future.wait([
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context).findAll();
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context).findAll();
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context).findAll();
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context).findAll();
|
||||
RepositoryProvider.of<SavedViewRepository>(context).findAll();
|
||||
BlocProvider.of<PaperlessServerInformationCubit>(context)
|
||||
.updateInformtion(),
|
||||
getIt<DocumentTypeCubit>().initialize(),
|
||||
getIt<CorrespondentCubit>().initialize(),
|
||||
getIt<TagCubit>().initialize(),
|
||||
getIt<StoragePathCubit>().initialize(),
|
||||
getIt<SavedViewCubit>().initialize(),
|
||||
]);
|
||||
.updateInformtion();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
return Future.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
@@ -188,11 +187,14 @@ class InfoDrawer extends StatelessWidget {
|
||||
onTap: () {
|
||||
try {
|
||||
BlocProvider.of<AuthenticationCubit>(context).logout();
|
||||
getIt<DocumentsCubit>().reset();
|
||||
getIt<CorrespondentCubit>().reset();
|
||||
getIt<DocumentTypeCubit>().reset();
|
||||
getIt<TagCubit>().reset();
|
||||
getIt<DocumentScannerCubit>().reset();
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context).clear();
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context)
|
||||
.clear();
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context)
|
||||
.clear();
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context)
|
||||
.clear();
|
||||
RepositoryProvider.of<SavedViewRepository>(context).clear();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
@@ -208,14 +210,15 @@ class InfoDrawer extends StatelessWidget {
|
||||
Future<void> _onOpenInbox(BuildContext context) async {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<InboxCubit>.value(
|
||||
value: getIt<InboxCubit>()..loadInbox(),
|
||||
builder: (_) => LabelRepositoriesProvider(
|
||||
child: BlocProvider(
|
||||
create: (context) => InboxCubit(
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
getIt<PaperlessDocumentsApi>(),
|
||||
),
|
||||
],
|
||||
child: const InboxPage(),
|
||||
),
|
||||
),
|
||||
maintainState: false,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
||||
|
||||
@injectable
|
||||
class InboxCubit extends Cubit<InboxState> {
|
||||
final PaperlessLabelsApi _labelApi;
|
||||
final LabelRepository<Tag> _tagsRepository;
|
||||
final PaperlessDocumentsApi _documentsApi;
|
||||
|
||||
InboxCubit(this._labelApi, this._documentsApi) : super(const InboxState());
|
||||
InboxCubit(this._tagsRepository, this._documentsApi)
|
||||
: super(const InboxState());
|
||||
|
||||
///
|
||||
/// Fetches inbox tag ids and loads the inbox items (documents).
|
||||
///
|
||||
Future<void> loadInbox() async {
|
||||
final inboxTags = await _labelApi.getTags().then(
|
||||
final inboxTags = await _tagsRepository.findAll().then(
|
||||
(tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!),
|
||||
);
|
||||
if (inboxTags.isEmpty) {
|
||||
|
||||
@@ -2,12 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||
|
||||
class InboxItem extends StatelessWidget {
|
||||
@@ -49,22 +48,20 @@ class InboxItem extends StatelessWidget {
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<DocumentDetailsCubit>(
|
||||
create: (context) => DocumentDetailsCubit(
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: DocumentDetailsCubit(
|
||||
getIt<PaperlessDocumentsApi>(),
|
||||
document,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: const DocumentDetailsPage(
|
||||
child: const LabelRepositoriesProvider(
|
||||
child: DocumentDetailsPage(
|
||||
allowEdit: false,
|
||||
isLabelClickable: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_cubit.dart';
|
||||
|
||||
class GlobalStateBlocProvider extends StatelessWidget {
|
||||
final List<BlocProvider> additionalProviders;
|
||||
final Widget child;
|
||||
const GlobalStateBlocProvider({
|
||||
super.key,
|
||||
this.additionalProviders = const [],
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: getIt<DocumentTypeCubit>()),
|
||||
BlocProvider.value(value: getIt<CorrespondentCubit>()),
|
||||
BlocProvider.value(value: getIt<TagCubit>()),
|
||||
BlocProvider.value(value: getIt<StoragePathCubit>()),
|
||||
BlocProvider.value(value: getIt<SavedViewCubit>()),
|
||||
...additionalProviders,
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,43 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.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/features/labels/bloc/label_state.dart';
|
||||
|
||||
abstract class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
||||
final PaperlessLabelsApi labelsApi;
|
||||
class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
||||
final LabelRepository<T> _repository;
|
||||
|
||||
LabelCubit(this.labelsApi) : super(LabelState.initial());
|
||||
late StreamSubscription _subscription;
|
||||
|
||||
@protected
|
||||
void loadFrom(Iterable<T> items) {
|
||||
emit(
|
||||
LabelState(
|
||||
isLoaded: true,
|
||||
labels: Map.fromIterable(items, key: (e) => (e as T).id!),
|
||||
),
|
||||
LabelCubit(this._repository) : super(LabelState.initial()) {
|
||||
_subscription = _repository.labels.listen(
|
||||
(update) => emit(LabelState(isLoaded: true, labels: update)),
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
/// Adds [item] to the current state. A new state is automatically pushed
|
||||
/// due to the subscription to the repository, which updates the state on
|
||||
/// operation.
|
||||
///
|
||||
Future<T> add(T item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await save(item);
|
||||
final newValues = {...state.labels};
|
||||
newValues.putIfAbsent(addedItem.id!, () => addedItem);
|
||||
emit(
|
||||
LabelState(
|
||||
isLoaded: true,
|
||||
labels: newValues,
|
||||
),
|
||||
);
|
||||
final addedItem = await _repository.create(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<T> replace(T item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await update(item);
|
||||
final updatedValues = {...state.labels};
|
||||
updatedValues[item.id!] = updatedItem;
|
||||
emit(
|
||||
LabelState(
|
||||
isLoaded: state.isLoaded,
|
||||
labels: updatedValues,
|
||||
),
|
||||
);
|
||||
final updatedItem = await _repository.update(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> remove(T item) async {
|
||||
assert(item.id != null);
|
||||
if (state.labels.containsKey(item.id)) {
|
||||
final deletedId = await delete(item);
|
||||
final updatedValues = {...state.labels}..remove(deletedId);
|
||||
emit(
|
||||
LabelState(isLoaded: true, labels: updatedValues),
|
||||
);
|
||||
await _repository.delete(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,14 +45,9 @@ abstract class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
||||
emit(LabelState(isLoaded: false, labels: {}));
|
||||
}
|
||||
|
||||
Future<void> initialize();
|
||||
|
||||
@protected
|
||||
Future<T> save(T item);
|
||||
|
||||
@protected
|
||||
Future<T> update(T item);
|
||||
|
||||
@protected
|
||||
Future<int> delete(T item);
|
||||
@override
|
||||
Future<void> close() {
|
||||
_subscription.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
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/features/labels/bloc/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>(
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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/features/labels/bloc/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>(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
39
lib/features/labels/bloc/providers/labels_bloc_provider.dart
Normal file
39
lib/features/labels/bloc/providers/labels_bloc_provider.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
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/features/labels/bloc/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>(
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
),
|
||||
BlocProvider<LabelCubit<Correspondent>>(
|
||||
create: (context) => LabelCubit<Correspondent>(
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
),
|
||||
),
|
||||
BlocProvider<LabelCubit<DocumentType>>(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
),
|
||||
BlocProvider<LabelCubit<Tag>>(
|
||||
create: (context) => LabelCubit<Tag>(
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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/features/labels/bloc/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>(
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
20
lib/features/labels/bloc/providers/tag_bloc_provider.dart
Normal file
20
lib/features/labels/bloc/providers/tag_bloc_provider.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
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/features/labels/bloc/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>(
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@lazySingleton
|
||||
class CorrespondentCubit extends LabelCubit<Correspondent> {
|
||||
CorrespondentCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
return labelsApi.getCorrespondents().then(loadFrom);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Correspondent> save(Correspondent item) =>
|
||||
labelsApi.saveCorrespondent(item);
|
||||
|
||||
@override
|
||||
Future<Correspondent> update(Correspondent item) =>
|
||||
labelsApi.updateCorrespondent(item);
|
||||
|
||||
@override
|
||||
Future<int> delete(Correspondent item) => labelsApi.deleteCorrespondent(item);
|
||||
}
|
||||
@@ -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/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/add_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class AddCorrespondentPage extends StatelessWidget {
|
||||
final String? initalValue;
|
||||
const AddCorrespondentPage({Key? key, this.initalValue}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AddLabelPage<Correspondent>(
|
||||
addLabelStr: S.of(context).addCorrespondentPageTitle,
|
||||
fromJson: Correspondent.fromJson,
|
||||
cubit: BlocProvider.of<CorrespondentCubit>(context),
|
||||
initialName: initalValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class EditCorrespondentPage extends StatelessWidget {
|
||||
final Correspondent correspondent;
|
||||
const EditCorrespondentPage({super.key, required this.correspondent});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EditLabelPage<Correspondent>(
|
||||
label: correspondent,
|
||||
onSubmit: BlocProvider.of<CorrespondentCubit>(context).replace,
|
||||
onDelete: (correspondent) => _onDelete(context, correspondent),
|
||||
fromJson: Correspondent.fromJson,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDelete(
|
||||
BuildContext context,
|
||||
Correspondent correspondent,
|
||||
) async {
|
||||
try {
|
||||
await BlocProvider.of<CorrespondentCubit>(context).remove(correspondent);
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (cubit.state.filter.correspondent.id == correspondent.id) {
|
||||
await cubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
correspondent: const CorrespondentQuery.unset(),
|
||||
),
|
||||
);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/correspondent_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
@@ -22,9 +23,11 @@ class CorrespondentWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AbsorbPointer(
|
||||
return CorrespondentBlocProvider(
|
||||
child: AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child: BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
|
||||
child:
|
||||
BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
onTap: () => _addCorrespondentToFilter(context),
|
||||
@@ -39,6 +42,7 @@ class CorrespondentWidget extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@lazySingleton
|
||||
class DocumentTypeCubit extends LabelCubit<DocumentType> {
|
||||
DocumentTypeCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
labelsApi.getDocumentTypes().then(loadFrom);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DocumentType> save(DocumentType item) =>
|
||||
labelsApi.saveDocumentType(item);
|
||||
|
||||
@override
|
||||
Future<DocumentType> update(DocumentType item) =>
|
||||
labelsApi.updateDocumentType(item);
|
||||
|
||||
@override
|
||||
Future<int> delete(DocumentType item) => labelsApi.deleteDocumentType(item);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/add_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class AddDocumentTypePage extends StatelessWidget {
|
||||
final String? initialName;
|
||||
const AddDocumentTypePage({Key? key, this.initialName}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AddLabelPage<DocumentType>(
|
||||
addLabelStr: S.of(context).addDocumentTypePageTitle,
|
||||
fromJson: DocumentType.fromJson,
|
||||
cubit: BlocProvider.of<DocumentTypeCubit>(context),
|
||||
initialName: initialName,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +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/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class EditDocumentTypePage extends StatelessWidget {
|
||||
final DocumentType documentType;
|
||||
const EditDocumentTypePage({super.key, required this.documentType});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EditLabelPage<DocumentType>(
|
||||
label: documentType,
|
||||
onSubmit: BlocProvider.of<DocumentTypeCubit>(context).replace,
|
||||
onDelete: (docType) => _onDelete(docType, context),
|
||||
fromJson: DocumentType.fromJson,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDelete(DocumentType docType, BuildContext context) async {
|
||||
try {
|
||||
await BlocProvider.of<DocumentTypeCubit>(context).remove(docType);
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (cubit.state.filter.documentType.id == docType.id) {
|
||||
cubit.updateFilter(
|
||||
filter: cubit.state.filter
|
||||
.copyWith(documentType: const DocumentTypeQuery.unset()),
|
||||
);
|
||||
}
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} finally {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.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/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
@@ -19,11 +20,16 @@ class DocumentTypeWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AbsorbPointer(
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
child: AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child: GestureDetector(
|
||||
onTap: () => _addDocumentTypeToFilter(context),
|
||||
child: BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
|
||||
child:
|
||||
BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return Text(
|
||||
state.labels[documentTypeId]?.toString() ?? "-",
|
||||
@@ -35,6 +41,7 @@ class DocumentTypeWidget extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@lazySingleton
|
||||
class StoragePathCubit extends LabelCubit<StoragePath> {
|
||||
StoragePathCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
return labelsApi.getStoragePaths().then(loadFrom);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StoragePath> save(StoragePath item) => labelsApi.saveStoragePath(item);
|
||||
|
||||
@override
|
||||
Future<StoragePath> update(StoragePath item) =>
|
||||
labelsApi.updateStoragePath(item);
|
||||
|
||||
@override
|
||||
Future<int> delete(StoragePath item) => labelsApi.deleteStoragePath(item);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/add_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class AddStoragePathPage extends StatelessWidget {
|
||||
final String? initalValue;
|
||||
const AddStoragePathPage({Key? key, this.initalValue}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AddLabelPage<StoragePath>(
|
||||
addLabelStr: S.of(context).addStoragePathPageTitle,
|
||||
fromJson: StoragePath.fromJson,
|
||||
cubit: BlocProvider.of<StoragePathCubit>(context),
|
||||
initialName: initalValue,
|
||||
additionalFields: const [
|
||||
StoragePathAutofillFormBuilderField(name: StoragePath.pathKey),
|
||||
SizedBox(height: 120.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class EditStoragePathPage extends StatelessWidget {
|
||||
final StoragePath storagePath;
|
||||
const EditStoragePathPage({super.key, required this.storagePath});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EditLabelPage<StoragePath>(
|
||||
label: storagePath,
|
||||
onSubmit: BlocProvider.of<StoragePathCubit>(context).replace,
|
||||
onDelete: (correspondent) => _onDelete(correspondent, context),
|
||||
fromJson: StoragePath.fromJson,
|
||||
additionalFields: [
|
||||
StoragePathAutofillFormBuilderField(
|
||||
name: StoragePath.pathKey,
|
||||
initialValue: storagePath.path,
|
||||
),
|
||||
const SizedBox(height: 120.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDelete(StoragePath path, BuildContext context) async {
|
||||
try {
|
||||
await BlocProvider.of<StoragePathCubit>(context).remove(path);
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
if (cubit.state.filter.storagePath.id == path.id) {
|
||||
cubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
storagePath: const StoragePathQuery.unset(),
|
||||
),
|
||||
);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import 'package:flutter/material.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/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class StoragePathWidget extends StatelessWidget {
|
||||
@@ -22,9 +23,13 @@ class StoragePathWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AbsorbPointer(
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<StoragePath>(
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
child: AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child: BlocBuilder<StoragePathCubit, LabelState<StoragePath>>(
|
||||
child: BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
onTap: () => _addStoragePathToFilter(context),
|
||||
@@ -39,6 +44,7 @@ class StoragePathWidget extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@lazySingleton
|
||||
class TagCubit extends LabelCubit<Tag> {
|
||||
TagCubit(super.metaDataService);
|
||||
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
return labelsApi.getTags().then(loadFrom);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Tag> save(Tag item) => labelsApi.saveTag(item);
|
||||
|
||||
@override
|
||||
Future<Tag> update(Tag item) => labelsApi.updateTag(item);
|
||||
|
||||
@override
|
||||
Future<int> delete(Tag item) => labelsApi.deleteTag(item);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/add_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:form_builder_extra_fields/form_builder_extra_fields.dart';
|
||||
|
||||
class AddTagPage extends StatelessWidget {
|
||||
final String? initialValue;
|
||||
const AddTagPage({Key? key, this.initialValue}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AddLabelPage<Tag>(
|
||||
addLabelStr: S.of(context).addTagPageTitle,
|
||||
fromJson: Tag.fromJson,
|
||||
cubit: BlocProvider.of<TagCubit>(context),
|
||||
initialName: initialValue,
|
||||
additionalFields: [
|
||||
FormBuilderColorPickerField(
|
||||
name: Tag.colorKey,
|
||||
valueTransformer: (color) => "#${color?.value.toRadixString(16)}",
|
||||
decoration: InputDecoration(
|
||||
label: Text(S.of(context).tagColorPropertyLabel),
|
||||
),
|
||||
colorPickerType: ColorPickerType.materialPicker,
|
||||
initialValue: null,
|
||||
),
|
||||
FormBuilderCheckbox(
|
||||
name: Tag.isInboxTagKey,
|
||||
title: Text(S.of(context).tagInboxTagPropertyLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_extra_fields/form_builder_extra_fields.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class EditTagPage extends StatelessWidget {
|
||||
final Tag tag;
|
||||
|
||||
const EditTagPage({super.key, required this.tag});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return EditLabelPage<Tag>(
|
||||
label: tag,
|
||||
onSubmit: (tag) async {
|
||||
await BlocProvider.of<TagCubit>(context).replace(tag);
|
||||
},
|
||||
onDelete: (tag) => _onDelete(tag, context),
|
||||
fromJson: Tag.fromJson,
|
||||
additionalFields: [
|
||||
FormBuilderColorPickerField(
|
||||
initialValue: tag.color,
|
||||
name: Tag.colorKey,
|
||||
decoration: InputDecoration(
|
||||
label: Text(S.of(context).tagColorPropertyLabel),
|
||||
),
|
||||
colorPickerType: ColorPickerType.blockPicker,
|
||||
),
|
||||
FormBuilderCheckbox(
|
||||
initialValue: tag.isInboxTag,
|
||||
name: Tag.isInboxTagKey,
|
||||
title: Text(S.of(context).tagInboxTagPropertyLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDelete(Tag tag, BuildContext context) async {
|
||||
try {
|
||||
await BlocProvider.of<TagCubit>(context).remove(tag);
|
||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
final currentFilter = cubit.state.filter;
|
||||
late DocumentFilter updatedFilter = currentFilter;
|
||||
if (currentFilter.tags is IdsTagsQuery) {
|
||||
if ((currentFilter.tags as IdsTagsQuery).includedIds.contains(tag.id)) {
|
||||
updatedFilter = currentFilter.copyWith(
|
||||
tags: (currentFilter.tags as IdsTagsQuery).withIdsRemoved(
|
||||
[tag.id!],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
cubit.updateFilter(filter: updatedFilter);
|
||||
Navigator.pop(context);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.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_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/pages/add_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/tag_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class TagFormField extends StatefulWidget {
|
||||
@@ -41,11 +44,13 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final state = BlocProvider.of<TagCubit>(context).state;
|
||||
_textEditingController = TextEditingController()
|
||||
..addListener(() {
|
||||
setState(() {
|
||||
_showCreationSuffixIcon = state.labels.values
|
||||
_showCreationSuffixIcon = BlocProvider.of<LabelCubit<Tag>>(context)
|
||||
.state
|
||||
.labels
|
||||
.values
|
||||
.where(
|
||||
(item) => item.name.toLowerCase().startsWith(
|
||||
_textEditingController.text.toLowerCase(),
|
||||
@@ -61,7 +66,8 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TagCubit, LabelState<Tag>>(
|
||||
return TagBlocProvider(
|
||||
child: BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
|
||||
builder: (context, tagState) {
|
||||
return FormBuilderField<TagsQuery>(
|
||||
builder: (field) {
|
||||
@@ -89,7 +95,9 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
.toList();
|
||||
if (field.value is IdsTagsQuery) {
|
||||
suggestions.removeWhere((element) =>
|
||||
(field.value as IdsTagsQuery).ids.contains(element));
|
||||
(field.value as IdsTagsQuery)
|
||||
.ids
|
||||
.contains(element));
|
||||
}
|
||||
if (widget.notAssignedSelectable &&
|
||||
field.value is! OnlyNotAssignedTagsQuery) {
|
||||
@@ -122,7 +130,8 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
title: Text(
|
||||
tag.name,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onBackground),
|
||||
color:
|
||||
Theme.of(context).colorScheme.onBackground),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -172,6 +181,7 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
name: widget.name,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -199,8 +209,8 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
void _onAddTag(BuildContext context, FormFieldState<TagsQuery> field) async {
|
||||
final Tag? tag = await Navigator.of(context).push<Tag>(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: BlocProvider.of<TagCubit>(context),
|
||||
builder: (_) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
child: AddTagPage(initialValue: _textEditingController.text),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/tag_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart';
|
||||
|
||||
class TagsWidget extends StatefulWidget {
|
||||
@@ -30,7 +31,8 @@ class TagsWidget extends StatefulWidget {
|
||||
class _TagsWidgetState extends State<TagsWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TagCubit, LabelState<Tag>>(
|
||||
return TagBlocProvider(
|
||||
child: BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
|
||||
builder: (context, state) {
|
||||
final children = widget.tagIds
|
||||
.where((id) => state.labels.containsKey(id))
|
||||
@@ -60,6 +62,7 @@ class _TagsWidgetState extends State<TagsWidget> {
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class AddLabelPage<T extends Label> extends StatefulWidget {
|
||||
final String? initialName;
|
||||
final String addLabelStr;
|
||||
final T Function(Map<String, dynamic> json) fromJson;
|
||||
final LabelCubit<T> cubit;
|
||||
final List<Widget> additionalFields;
|
||||
|
||||
const AddLabelPage({
|
||||
Key? key,
|
||||
this.initialName,
|
||||
required this.addLabelStr,
|
||||
required this.fromJson,
|
||||
required this.cubit,
|
||||
this.additionalFields = const [],
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AddLabelPage> createState() => _AddLabelPageState<T>();
|
||||
}
|
||||
|
||||
class _AddLabelPageState<T extends Label> extends State<AddLabelPage<T>> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
PaperlessValidationErrors _errors = {};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
appBar: AppBar(
|
||||
title: Text(widget.addLabelStr),
|
||||
),
|
||||
floatingActionButton: Visibility(
|
||||
visible: MediaQuery.of(context).viewInsets.bottom == 0,
|
||||
child: FloatingActionButton.extended(
|
||||
icon: const Icon(Icons.add),
|
||||
label: Text(S.of(context).genericActionCreateLabel),
|
||||
onPressed: _onSubmit,
|
||||
),
|
||||
),
|
||||
body: FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
FormBuilderTextField(
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
name: Label.nameKey,
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context).labelNamePropertyLabel,
|
||||
errorText: _errors[Label.nameKey],
|
||||
),
|
||||
initialValue: widget.initialName,
|
||||
validator: FormBuilderValidators.required(),
|
||||
onChanged: (val) => setState(() => _errors = {}),
|
||||
),
|
||||
FormBuilderTextField(
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
name: Label.matchKey,
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context).labelMatchPropertyLabel,
|
||||
),
|
||||
onChanged: (val) => setState(() => _errors = {}),
|
||||
),
|
||||
FormBuilderDropdown<int?>(
|
||||
name: Label.matchingAlgorithmKey,
|
||||
initialValue: MatchingAlgorithm.anyWord.value,
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context).labelMatchingAlgorithmPropertyLabel,
|
||||
errorText: _errors[Label.matchingAlgorithmKey],
|
||||
),
|
||||
onChanged: (val) => setState(() => _errors = {}),
|
||||
items: MatchingAlgorithm.values
|
||||
.map((algo) => DropdownMenuItem<int?>(
|
||||
child: Text(algo.name), //TODO: INTL
|
||||
value: algo.value))
|
||||
.toList(),
|
||||
),
|
||||
FormBuilderCheckbox(
|
||||
name: Label.isInsensitiveKey,
|
||||
initialValue: true,
|
||||
title: Text(S.of(context).labelIsInsensivitePropertyLabel),
|
||||
),
|
||||
...widget.additionalFields,
|
||||
].padded(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSubmit() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
try {
|
||||
final label = await widget.cubit
|
||||
.add(widget.fromJson(_formKey.currentState!.value));
|
||||
Navigator.pop(context, label);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} on PaperlessValidationErrors catch (json) {
|
||||
setState(() => _errors = json);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.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_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_type_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/home/view/widget/info_drawer.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/view/pages/add_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/view/pages/edit_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/view/pages/add_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/view/pages/edit_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/pages/add_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/pages/edit_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/pages/add_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/pages/edit_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
@@ -35,10 +30,6 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
BlocProvider.of<CorrespondentCubit>(context).initialize();
|
||||
BlocProvider.of<DocumentTypeCubit>(context).initialize();
|
||||
BlocProvider.of<TagCubit>(context).initialize();
|
||||
|
||||
_tabController = TabController(length: 4, vsync: this)
|
||||
..addListener(() => setState(() => _currentIndex = _tabController.index));
|
||||
}
|
||||
@@ -60,7 +51,12 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _onAddPressed,
|
||||
onPressed: [
|
||||
_openAddCorrespondentPage,
|
||||
_openAddDocumentTypePage,
|
||||
_openAddTagPage,
|
||||
_openAddStoragePathPage,
|
||||
][_currentIndex],
|
||||
icon: const Icon(Icons.add),
|
||||
)
|
||||
],
|
||||
@@ -104,40 +100,52 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
LabelTabView<Correspondent>(
|
||||
cubit: BlocProvider.of<CorrespondentCubit>(context),
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit(
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
),
|
||||
child: LabelTabView<Correspondent>(
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
correspondent: CorrespondentQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onOpenEditPage: _openEditCorrespondentPage,
|
||||
onEdit: _openEditCorrespondentPage,
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageCorrespondentEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageCorrespondentEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
onAddNew: _openAddCorrespondentPage,
|
||||
),
|
||||
LabelTabView<DocumentType>(
|
||||
cubit: BlocProvider.of<DocumentTypeCubit>(context),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
child: LabelTabView<DocumentType>(
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
documentType: DocumentTypeQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onOpenEditPage: _openEditDocumentTypePage,
|
||||
onEdit: _openEditDocumentTypePage,
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageDocumentTypeEmptyStateAddNewLabel,
|
||||
emptyStateDescription:
|
||||
S.of(context).labelsPageDocumentTypeEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageDocumentTypeEmptyStateDescriptionText,
|
||||
onAddNew: _openAddDocumentTypePage,
|
||||
),
|
||||
LabelTabView<Tag>(
|
||||
cubit: BlocProvider.of<TagCubit>(context),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<Tag>(
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
),
|
||||
child: LabelTabView<Tag>(
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
tags: IdsTagsQuery.fromIds([label.id!]),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onOpenEditPage: _openEditTagPage,
|
||||
onEdit: _openEditTagPage,
|
||||
leadingBuilder: (t) => CircleAvatar(
|
||||
backgroundColor: t.color,
|
||||
child: t.isInboxTag ?? false
|
||||
@@ -152,11 +160,15 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
S.of(context).labelsPageTagsEmptyStateAddNewLabel,
|
||||
emptyStateDescription:
|
||||
S.of(context).labelsPageTagsEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
onAddNew: _openAddTagPage,
|
||||
),
|
||||
LabelTabView<StoragePath>(
|
||||
cubit: BlocProvider.of<StoragePathCubit>(context),
|
||||
onOpenEditPage: _openEditStoragePathPage,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<StoragePath>(
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
child: LabelTabView<StoragePath>(
|
||||
onEdit: _openEditStoragePathPage,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
storagePath: StoragePathQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
@@ -164,9 +176,11 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
contentBuilder: (path) => Text(path.path ?? ""),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageStoragePathEmptyStateAddNewLabel,
|
||||
emptyStateDescription:
|
||||
S.of(context).labelsPageStoragePathEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageStoragePathEmptyStateDescriptionText,
|
||||
onAddNew: _openAddStoragePathPage,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -178,12 +192,8 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<DocumentsCubit>.value(
|
||||
value: BlocProvider.of<DocumentsCubit>(context),
|
||||
),
|
||||
],
|
||||
builder: (_) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
child: EditCorrespondentPage(correspondent: correspondent),
|
||||
),
|
||||
),
|
||||
@@ -194,12 +204,8 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<DocumentsCubit>.value(
|
||||
value: BlocProvider.of<DocumentsCubit>(context),
|
||||
),
|
||||
],
|
||||
builder: (_) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
child: EditDocumentTypePage(documentType: docType),
|
||||
),
|
||||
),
|
||||
@@ -210,12 +216,8 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<DocumentsCubit>.value(
|
||||
value: BlocProvider.of<DocumentsCubit>(context),
|
||||
),
|
||||
],
|
||||
builder: (_) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
child: EditTagPage(tag: tag),
|
||||
),
|
||||
),
|
||||
@@ -226,37 +228,61 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<DocumentsCubit>.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
builder: (_) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
child: EditStoragePathPage(
|
||||
storagePath: path,
|
||||
),
|
||||
],
|
||||
child: EditStoragePathPage(storagePath: path),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onAddPressed() {
|
||||
Navigator.push(context, MaterialPageRoute(
|
||||
builder: (context) {
|
||||
late final Widget page;
|
||||
switch (_currentIndex) {
|
||||
case 0:
|
||||
page = const AddCorrespondentPage();
|
||||
break;
|
||||
case 1:
|
||||
page = const AddDocumentTypePage();
|
||||
break;
|
||||
case 2:
|
||||
page = const AddTagPage();
|
||||
break;
|
||||
case 3:
|
||||
page = const AddStoragePathPage();
|
||||
void _openAddCorrespondentPage() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(context),
|
||||
child: const AddCorrespondentPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return GlobalStateBlocProvider(child: page);
|
||||
},
|
||||
));
|
||||
|
||||
void _openAddDocumentTypePage() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
child: const AddDocumentTypePage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _openAddTagPage() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
child: const AddTagPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _openAddStoragePathPage() {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
child: const AddStoragePathPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/view/pages/linked_documents_page.dart';
|
||||
@@ -46,12 +45,11 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<LinkedDocumentsCubit>.value(
|
||||
value: getIt<LinkedDocumentsCubit>()
|
||||
..initialize(filter)),
|
||||
],
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: LinkedDocumentsCubit(
|
||||
getIt<PaperlessDocumentsApi>(),
|
||||
filter,
|
||||
),
|
||||
child: const LinkedDocumentsPage(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
@@ -9,10 +10,9 @@ import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
|
||||
class LabelTabView<T extends Label> extends StatelessWidget {
|
||||
final LabelCubit<T> cubit;
|
||||
final DocumentFilter Function(Label) filterBuilder;
|
||||
final void Function(T) onOpenEditPage;
|
||||
final void Function() onOpenAddNewPage;
|
||||
final void Function(T) onEdit;
|
||||
final void Function() onAddNew;
|
||||
|
||||
/// Displayed as the subtitle of the [ListTile]
|
||||
final Widget Function(T)? contentBuilder;
|
||||
@@ -26,13 +26,12 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
||||
|
||||
const LabelTabView({
|
||||
super.key,
|
||||
required this.cubit,
|
||||
required this.filterBuilder,
|
||||
this.contentBuilder,
|
||||
this.leadingBuilder,
|
||||
required this.onOpenEditPage,
|
||||
required this.onEdit,
|
||||
required this.emptyStateDescription,
|
||||
required this.onOpenAddNewPage,
|
||||
required this.onAddNew,
|
||||
required this.emptyStateActionButtonLabel,
|
||||
});
|
||||
|
||||
@@ -43,10 +42,7 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
||||
if (state == ConnectivityState.notConnected) {
|
||||
return const OfflineWidget();
|
||||
}
|
||||
return RefreshIndicator(
|
||||
onRefresh: cubit.initialize,
|
||||
child: BlocBuilder<Cubit<LabelState<T>>, LabelState<T>>(
|
||||
bloc: cubit,
|
||||
return BlocBuilder<LabelCubit<T>, LabelState<T>>(
|
||||
builder: (context, state) {
|
||||
final labels = state.labels.values.toList()..sort();
|
||||
if (labels.isEmpty) {
|
||||
@@ -59,7 +55,7 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: onOpenAddNewPage,
|
||||
onPressed: onAddNew,
|
||||
child: Text(emptyStateActionButtonLabel),
|
||||
)
|
||||
].padded(),
|
||||
@@ -72,7 +68,7 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
||||
name: l.name,
|
||||
content:
|
||||
contentBuilder?.call(l) ?? Text(l.match ?? '-'),
|
||||
onOpenEditPage: onOpenEditPage,
|
||||
onOpenEditPage: onEdit,
|
||||
filterBuilder: filterBuilder,
|
||||
leading: leadingBuilder?.call(l),
|
||||
label: l,
|
||||
@@ -80,7 +76,6 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/state/linked_documents_state.dart';
|
||||
|
||||
@injectable
|
||||
class LinkedDocumentsCubit extends Cubit<LinkedDocumentsState> {
|
||||
final PaperlessDocumentsApi _api;
|
||||
|
||||
LinkedDocumentsCubit(this._api) : super(LinkedDocumentsState());
|
||||
LinkedDocumentsCubit(this._api, DocumentFilter filter)
|
||||
: super(LinkedDocumentsState(filter: filter)) {
|
||||
_initialize();
|
||||
}
|
||||
|
||||
Future<void> initialize(DocumentFilter filter) async {
|
||||
Future<void> _initialize() async {
|
||||
final documents = await _api.find(
|
||||
filter.copyWith(
|
||||
state.filter.copyWith(
|
||||
pageSize: 100,
|
||||
),
|
||||
);
|
||||
emit(LinkedDocumentsState(
|
||||
isLoaded: true,
|
||||
documents: documents,
|
||||
filter: filter,
|
||||
filter: state.filter,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
class LinkedDocumentsState {
|
||||
final bool isLoaded;
|
||||
final PagedSearchResult<DocumentModel>? documents;
|
||||
final DocumentFilter? filter;
|
||||
final DocumentFilter filter;
|
||||
|
||||
LinkedDocumentsState({
|
||||
this.filter,
|
||||
required this.filter,
|
||||
this.isLoaded = false,
|
||||
this.documents,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/state/linked_documents_state.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
@@ -64,15 +63,12 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (ctxt) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
builder: (context) =>
|
||||
BlocProvider<DocumentDetailsCubit>.value(
|
||||
value: DocumentDetailsCubit(
|
||||
getIt<PaperlessDocumentsApi>(),
|
||||
document,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: const DocumentDetailsPage(
|
||||
isLabelClickable: false,
|
||||
allowEdit: false,
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart';
|
||||
|
||||
@prod
|
||||
@test
|
||||
@lazySingleton
|
||||
class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
final PaperlessSavedViewsApi _api;
|
||||
SavedViewCubit(this._api) : super(SavedViewState(value: {}));
|
||||
final SavedViewRepository _repository;
|
||||
StreamSubscription? _subscription;
|
||||
|
||||
SavedViewCubit(this._repository) : super(SavedViewState(value: {})) {
|
||||
_subscription = _repository.savedViews.listen(
|
||||
(savedViews) => emit(state.copyWith(value: savedViews)),
|
||||
);
|
||||
}
|
||||
|
||||
void selectView(SavedView? view) {
|
||||
emit(SavedViewState(value: state.value, selectedSavedViewId: view?.id));
|
||||
}
|
||||
|
||||
Future<SavedView> add(SavedView view) async {
|
||||
final savedView = await _api.save(view);
|
||||
final savedView = await _repository.create(view);
|
||||
emit(
|
||||
SavedViewState(
|
||||
value: {...state.value, savedView.id!: savedView},
|
||||
@@ -26,22 +32,15 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
}
|
||||
|
||||
Future<int> remove(SavedView view) async {
|
||||
final id = await _api.delete(view);
|
||||
final newValue = {...state.value};
|
||||
newValue.removeWhere((key, value) => key == id);
|
||||
emit(
|
||||
SavedViewState(
|
||||
value: newValue,
|
||||
selectedSavedViewId: view.id == state.selectedSavedViewId
|
||||
? null
|
||||
: state.selectedSavedViewId,
|
||||
),
|
||||
);
|
||||
final id = await _repository.delete(view);
|
||||
if (state.selectedSavedViewId == id) {
|
||||
resetSelection();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
final views = await _api.getAll();
|
||||
final views = await _repository.findAll();
|
||||
final values = {for (var element in views) element.id!: element};
|
||||
emit(SavedViewState(value: values));
|
||||
}
|
||||
@@ -49,4 +48,10 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
void resetSelection() {
|
||||
emit(SavedViewState(value: state.value));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_subscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
@@ -15,4 +15,17 @@ class SavedViewState with EquatableMixin {
|
||||
value,
|
||||
selectedSavedViewId,
|
||||
];
|
||||
|
||||
SavedViewState copyWith({
|
||||
Map<int, SavedView>? value,
|
||||
int? selectedSavedViewId,
|
||||
bool overwriteSelectedSavedViewId = false,
|
||||
}) {
|
||||
return SavedViewState(
|
||||
value: value ?? this.value,
|
||||
selectedSavedViewId: overwriteSelectedSavedViewId
|
||||
? selectedSavedViewId
|
||||
: this.selectedSavedViewId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_state.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
@@ -94,18 +94,11 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
|
||||
void _onSelected(
|
||||
bool isSelected, BuildContext context, SavedView view) async {
|
||||
try {
|
||||
if (isSelected) {
|
||||
BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateFilter(filter: view.toDocumentFilter());
|
||||
BlocProvider.of<SavedViewCubit>(context).selectView(view);
|
||||
} else {
|
||||
BlocProvider.of<DocumentsCubit>(context).updateFilter();
|
||||
BlocProvider.of<SavedViewCubit>(context).selectView(null);
|
||||
}
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
void _onDelete(BuildContext context, SavedView view) async {
|
||||
|
||||
@@ -9,13 +9,8 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||
|
||||
@injectable
|
||||
class DocumentScannerCubit extends Cubit<List<File>> {
|
||||
final PaperlessDocumentsApi _api;
|
||||
|
||||
static List<File> initialState = [];
|
||||
|
||||
DocumentScannerCubit(this._api) : super(initialState);
|
||||
DocumentScannerCubit() : super(const []);
|
||||
|
||||
void addScan(File file) => emit([...state, file]);
|
||||
|
||||
@@ -39,41 +34,9 @@ class DocumentScannerCubit extends Cubit<List<File>> {
|
||||
}
|
||||
}
|
||||
imageCache.clear();
|
||||
emit(initialState);
|
||||
emit([]);
|
||||
} catch (_) {
|
||||
throw const PaperlessServerException(ErrorCode.scanRemoveFailed);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> uploadDocument(
|
||||
Uint8List bytes,
|
||||
String fileName, {
|
||||
required String title,
|
||||
required void Function(DocumentModel document)? onConsumptionFinished,
|
||||
int? documentType,
|
||||
int? correspondent,
|
||||
Iterable<int> tags = const [],
|
||||
DateTime? createdAt,
|
||||
}) async {
|
||||
final auth = getIt<AuthenticationCubit>().state.authentication;
|
||||
if (auth == null) {
|
||||
throw const PaperlessServerException(ErrorCode.notAuthenticated);
|
||||
}
|
||||
await _api.create(
|
||||
bytes,
|
||||
filename: fileName,
|
||||
title: title,
|
||||
documentType: documentType,
|
||||
correspondent: correspondent,
|
||||
tags: tags,
|
||||
createdAt: createdAt,
|
||||
authToken: auth.token!,
|
||||
serverUrl: auth.serverUrl,
|
||||
);
|
||||
if (onConsumptionFinished != null) {
|
||||
_api
|
||||
.waitForConsumptionFinished(fileName, title)
|
||||
.then((value) => onConsumptionFinished(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/view/pages/add_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/view/pages/add_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class DocumentUploadPage extends StatefulWidget {
|
||||
final Uint8List fileBytes;
|
||||
final String? title;
|
||||
final String? filename;
|
||||
final void Function()? afterUpload;
|
||||
final void Function(DocumentModel)? onSuccessfullyConsumed;
|
||||
|
||||
const DocumentUploadPage({
|
||||
Key? key,
|
||||
required this.fileBytes,
|
||||
this.afterUpload,
|
||||
this.title,
|
||||
this.filename,
|
||||
this.onSuccessfullyConsumed,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DocumentUploadPage> createState() => _DocumentUploadPageState();
|
||||
}
|
||||
|
||||
class _DocumentUploadPageState extends State<DocumentUploadPage> {
|
||||
static const fkFileName = "filename";
|
||||
static final fileNameDateFormat = DateFormat("yyyy_MM_ddTHH_mm_ss");
|
||||
|
||||
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
|
||||
|
||||
PaperlessValidationErrors _errors = {};
|
||||
bool _isUploadLoading = false;
|
||||
late bool _syncTitleAndFilename;
|
||||
final _now = DateTime.now();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_syncTitleAndFilename = widget.filename == null && widget.title == null;
|
||||
initializeDateFormatting();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).documentsUploadPageTitle),
|
||||
bottom: _isUploadLoading
|
||||
? const PreferredSize(
|
||||
child: LinearProgressIndicator(),
|
||||
preferredSize: Size.fromHeight(4.0))
|
||||
: null,
|
||||
),
|
||||
floatingActionButton: Visibility(
|
||||
visible: MediaQuery.of(context).viewInsets.bottom == 0,
|
||||
child: FloatingActionButton.extended(
|
||||
onPressed: _onSubmit,
|
||||
label: Text(S.of(context).genericActionUploadLabel),
|
||||
icon: const Icon(Icons.upload),
|
||||
),
|
||||
),
|
||||
body: FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
FormBuilderTextField(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
name: DocumentModel.titleKey,
|
||||
initialValue:
|
||||
widget.title ?? "scan_${fileNameDateFormat.format(_now)}",
|
||||
validator: FormBuilderValidators.required(),
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context).documentTitlePropertyLabel,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
_formKey.currentState?.fields[DocumentModel.titleKey]
|
||||
?.didChange("");
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]?.didChange("");
|
||||
}
|
||||
},
|
||||
),
|
||||
errorText: _errors[DocumentModel.titleKey],
|
||||
),
|
||||
onChanged: (value) {
|
||||
final String transformedValue = _formatFilename(value ?? '');
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]
|
||||
?.didChange(transformedValue);
|
||||
}
|
||||
},
|
||||
),
|
||||
FormBuilderTextField(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
readOnly: _syncTitleAndFilename,
|
||||
enabled: !_syncTitleAndFilename,
|
||||
name: fkFileName,
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context).documentUploadFileNameLabel,
|
||||
suffixText: ".pdf",
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () =>
|
||||
_formKey.currentState?.fields[fkFileName]?.didChange(''),
|
||||
),
|
||||
),
|
||||
initialValue:
|
||||
widget.filename ?? "scan_${fileNameDateFormat.format(_now)}",
|
||||
),
|
||||
SwitchListTile(
|
||||
value: _syncTitleAndFilename,
|
||||
onChanged: (value) {
|
||||
setState(
|
||||
() => _syncTitleAndFilename = value,
|
||||
);
|
||||
if (_syncTitleAndFilename) {
|
||||
final String transformedValue = _formatFilename(_formKey
|
||||
.currentState
|
||||
?.fields[DocumentModel.titleKey]
|
||||
?.value as String);
|
||||
if (_syncTitleAndFilename) {
|
||||
_formKey.currentState?.fields[fkFileName]
|
||||
?.didChange(transformedValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
title: Text(S
|
||||
.of(context)
|
||||
.documentUploadPageSynchronizeTitleAndFilenameLabel), //TODO: INTL
|
||||
),
|
||||
FormBuilderDateTimePicker(
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
format: DateFormat("dd. MMMM yyyy"), //TODO: INTL
|
||||
inputType: InputType.date,
|
||||
name: DocumentModel.createdKey,
|
||||
initialValue: null,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.calendar_month_outlined),
|
||||
labelText: S.of(context).documentCreatedPropertyLabel + " *",
|
||||
),
|
||||
),
|
||||
BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
|
||||
bloc: getIt<DocumentTypeCubit>(), //TODO: Use provider
|
||||
builder: (context, state) {
|
||||
return LabelFormField<DocumentType, DocumentTypeQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentTypeCubit>(context),
|
||||
child: AddDocumentTypePage(initialName: initialValue),
|
||||
),
|
||||
label: S.of(context).documentDocumentTypePropertyLabel + " *",
|
||||
name: DocumentModel.documentTypeKey,
|
||||
state: state.labels,
|
||||
queryParameterIdBuilder: DocumentTypeQuery.fromId,
|
||||
queryParameterNotAssignedBuilder:
|
||||
DocumentTypeQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
);
|
||||
},
|
||||
),
|
||||
BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
|
||||
bloc: getIt<CorrespondentCubit>(), //TODO: Use provider
|
||||
builder: (context, state) {
|
||||
return LabelFormField<Correspondent, CorrespondentQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<CorrespondentCubit>(context),
|
||||
child: AddCorrespondentPage(initalValue: initialValue),
|
||||
),
|
||||
label:
|
||||
S.of(context).documentCorrespondentPropertyLabel + " *",
|
||||
name: DocumentModel.correspondentKey,
|
||||
state: state.labels,
|
||||
queryParameterIdBuilder: CorrespondentQuery.fromId,
|
||||
queryParameterNotAssignedBuilder:
|
||||
CorrespondentQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
);
|
||||
},
|
||||
),
|
||||
const TagFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
notAssignedSelectable: false,
|
||||
anyAssignedSelectable: false,
|
||||
excludeAllowed: false,
|
||||
//Label: "Tags" + " *",
|
||||
),
|
||||
Text(
|
||||
"* " + S.of(context).uploadPageAutomaticallInferredFieldsHintText,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
].padded(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSubmit() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final cubit = BlocProvider.of<DocumentScannerCubit>(context);
|
||||
try {
|
||||
setState(() => _isUploadLoading = true);
|
||||
|
||||
final fv = _formKey.currentState!.value;
|
||||
|
||||
final createdAt = fv[DocumentModel.createdKey] as DateTime?;
|
||||
final title = fv[DocumentModel.titleKey] as String;
|
||||
final docType = fv[DocumentModel.documentTypeKey] as IdQueryParameter;
|
||||
final tags = fv[DocumentModel.tagsKey] as IdsTagsQuery;
|
||||
final correspondent =
|
||||
fv[DocumentModel.correspondentKey] as IdQueryParameter;
|
||||
|
||||
await cubit.uploadDocument(
|
||||
widget.fileBytes,
|
||||
_padWithPdfExtension(_formKey.currentState?.value[fkFileName]),
|
||||
onConsumptionFinished: widget.onSuccessfullyConsumed,
|
||||
title: title,
|
||||
documentType: docType.id,
|
||||
correspondent: correspondent.id,
|
||||
tags: tags.ids,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
|
||||
cubit.reset(); //TODO: Access via provider
|
||||
showSnackBar(context, S.of(context).documentUploadSuccessText);
|
||||
Navigator.pop(context);
|
||||
widget.afterUpload?.call();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} on PaperlessValidationErrors catch (PaperlessServerExceptions) {
|
||||
setState(() => _errors = PaperlessServerExceptions);
|
||||
} catch (unknownError, stackTrace) {
|
||||
showErrorMessage(
|
||||
context, const PaperlessServerException.unknown(), stackTrace);
|
||||
} finally {
|
||||
setState(() {
|
||||
_isUploadLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _padWithPdfExtension(String source) {
|
||||
return source.endsWith(".pdf") ? source : '$source.pdf';
|
||||
}
|
||||
|
||||
String _formatFilename(String source) {
|
||||
return source.replaceAll(RegExp(r"[\W_]"), "_");
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,17 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart';
|
||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/features/scan/view/document_upload_page.dart';
|
||||
import 'package:paperless_mobile/features/scan/view/widgets/grid_image_item_widget.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
@@ -124,17 +128,29 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
final bytes = await doc.save();
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<DocumentScannerCubit>.value(
|
||||
value: BlocProvider.of<DocumentScannerCubit>(context),
|
||||
builder: (_) => LabelRepositoriesProvider(
|
||||
child: BlocProvider(
|
||||
create: (context) => DocumentUploadCubit(
|
||||
localVault: getIt<LocalVault>(),
|
||||
documentApi: getIt<PaperlessDocumentsApi>(),
|
||||
correspondentRepository:
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
],
|
||||
child: DocumentUploadPage(
|
||||
documentTypeRepository:
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
tagRepository: RepositoryProvider.of<LabelRepository<Tag>>(
|
||||
context,
|
||||
),
|
||||
),
|
||||
child: DocumentUploadPreparationPage(
|
||||
fileBytes: bytes,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -243,15 +259,27 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
}
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<DocumentScannerCubit>.value(
|
||||
value: BlocProvider.of<DocumentScannerCubit>(context),
|
||||
builder: (_) => LabelRepositoriesProvider(
|
||||
child: BlocProvider(
|
||||
create: (context) => DocumentUploadCubit(
|
||||
localVault: getIt<LocalVault>(),
|
||||
documentApi: getIt<PaperlessDocumentsApi>(),
|
||||
correspondentRepository:
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
],
|
||||
child: DocumentUploadPage(
|
||||
filename: filename,
|
||||
documentTypeRepository:
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
tagRepository: RepositoryProvider.of<LabelRepository<Tag>>(
|
||||
context,
|
||||
),
|
||||
),
|
||||
child: DocumentUploadPreparationPage(
|
||||
fileBytes: fileBytes,
|
||||
filename: filename,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,26 +13,31 @@ import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/bloc_changes_observer.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
import 'package:paperless_mobile/core/global/http_self_signed_certificate_override.dart';
|
||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.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/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/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||
import 'package:paperless_mobile/features/home/view/home_page.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/features/scan/view/document_upload_page.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
|
||||
void main() async {
|
||||
Bloc.observer = BlocChangesObserver();
|
||||
@@ -52,7 +57,30 @@ void main() async {
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
|
||||
runApp(const PaperlessMobileEntrypoint());
|
||||
// Create repositories
|
||||
final LabelRepository<Tag> tagRepository =
|
||||
TagRepositoryImpl(getIt<PaperlessLabelsApi>());
|
||||
final LabelRepository<Correspondent> correspondentRepository =
|
||||
CorrespondentRepositoryImpl(getIt<PaperlessLabelsApi>());
|
||||
final LabelRepository<DocumentType> documentTypeRepository =
|
||||
DocumentTypeRepositoryImpl(getIt<PaperlessLabelsApi>());
|
||||
final LabelRepository<StoragePath> storagePathRepository =
|
||||
StoragePathRepositoryImpl(getIt<PaperlessLabelsApi>());
|
||||
final SavedViewRepository savedViewRepository =
|
||||
SavedViewRepositoryImpl(getIt<PaperlessSavedViewsApi>());
|
||||
|
||||
runApp(
|
||||
MultiRepositoryProvider(
|
||||
providers: [
|
||||
RepositoryProvider.value(value: tagRepository),
|
||||
RepositoryProvider.value(value: correspondentRepository),
|
||||
RepositoryProvider.value(value: documentTypeRepository),
|
||||
RepositoryProvider.value(value: storagePathRepository),
|
||||
RepositoryProvider.value(value: savedViewRepository),
|
||||
],
|
||||
child: const PaperlessMobileEntrypoint(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||
@@ -71,9 +99,6 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
BlocProvider<ConnectivityCubit>.value(
|
||||
value: getIt<ConnectivityCubit>(),
|
||||
),
|
||||
BlocProvider<AuthenticationCubit>.value(
|
||||
value: getIt<AuthenticationCubit>(),
|
||||
),
|
||||
BlocProvider<PaperlessServerInformationCubit>.value(
|
||||
value: getIt<PaperlessServerInformationCubit>(),
|
||||
),
|
||||
@@ -126,7 +151,10 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
FormBuilderLocalizations.delegate,
|
||||
],
|
||||
home: const AuthenticationWrapper(),
|
||||
home: BlocProvider<AuthenticationCubit>.value(
|
||||
value: getIt<AuthenticationCubit>(),
|
||||
child: const AuthenticationWrapper(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -177,21 +205,21 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
}
|
||||
final filename = extractFilenameFromPath(file.path);
|
||||
final bytes = File(file.path).readAsBytesSync();
|
||||
Navigator.push(
|
||||
final success = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider.value(value: getIt<DocumentScannerCubit>()),
|
||||
],
|
||||
child: DocumentUploadPage(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: getIt<DocumentScannerCubit>(),
|
||||
child: DocumentUploadPreparationPage(
|
||||
fileBytes: bytes,
|
||||
afterUpload: SystemNavigator.pop,
|
||||
filename: filename,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (success) {
|
||||
SystemNavigator.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -232,17 +260,12 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
},
|
||||
builder: (context, authentication) {
|
||||
if (authentication.isAuthenticated) {
|
||||
return GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||
],
|
||||
child: const HomePage(),
|
||||
);
|
||||
return const HomePage();
|
||||
} else {
|
||||
if (authentication.wasLoginStored &&
|
||||
!(authentication.wasLocalAuthenticationSuccessful ?? false)) {
|
||||
return BiometricAuthenticationPage();
|
||||
}
|
||||
// if (authentication.wasLoginStored &&
|
||||
// !(authentication.wasLocalAuthenticationSuccessful ?? false)) {
|
||||
// return const BiometricAuthenticationPage();
|
||||
// }
|
||||
return const LoginPage();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -14,25 +14,25 @@ import 'package:paperless_api/src/models/labels/tag_model.dart';
|
||||
///
|
||||
abstract class PaperlessLabelsApi {
|
||||
Future<Correspondent?> getCorrespondent(int id);
|
||||
Future<List<Correspondent>> getCorrespondents();
|
||||
Future<List<Correspondent>> getCorrespondents([Iterable<int>? ids]);
|
||||
Future<Correspondent> saveCorrespondent(Correspondent correspondent);
|
||||
Future<Correspondent> updateCorrespondent(Correspondent correspondent);
|
||||
Future<int> deleteCorrespondent(Correspondent correspondent);
|
||||
|
||||
Future<Tag?> getTag(int id);
|
||||
Future<List<Tag>> getTags({List<int>? ids});
|
||||
Future<List<Tag>> getTags([Iterable<int>? ids]);
|
||||
Future<Tag> saveTag(Tag tag);
|
||||
Future<Tag> updateTag(Tag tag);
|
||||
Future<int> deleteTag(Tag tag);
|
||||
|
||||
Future<DocumentType?> getDocumentType(int id);
|
||||
Future<List<DocumentType>> getDocumentTypes();
|
||||
Future<List<DocumentType>> getDocumentTypes([Iterable<int>? ids]);
|
||||
Future<DocumentType> saveDocumentType(DocumentType type);
|
||||
Future<DocumentType> updateDocumentType(DocumentType documentType);
|
||||
Future<int> deleteDocumentType(DocumentType documentType);
|
||||
|
||||
Future<StoragePath?> getStoragePath(int id);
|
||||
Future<List<StoragePath>> getStoragePaths();
|
||||
Future<List<StoragePath>> getStoragePaths([Iterable<int>? ids]);
|
||||
Future<StoragePath> saveStoragePath(StoragePath path);
|
||||
Future<StoragePath> updateStoragePath(StoragePath path);
|
||||
Future<int> deleteStoragePath(StoragePath path);
|
||||
|
||||
@@ -35,7 +35,7 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Tag>> getTags({List<int>? ids}) async {
|
||||
Future<List<Tag>> getTags([Iterable<int>? ids]) async {
|
||||
final results = await getCollection(
|
||||
"/api/tags/?page=1&page_size=100000",
|
||||
Tag.fromJson,
|
||||
@@ -59,23 +59,31 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Correspondent>> getCorrespondents() {
|
||||
return getCollection(
|
||||
Future<List<Correspondent>> getCorrespondents([Iterable<int>? ids]) async {
|
||||
final results = await getCollection(
|
||||
"/api/correspondents/?page=1&page_size=100000",
|
||||
Correspondent.fromJson,
|
||||
ErrorCode.correspondentLoadFailed,
|
||||
client: client,
|
||||
);
|
||||
|
||||
return results
|
||||
.where((element) => ids?.contains(element.id) ?? true)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<DocumentType>> getDocumentTypes() {
|
||||
return getCollection(
|
||||
Future<List<DocumentType>> getDocumentTypes([Iterable<int>? ids]) async {
|
||||
final results = await getCollection(
|
||||
"/api/document_types/?page=1&page_size=100000",
|
||||
DocumentType.fromJson,
|
||||
ErrorCode.documentTypeLoadFailed,
|
||||
client: client,
|
||||
);
|
||||
|
||||
return results
|
||||
.where((element) => ids?.contains(element.id) ?? true)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -261,13 +269,17 @@ class PaperlessLabelApiImpl implements PaperlessLabelsApi {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<StoragePath>> getStoragePaths() {
|
||||
return getCollection(
|
||||
Future<List<StoragePath>> getStoragePaths([Iterable<int>? ids]) async {
|
||||
final results = await getCollection(
|
||||
"/api/storage_paths/?page=1&page_size=100000",
|
||||
StoragePath.fromJson,
|
||||
ErrorCode.storagePathLoadFailed,
|
||||
client: client,
|
||||
);
|
||||
|
||||
return results
|
||||
.where((element) => ids?.contains(element.id) ?? true)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:paperless_api/src/models/saved_view_model.dart';
|
||||
|
||||
abstract class PaperlessSavedViewsApi {
|
||||
Future<List<SavedView>> getAll();
|
||||
Future<SavedView> find(int id);
|
||||
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]);
|
||||
|
||||
Future<SavedView> save(SavedView view);
|
||||
Future<int> delete(SavedView view);
|
||||
|
||||
@@ -14,13 +14,15 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
||||
PaperlessSavedViewsApiImpl(this.client);
|
||||
|
||||
@override
|
||||
Future<List<SavedView>> getAll() {
|
||||
return getCollection(
|
||||
Future<Iterable<SavedView>> findAll([Iterable<int>? ids]) async {
|
||||
final result = await getCollection(
|
||||
"/api/saved_views/",
|
||||
SavedView.fromJson,
|
||||
ErrorCode.loadSavedViewsError,
|
||||
client: client,
|
||||
);
|
||||
|
||||
return result.where((view) => ids?.contains(view.id!) ?? true);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -51,4 +53,14 @@ class PaperlessSavedViewsApiImpl implements PaperlessSavedViewsApi {
|
||||
httpStatusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SavedView> find(int id) {
|
||||
return getSingleResult(
|
||||
"/api/saved_views/$id/",
|
||||
SavedView.fromJson,
|
||||
ErrorCode.loadSavedViewsError,
|
||||
client: client,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1147,12 +1147,12 @@ packages:
|
||||
source: hosted
|
||||
version: "1.4.5"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: rxdart
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.27.4"
|
||||
version: "0.27.7"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -81,6 +81,7 @@ dependencies:
|
||||
paperless_api:
|
||||
path: packages/paperless_api
|
||||
hive: ^2.2.3
|
||||
rxdart: ^0.27.7
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
|
||||
Reference in New Issue
Block a user