mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-08 20:07:46 -06:00
WIP - more decoupling of blocs
This commit is contained in:
@@ -34,4 +34,10 @@ class AuthenticationInterceptor implements InterceptorContract {
|
||||
Future<BaseResponse> interceptResponse(
|
||||
{required BaseResponse response}) async =>
|
||||
response;
|
||||
|
||||
@override
|
||||
Future<bool> shouldInterceptRequest() async => true;
|
||||
|
||||
@override
|
||||
Future<bool> shouldInterceptResponse() async => true;
|
||||
}
|
||||
|
||||
@@ -25,4 +25,10 @@ class BaseUrlInterceptor implements InterceptorContract {
|
||||
Future<BaseResponse> interceptResponse(
|
||||
{required BaseResponse response}) async =>
|
||||
response;
|
||||
|
||||
@override
|
||||
Future<bool> shouldInterceptRequest() async => true;
|
||||
|
||||
@override
|
||||
Future<bool> shouldInterceptResponse() async => true;
|
||||
}
|
||||
|
||||
@@ -25,4 +25,10 @@ class LanguageHeaderInterceptor implements InterceptorContract {
|
||||
Future<BaseResponse> interceptResponse(
|
||||
{required BaseResponse response}) async =>
|
||||
response;
|
||||
|
||||
@override
|
||||
Future<bool> shouldInterceptRequest() async => true;
|
||||
|
||||
@override
|
||||
Future<bool> shouldInterceptResponse() async => true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http_interceptor/http/http.dart';
|
||||
import 'package:http_interceptor/http_interceptor.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
const interceptedRoutes = ['thumb/'];
|
||||
@@ -33,4 +32,10 @@ class ResponseConversionInterceptor implements InterceptorContract {
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> shouldInterceptRequest() async => true;
|
||||
|
||||
@override
|
||||
Future<bool> shouldInterceptResponse() async => true;
|
||||
}
|
||||
|
||||
@@ -64,4 +64,7 @@ class CorrespondentRepositoryImpl implements LabelRepository<Correspondent> {
|
||||
void clear() {
|
||||
_subject.add(const {});
|
||||
}
|
||||
|
||||
@override
|
||||
Map<int, Correspondent> get current => _subject.value;
|
||||
}
|
||||
|
||||
@@ -63,4 +63,7 @@ class DocumentTypeRepositoryImpl implements LabelRepository<DocumentType> {
|
||||
void clear() {
|
||||
_subject.add(const {});
|
||||
}
|
||||
|
||||
@override
|
||||
Map<int, DocumentType> get current => _subject.value;
|
||||
}
|
||||
|
||||
@@ -63,4 +63,7 @@ class StoragePathRepositoryImpl implements LabelRepository<StoragePath> {
|
||||
void clear() {
|
||||
_subject.add(const {});
|
||||
}
|
||||
|
||||
@override
|
||||
Map<int, StoragePath> get current => _subject.value;
|
||||
}
|
||||
|
||||
@@ -62,4 +62,7 @@ class TagRepositoryImpl implements LabelRepository<Tag> {
|
||||
void clear() {
|
||||
_subject.add(const {});
|
||||
}
|
||||
|
||||
@override
|
||||
Map<int, Tag> get current => _subject.value;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
abstract class LabelRepository<T extends Label> {
|
||||
Stream<Map<int, T>> get labels;
|
||||
|
||||
Map<int, T> get current;
|
||||
|
||||
Future<T> create(T label);
|
||||
Future<T?> find(int id);
|
||||
Future<Iterable<T>> findAll([Iterable<int>? ids]);
|
||||
|
||||
@@ -71,8 +71,7 @@ Note: If you have the GitHub Android app installed, the descriptions will not be
|
||||
Text(
|
||||
'Stack Trace',
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
).padded(
|
||||
const EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0)),
|
||||
).paddedOnly(top: 8.0, left: 8.0, right: 8.0),
|
||||
TextButton.icon(
|
||||
label: const Text('Copy'),
|
||||
icon: const Icon(Icons.copy),
|
||||
|
||||
@@ -3,13 +3,12 @@ import 'dart:io';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:http_interceptor/http_interceptor.dart';
|
||||
import 'package:paperless_mobile/core/interceptor/authentication.interceptor.dart';
|
||||
import 'package:paperless_mobile/core/interceptor/base_url_interceptor.dart';
|
||||
import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart';
|
||||
import 'package:paperless_mobile/core/interceptor/response_conversion.interceptor.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:http_interceptor/http/http.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
extension WidgetPadding on Widget {
|
||||
Widget padded([EdgeInsetsGeometry value = const EdgeInsets.all(8)]) {
|
||||
Widget padded([double all = 8.0]) {
|
||||
return Padding(
|
||||
padding: value,
|
||||
padding: EdgeInsets.all(all),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
|
||||
Widget paddedSymmetrically({double horizontal = 0.0, double vertical = 0.0}) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
|
||||
Widget paddedOnly(
|
||||
{double top = 0.0,
|
||||
double bottom = 0.0,
|
||||
double left = 0.0,
|
||||
double right = 0.0}) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: top,
|
||||
bottom: bottom,
|
||||
left: left,
|
||||
right: right,
|
||||
),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,18 +12,18 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
|
||||
Future<void> delete(DocumentModel document) async {
|
||||
await _api.delete(document);
|
||||
emit(const DocumentDetailsState());
|
||||
}
|
||||
|
||||
Future<void> update(DocumentModel document) async {
|
||||
final updatedDocument = await _api.update(document);
|
||||
emit(DocumentDetailsState(document: updatedDocument));
|
||||
}
|
||||
|
||||
Future<void> assignAsn(DocumentModel document) async {
|
||||
if (document.archiveSerialNumber == null) {
|
||||
final int asn = await _api.findNextAsn();
|
||||
update(document.copyWith(archiveSerialNumber: asn));
|
||||
final updatedDocument =
|
||||
await _api.update(document.copyWith(archiveSerialNumber: asn));
|
||||
emit(DocumentDetailsState(document: updatedDocument));
|
||||
}
|
||||
}
|
||||
|
||||
void replaceDocument(DocumentModel document) {
|
||||
emit(DocumentDetailsState(document: document));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
part of 'document_details_cubit.dart';
|
||||
|
||||
class DocumentDetailsState with EquatableMixin {
|
||||
final DocumentModel? document;
|
||||
final DocumentModel document;
|
||||
|
||||
const DocumentDetailsState({
|
||||
this.document,
|
||||
required this.document,
|
||||
});
|
||||
|
||||
@override
|
||||
|
||||
@@ -18,6 +18,7 @@ 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/edit_document/cubit/edit_document_cubit.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';
|
||||
@@ -65,9 +66,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
child: Scaffold(
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||
floatingActionButton: widget.allowEdit
|
||||
? FloatingActionButton(
|
||||
child: const Icon(Icons.edit),
|
||||
onPressed: _onEdit,
|
||||
? BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||
builder: (context, state) {
|
||||
return FloatingActionButton(
|
||||
child: const Icon(Icons.edit),
|
||||
onPressed: () => _onEdit(state.document),
|
||||
);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
bottomNavigationBar:
|
||||
@@ -79,24 +84,20 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: widget.allowEdit && state.document != null
|
||||
? () => _onDelete(state.document!)
|
||||
onPressed: widget.allowEdit
|
||||
? () => _onDelete(state.document)
|
||||
: null,
|
||||
).padded(const EdgeInsets.symmetric(horizontal: 4)),
|
||||
).paddedSymmetrically(horizontal: 4),
|
||||
DocumentDownloadButton(
|
||||
document: state.document,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.open_in_new),
|
||||
onPressed: state.document != null
|
||||
? () => _onOpen(state.document!)
|
||||
: null,
|
||||
).padded(const EdgeInsets.only(right: 4)),
|
||||
onPressed: () => _onOpen(state.document),
|
||||
).paddedOnly(right: 4.0),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.share),
|
||||
onPressed: state.document != null
|
||||
? () => _onShare(state.document!)
|
||||
: null,
|
||||
onPressed: () => _onShare(state.document),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -123,15 +124,10 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
expandedHeight: 200.0,
|
||||
flexibleSpace:
|
||||
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||
builder: (context, state) {
|
||||
if (state.document == null) {
|
||||
return Container(height: 200);
|
||||
}
|
||||
return DocumentPreview(
|
||||
id: state.document!.id,
|
||||
fit: BoxFit.cover,
|
||||
);
|
||||
},
|
||||
builder: (context, state) => DocumentPreview(
|
||||
id: state.document.id,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
bottom: ColoredTabBar(
|
||||
backgroundColor:
|
||||
@@ -172,27 +168,18 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
],
|
||||
body: BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||
builder: (context, state) {
|
||||
if (state.document == null) {
|
||||
return TabBarView(
|
||||
children: [
|
||||
Container(),
|
||||
Container(),
|
||||
Container(),
|
||||
],
|
||||
);
|
||||
}
|
||||
return TabBarView(
|
||||
children: [
|
||||
_buildDocumentOverview(
|
||||
state.document!,
|
||||
state.document,
|
||||
widget.titleAndContentQueryString,
|
||||
),
|
||||
_buildDocumentContentView(
|
||||
state.document!,
|
||||
state.document,
|
||||
widget.titleAndContentQueryString,
|
||||
),
|
||||
_buildDocumentMetaDataView(
|
||||
state.document!,
|
||||
state.document,
|
||||
),
|
||||
].padded(),
|
||||
);
|
||||
@@ -204,47 +191,42 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onEdit() async {
|
||||
Future<void> _onEdit(DocumentModel document) async {
|
||||
{
|
||||
final cubit = BlocProvider.of<DocumentDetailsCubit>(context);
|
||||
if (cubit.state.document == null) {
|
||||
return;
|
||||
}
|
||||
Navigator.push<bool>(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MultiRepositoryProvider(
|
||||
providers: [
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
builder: (context) => BlocProvider(
|
||||
create: (context) => EditDocumentCubit(
|
||||
document,
|
||||
documentsApi: getIt<PaperlessDocumentsApi>(),
|
||||
correspondentRepository:
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Tag>>(
|
||||
context,
|
||||
),
|
||||
documentTypeRepository:
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<StoragePath>>(
|
||||
context,
|
||||
),
|
||||
storagePathRepository:
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(
|
||||
context,
|
||||
),
|
||||
RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
tagRepository: RepositoryProvider.of<LabelRepository<Tag>>(
|
||||
context,
|
||||
),
|
||||
],
|
||||
child: DocumentEditPage(
|
||||
document: cubit.state.document!,
|
||||
onEdit: (updatedDocument) {
|
||||
return BlocProvider.of<DocumentDetailsCubit>(context)
|
||||
.update(updatedDocument);
|
||||
),
|
||||
child: BlocListener<EditDocumentCubit, EditDocumentState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.document != current.document,
|
||||
listener: (context, state) {
|
||||
cubit.replaceDocument(state.document);
|
||||
},
|
||||
child: const DocumentEditPage(),
|
||||
),
|
||||
),
|
||||
maintainState: false,
|
||||
maintainState: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -33,7 +32,7 @@ class _DocumentDownloadButtonState extends State<DocumentDownloadButton> {
|
||||
onPressed: Platform.isAndroid && widget.document != null
|
||||
? () => _onDownload(widget.document!)
|
||||
: null,
|
||||
).padded(const EdgeInsets.only(right: 4));
|
||||
).paddedOnly(right: 4);
|
||||
}
|
||||
|
||||
Future<void> _onDownload(DocumentModel document) async {
|
||||
|
||||
@@ -200,11 +200,12 @@ class _DocumentUploadPreparationPageState
|
||||
CorrespondentQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
),
|
||||
const TagFormField(
|
||||
TagFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
notAssignedSelectable: false,
|
||||
anyAssignedSelectable: false,
|
||||
excludeAllowed: false,
|
||||
selectableOptions: state.tags,
|
||||
//Label: "Tags" + " *",
|
||||
),
|
||||
Text(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -8,27 +7,19 @@ 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/edit_document/cubit/edit_document_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/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/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 DocumentEditPage extends StatefulWidget {
|
||||
final DocumentModel document;
|
||||
final FutureOr<void> Function(DocumentModel updatedDocument) onEdit;
|
||||
|
||||
const DocumentEditPage({
|
||||
Key? key,
|
||||
required this.document,
|
||||
required this.onEdit,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -43,150 +34,133 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
static const fkCreatedDate = "createdAtDate";
|
||||
static const fkStoragePath = 'storagePath';
|
||||
|
||||
late Future<Uint8List> documentBytes;
|
||||
|
||||
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
|
||||
bool _isSubmitLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
documentBytes =
|
||||
getIt<PaperlessDocumentsApi>().getPreview(widget.document.id);
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EditDocumentCubit, EditDocumentState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () => _onSubmit(state.document),
|
||||
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(state.document.title).padded(),
|
||||
_buildCreatedAtFormField(state.document.created).padded(),
|
||||
_buildDocumentTypeFormField(
|
||||
state.document.documentType, state.documentTypes)
|
||||
.padded(),
|
||||
_buildCorrespondentFormField(
|
||||
state.document.correspondent, state.correspondents)
|
||||
.padded(),
|
||||
_buildStoragePathFormField(
|
||||
state.document.storagePath, state.storagePaths)
|
||||
.padded(),
|
||||
TagFormField(
|
||||
initialValue: IdsTagsQuery.included(state.document.tags),
|
||||
notAssignedSelectable: false,
|
||||
anyAssignedSelectable: false,
|
||||
excludeAllowed: false,
|
||||
name: fkTags,
|
||||
selectableOptions: state.tags,
|
||||
).padded(),
|
||||
]),
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LabelsBlocProvider(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: _onSubmit,
|
||||
icon: const Icon(Icons.save),
|
||||
label: Text(S.of(context).genericActionSaveLabel),
|
||||
Widget _buildStoragePathFormField(
|
||||
int? initialId, Map<int, StoragePath> options) {
|
||||
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: options,
|
||||
initialValue: StoragePathQuery.fromId(initialId),
|
||||
name: fkStoragePath,
|
||||
queryParameterIdBuilder: StoragePathQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: StoragePathQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCorrespondentFormField(
|
||||
int? initialId, Map<int, Correspondent> options) {
|
||||
return LabelFormField<Correspondent, CorrespondentQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context,
|
||||
),
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).documentEditPageTitle),
|
||||
bottom: _isSubmitLoading
|
||||
? const PreferredSize(
|
||||
preferredSize: Size.fromHeight(4),
|
||||
child: LinearProgressIndicator(),
|
||||
)
|
||||
: null,
|
||||
child: AddCorrespondentPage(initialName: initialValue),
|
||||
),
|
||||
label: S.of(context).documentCorrespondentPropertyLabel,
|
||||
state: options,
|
||||
initialValue: CorrespondentQuery.fromId(initialId),
|
||||
name: fkCorrespondent,
|
||||
queryParameterIdBuilder: CorrespondentQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: CorrespondentQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDocumentTypeFormField(
|
||||
int? initialId, Map<int, DocumentType> options) {
|
||||
return LabelFormField<DocumentType, DocumentTypeQuery>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (currentInput) => RepositoryProvider.value(
|
||||
value: RepositoryProvider.of<LabelRepository<DocumentType>>(
|
||||
context,
|
||||
),
|
||||
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(),
|
||||
]),
|
||||
),
|
||||
child: AddDocumentTypePage(
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
label: S.of(context).documentDocumentTypePropertyLabel,
|
||||
initialValue: DocumentTypeQuery.fromId(initialId),
|
||||
state: options,
|
||||
name: fkDocumentType,
|
||||
queryParameterIdBuilder: DocumentTypeQuery.fromId,
|
||||
queryParameterNotAssignedBuilder: DocumentTypeQuery.notAssigned,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
Future<void> _onSubmit(DocumentModel document) async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final values = _formKey.currentState!.value;
|
||||
var updatedDocument = widget.document.copyWith(
|
||||
var mergedDocument = document.copyWith(
|
||||
title: values[fkTitle],
|
||||
created: values[fkCreatedDate],
|
||||
overwriteDocumentType: true,
|
||||
@@ -201,9 +175,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
setState(() {
|
||||
_isSubmitLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
await widget.onEdit(updatedDocument);
|
||||
await BlocProvider.of<EditDocumentCubit>(context)
|
||||
.updateDocument(mergedDocument);
|
||||
showSnackBar(context, S.of(context).documentUpdateSuccessMessage);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
@@ -216,18 +190,18 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTitleFormField() {
|
||||
Widget _buildTitleFormField(String? initialTitle) {
|
||||
return FormBuilderTextField(
|
||||
name: fkTitle,
|
||||
validator: FormBuilderValidators.required(),
|
||||
decoration: InputDecoration(
|
||||
label: Text(S.of(context).documentTitlePropertyLabel),
|
||||
),
|
||||
initialValue: widget.document.title,
|
||||
initialValue: initialTitle,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCreatedAtFormField() {
|
||||
Widget _buildCreatedAtFormField(DateTime? initialCreatedAtDate) {
|
||||
return FormBuilderDateTimePicker(
|
||||
inputType: InputType.date,
|
||||
name: fkCreatedDate,
|
||||
@@ -235,7 +209,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
prefixIcon: const Icon(Icons.calendar_month_outlined),
|
||||
label: Text(S.of(context).documentCreatedPropertyLabel),
|
||||
),
|
||||
initialValue: widget.document.created,
|
||||
initialValue: initialCreatedAtDate,
|
||||
format: DateFormat("dd. MMMM yyyy"), //TODO: Localized date format
|
||||
initialEntryMode: DatePickerEntryMode.calendar,
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
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';
|
||||
@@ -24,7 +24,6 @@ import 'package:paperless_mobile/features/settings/bloc/application_settings_cub
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:sliding_up_panel/sliding_up_panel.dart';
|
||||
|
||||
class DocumentsPage extends StatefulWidget {
|
||||
const DocumentsPage({Key? key}) : super(key: key);
|
||||
@@ -35,16 +34,17 @@ class DocumentsPage extends StatefulWidget {
|
||||
|
||||
class _DocumentsPageState extends State<DocumentsPage> {
|
||||
late final DocumentsCubit _documentsCubit;
|
||||
late final SavedViewCubit _savedViewCubit;
|
||||
|
||||
final _pagingController = PagingController<int, DocumentModel>(
|
||||
firstPageKey: 1,
|
||||
);
|
||||
|
||||
final _filterPanelController = PanelController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_documentsCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
_savedViewCubit = BlocProvider.of<SavedViewCubit>(context);
|
||||
try {
|
||||
_documentsCubit.load();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
@@ -59,97 +59,69 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadNewPage(int pageKey) async {
|
||||
final pageCount = _documentsCubit.state
|
||||
.inferPageCount(pageSize: _documentsCubit.state.filter.pageSize);
|
||||
if (pageCount <= pageKey + 1) {
|
||||
_pagingController.nextPageKey = null;
|
||||
}
|
||||
try {
|
||||
await _documentsCubit.loadMore();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelected(DocumentModel model) {
|
||||
_documentsCubit.toggleDocumentSelection(model);
|
||||
}
|
||||
|
||||
Future<void> _onRefresh() async {
|
||||
try {
|
||||
await _documentsCubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(page: 1),
|
||||
);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (_filterPanelController.isPanelOpen) {
|
||||
FocusScope.of(context).unfocus();
|
||||
_filterPanelController.close();
|
||||
return false;
|
||||
}
|
||||
if (_documentsCubit.state.selection.isNotEmpty) {
|
||||
_documentsCubit.resetSelection();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous != ConnectivityState.connected &&
|
||||
current == ConnectivityState.connected,
|
||||
listener: (context, state) {
|
||||
_documentsCubit.load();
|
||||
},
|
||||
child: BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous != ConnectivityState.connected &&
|
||||
current == ConnectivityState.connected,
|
||||
listener: (context, state) {
|
||||
_documentsCubit.load();
|
||||
},
|
||||
builder: (context, connectivityState) {
|
||||
return Scaffold(
|
||||
builder: (context, connectivityState) {
|
||||
return Scaffold(
|
||||
drawer: BlocProvider.value(
|
||||
value: BlocProvider.of<AuthenticationCubit>(context),
|
||||
child: InfoDrawer(
|
||||
afterInboxClosed: () => _documentsCubit.reload(),
|
||||
),
|
||||
),
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: SlidingUpPanel(
|
||||
backdropEnabled: true,
|
||||
parallaxEnabled: true,
|
||||
parallaxOffset: .5,
|
||||
controller: _filterPanelController,
|
||||
defaultPanelState: PanelState.CLOSED,
|
||||
minHeight: 48,
|
||||
maxHeight: (MediaQuery.of(context).size.height * 3) / 4,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
body: _buildBody(connectivityState),
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
panelBuilder: (scrollController) =>
|
||||
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
return LabelsBlocProvider(
|
||||
child: DocumentFilterPanel(
|
||||
panelController: _filterPanelController,
|
||||
scrollController: scrollController,
|
||||
initialFilter: state.filter,
|
||||
onFilterChanged: (filter) =>
|
||||
_documentsCubit.updateFilter(filter: filter),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
||||
return Badge(
|
||||
toAnimate: false,
|
||||
showBadge: appliedFiltersCount > 0,
|
||||
badgeContent: appliedFiltersCount > 0
|
||||
? Text(state.filter.appliedFiltersCount.toString())
|
||||
: null,
|
||||
child: FloatingActionButton(
|
||||
child: const Icon(Icons.filter_alt),
|
||||
onPressed: _openDocumentFilter,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: _buildBody(connectivityState));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _openDocumentFilter() async {
|
||||
final filter = await showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => SizedBox(
|
||||
height: MediaQuery.of(context).size.height - kToolbarHeight - 16,
|
||||
child: LabelsBlocProvider(
|
||||
child: DocumentFilterPanel(
|
||||
initialFilter: _documentsCubit.state.filter,
|
||||
),
|
||||
),
|
||||
),
|
||||
isDismissible: true,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16.0),
|
||||
topRight: Radius.circular(16.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (filter != null) {
|
||||
_documentsCubit.updateFilter(filter: filter);
|
||||
_savedViewCubit.resetSelection();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBody(ConnectivityState connectivityState) {
|
||||
@@ -193,6 +165,10 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
child = SliverToBoxAdapter(
|
||||
child: DocumentsEmptyState(
|
||||
state: state,
|
||||
onReset: () {
|
||||
_documentsCubit.updateFilter();
|
||||
_savedViewCubit.resetSelection();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -201,51 +177,45 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
onRefresh: _onRefresh,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
BlocProvider(
|
||||
create: (context) => SavedViewCubit(
|
||||
RepositoryProvider.of<SavedViewRepository>(context)),
|
||||
child: BlocListener<SavedViewCubit, SavedViewState>(
|
||||
listener: (context, state) {
|
||||
try {
|
||||
if (state.selectedSavedViewId == null) {
|
||||
_documentsCubit.updateFilter();
|
||||
} else {
|
||||
final newFilter = state
|
||||
.value[state.selectedSavedViewId]
|
||||
?.toDocumentFilter();
|
||||
if (newFilter != null) {
|
||||
_documentsCubit.updateFilter(filter: newFilter);
|
||||
}
|
||||
BlocListener<SavedViewCubit, SavedViewState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.selectedSavedViewId !=
|
||||
current.selectedSavedViewId,
|
||||
listener: (context, state) {
|
||||
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(
|
||||
icon: Icon(
|
||||
settings.preferredViewType == ViewType.grid
|
||||
? Icons.list
|
||||
: Icons.grid_view,
|
||||
),
|
||||
onPressed: () =>
|
||||
BlocProvider.of<ApplicationSettingsCubit>(
|
||||
context)
|
||||
.setViewType(
|
||||
settings.preferredViewType.toggle()),
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
},
|
||||
child: DocumentsPageAppBar(
|
||||
actions: [
|
||||
const SortDocumentsButton(),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
settings.preferredViewType == ViewType.grid
|
||||
? Icons.list
|
||||
: Icons.grid_view,
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () =>
|
||||
BlocProvider.of<ApplicationSettingsCubit>(context)
|
||||
.setViewType(
|
||||
settings.preferredViewType.toggle(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child,
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 4,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -296,4 +266,31 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadNewPage(int pageKey) async {
|
||||
final pageCount = _documentsCubit.state
|
||||
.inferPageCount(pageSize: _documentsCubit.state.filter.pageSize);
|
||||
if (pageCount <= pageKey + 1) {
|
||||
_pagingController.nextPageKey = null;
|
||||
}
|
||||
try {
|
||||
await _documentsCubit.loadMore();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelected(DocumentModel model) {
|
||||
_documentsCubit.toggleDocumentSelection(model);
|
||||
}
|
||||
|
||||
Future<void> _onRefresh() async {
|
||||
try {
|
||||
await _documentsCubit.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(page: 1),
|
||||
);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@ import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class DocumentsEmptyState extends StatelessWidget {
|
||||
final DocumentsState state;
|
||||
final VoidCallback onReset;
|
||||
const DocumentsEmptyState({
|
||||
Key? key,
|
||||
required this.state,
|
||||
required this.onReset,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -22,10 +24,7 @@ class DocumentsEmptyState extends StatelessWidget {
|
||||
subtitle: S.of(context).documentsPageEmptyStateNothingHereText,
|
||||
bottomChild: state.filter != DocumentFilter.initial
|
||||
? TextButton(
|
||||
onPressed: () async {
|
||||
await BlocProvider.of<DocumentsCubit>(context).updateFilter();
|
||||
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
||||
},
|
||||
onPressed: onReset,
|
||||
child: Text(
|
||||
S.of(context).documentsFilterPageResetFilterLabel,
|
||||
),
|
||||
|
||||
@@ -1,34 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:intl/intl.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/view/widgets/search/query_type_form_field.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/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/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:sliding_up_panel/sliding_up_panel.dart';
|
||||
|
||||
enum DateRangeSelection { before, after }
|
||||
|
||||
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);
|
||||
|
||||
@@ -60,33 +50,17 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const radius = Radius.circular(16);
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
topLeft: radius,
|
||||
topRight: radius,
|
||||
),
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
_buildDragLine(),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
label:
|
||||
Text(S.of(context).documentsFilterPageResetFilterLabel),
|
||||
onPressed: () => _resetFilter(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8.0,
|
||||
),
|
||||
_buildDraggableResetHeader(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -101,9 +75,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
const SizedBox(
|
||||
height: 16.0,
|
||||
),
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
@@ -111,34 +82,30 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
topRight: Radius.circular(16.0),
|
||||
),
|
||||
child: ListView(
|
||||
controller: widget.scrollController,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(S.of(context).documentsFilterPageSearchLabel),
|
||||
).padded(const EdgeInsets.only(left: 8.0)),
|
||||
_buildQueryFormField(),
|
||||
).paddedOnly(left: 8.0),
|
||||
_buildQueryFormField().padded(),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child:
|
||||
Text(S.of(context).documentsFilterPageAdvancedLabel),
|
||||
).padded(const EdgeInsets.only(left: 8.0, top: 8.0)),
|
||||
_buildCreatedDateRangePickerFormField().padded(),
|
||||
_buildAddedDateRangePickerFormField().padded(),
|
||||
).padded(),
|
||||
_buildCreatedDateRangePickerFormField(),
|
||||
_buildAddedDateRangePickerFormField(),
|
||||
_buildCorrespondentFormField().padded(),
|
||||
_buildDocumentTypeFormField().padded(),
|
||||
_buildStoragePathFormField().padded(),
|
||||
TagFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
initialValue: widget.initialFilter.tags,
|
||||
allowCreation: false,
|
||||
).padded(),
|
||||
_buildTagsFormField()
|
||||
.paddedSymmetrically(horizontal: 8, vertical: 4.0),
|
||||
// Required in order for the storage path field to be visible when typing
|
||||
const SizedBox(
|
||||
height: 150,
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -147,13 +114,39 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
);
|
||||
}
|
||||
|
||||
BlocBuilder<LabelCubit<Tag>, LabelState<Tag>> _buildTagsFormField() {
|
||||
return BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
|
||||
builder: (context, state) {
|
||||
return TagFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
initialValue: widget.initialFilter.tags,
|
||||
allowCreation: false,
|
||||
selectableOptions: state.labels,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Stack _buildDraggableResetHeader() {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
_buildDragLine(),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: Text(S.of(context).documentsFilterPageResetFilterLabel),
|
||||
onPressed: () => _resetFilter(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _resetFilter(BuildContext context) async {
|
||||
FocusScope.of(context).unfocus();
|
||||
await BlocProvider.of<DocumentsCubit>(context).updateFilter();
|
||||
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
||||
if (!widget.panelController.isPanelClosed) {
|
||||
widget.panelController.close();
|
||||
}
|
||||
Navigator.pop(context, DocumentFilter.initial);
|
||||
}
|
||||
|
||||
//TODO: Check if the blocs can be found in the context, otherwise just provide repository and create new bloc inside LabelFormField!
|
||||
@@ -238,14 +231,17 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
),
|
||||
initialValue: widget.initialFilter.queryText,
|
||||
).padded();
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDateRangePickerHelper(String formFieldKey) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
const spacer = SizedBox(width: 8.0);
|
||||
return SizedBox(
|
||||
height: 64,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
spacer,
|
||||
ActionChip(
|
||||
label: Text(
|
||||
S.of(context).documentsFilterPageDateRangeLastSevenDaysLabel,
|
||||
@@ -258,7 +254,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
);
|
||||
},
|
||||
).padded(const EdgeInsets.only(right: 8.0)),
|
||||
),
|
||||
spacer,
|
||||
ActionChip(
|
||||
label: Text(
|
||||
S.of(context).documentsFilterPageDateRangeLastMonthLabel,
|
||||
@@ -275,7 +272,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
);
|
||||
},
|
||||
).padded(const EdgeInsets.only(right: 8.0)),
|
||||
),
|
||||
spacer,
|
||||
ActionChip(
|
||||
label: Text(
|
||||
S.of(context).documentsFilterPageDateRangeLastThreeMonthsLabel,
|
||||
@@ -295,7 +293,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
);
|
||||
},
|
||||
).padded(const EdgeInsets.only(right: 8.0)),
|
||||
),
|
||||
spacer,
|
||||
ActionChip(
|
||||
label: Text(
|
||||
S.of(context).documentsFilterPageDateRangeLastYearLabel,
|
||||
@@ -316,6 +315,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
);
|
||||
},
|
||||
),
|
||||
spacer,
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -358,12 +358,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
labelText: S.of(context).documentCreatedPropertyLabel,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () =>
|
||||
_formKey.currentState?.fields[fkCreatedAt]?.didChange(null),
|
||||
onPressed: () {
|
||||
_formKey.currentState?.fields[fkCreatedAt]?.didChange(null);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
).paddedSymmetrically(horizontal: 8, vertical: 4.0),
|
||||
_buildDateRangePickerHelper(fkCreatedAt),
|
||||
],
|
||||
);
|
||||
@@ -393,7 +393,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
),
|
||||
child: child!,
|
||||
),
|
||||
format: DateFormat.yMMMd(Localizations.localeOf(context).toString()),
|
||||
format: DateFormat.yMMMd(),
|
||||
fieldStartLabelText:
|
||||
S.of(context).documentsFilterPageDateRangeFieldStartLabel,
|
||||
fieldEndLabelText:
|
||||
@@ -406,11 +406,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
labelText: S.of(context).documentAddedPropertyLabel,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () =>
|
||||
_formKey.currentState?.fields[fkAddedAt]?.didChange(null),
|
||||
onPressed: () {
|
||||
_formKey.currentState?.fields[fkAddedAt]?.didChange(null);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
).paddedSymmetrically(horizontal: 8),
|
||||
const SizedBox(height: 4.0),
|
||||
_buildDateRangePickerHelper(fkAddedAt),
|
||||
],
|
||||
@@ -429,28 +430,33 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
}
|
||||
|
||||
void _onApplyFilter() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
_formKey.currentState?.save();
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
final v = _formKey.currentState!.value;
|
||||
final docCubit = BlocProvider.of<DocumentsCubit>(context);
|
||||
DocumentFilter newFilter = docCubit.state.filter.copyWith(
|
||||
DocumentFilter newFilter = DocumentFilter(
|
||||
createdDateBefore: (v[fkCreatedAt] as DateTimeRange?)?.end,
|
||||
createdDateAfter: (v[fkCreatedAt] as DateTimeRange?)?.start,
|
||||
correspondent: v[fkCorrespondent] as CorrespondentQuery?,
|
||||
documentType: v[fkDocumentType] as DocumentTypeQuery?,
|
||||
storagePath: v[fkStoragePath] as StoragePathQuery?,
|
||||
tags: v[DocumentModel.tagsKey] as TagsQuery?,
|
||||
page: 1,
|
||||
correspondent: v[fkCorrespondent] as CorrespondentQuery? ??
|
||||
DocumentFilter.initial.correspondent,
|
||||
documentType: v[fkDocumentType] as DocumentTypeQuery? ??
|
||||
DocumentFilter.initial.documentType,
|
||||
storagePath: v[fkStoragePath] as StoragePathQuery? ??
|
||||
DocumentFilter.initial.storagePath,
|
||||
tags: v[DocumentModel.tagsKey] as TagsQuery? ??
|
||||
DocumentFilter.initial.tags,
|
||||
queryText: v[fkQuery] as String?,
|
||||
addedDateBefore: (v[fkAddedAt] as DateTimeRange?)?.end,
|
||||
addedDateAfter: (v[fkAddedAt] as DateTimeRange?)?.start,
|
||||
queryType: v[QueryTypeFormField.fkQueryType] as QueryType,
|
||||
asnQuery: widget.initialFilter.asnQuery,
|
||||
page: 1,
|
||||
pageSize: widget.initialFilter.pageSize,
|
||||
sortField: widget.initialFilter.sortField,
|
||||
sortOrder: widget.initialFilter.sortOrder,
|
||||
);
|
||||
try {
|
||||
await BlocProvider.of<DocumentsCubit>(context)
|
||||
.updateFilter(filter: newFilter);
|
||||
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
||||
FocusScope.of(context).unfocus();
|
||||
widget.panelController.close();
|
||||
Navigator.pop(context, newFilter);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
|
||||
@@ -1,6 +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/extensions/flutter_extensions.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/generated/l10n.dart';
|
||||
|
||||
class SortFieldSelectionBottomSheet extends StatefulWidget {
|
||||
@@ -46,30 +49,58 @@ class _SortFieldSelectionBottomSheetState
|
||||
S.of(context).documentsPageOrderByLabel,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
textAlign: TextAlign.start,
|
||||
).padded(
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(S.of(context).documentsFilterPageApplyFilterLabel),
|
||||
onPressed: () => widget.onSubmit(
|
||||
_currentSortField,
|
||||
_currentSortOrder,
|
||||
),
|
||||
onPressed: () {
|
||||
widget.onSubmit(
|
||||
_currentSortField,
|
||||
_currentSortOrder,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
).paddedSymmetrically(horizontal: 16, vertical: 8.0),
|
||||
Column(
|
||||
children: SortField.values.map(_buildSortOption).toList(),
|
||||
children: [
|
||||
_buildSortOption(SortField.archiveSerialNumber),
|
||||
BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
|
||||
builder: (context, state) {
|
||||
return _buildSortOption(
|
||||
SortField.correspondentName,
|
||||
enabled: state.labels.values.fold<bool>(
|
||||
false,
|
||||
(previousValue, element) =>
|
||||
previousValue || (element.documentCount ?? 0) > 0),
|
||||
);
|
||||
},
|
||||
),
|
||||
_buildSortOption(SortField.title),
|
||||
BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||
builder: (context, state) {
|
||||
return _buildSortOption(
|
||||
SortField.documentType,
|
||||
enabled: state.labels.values.fold<bool>(
|
||||
false,
|
||||
(previousValue, element) =>
|
||||
previousValue || (element.documentCount ?? 0) > 0),
|
||||
);
|
||||
},
|
||||
),
|
||||
_buildSortOption(SortField.created),
|
||||
_buildSortOption(SortField.added),
|
||||
_buildSortOption(SortField.modified),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSortOption(
|
||||
SortField field,
|
||||
) {
|
||||
Widget _buildSortOption(SortField field, {bool enabled = true}) {
|
||||
return ListTile(
|
||||
enabled: enabled,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
title: Text(
|
||||
_localizedSortField(field),
|
||||
@@ -77,6 +108,14 @@ class _SortFieldSelectionBottomSheetState
|
||||
trailing: _currentSortField == field
|
||||
? _buildOrderIcon(_currentSortOrder)
|
||||
: null,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_currentSortOrder = (_currentSortField == field
|
||||
? _currentSortOrder.toggle()
|
||||
: SortOrder.descending);
|
||||
_currentSortField = field;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
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/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';
|
||||
@@ -35,7 +33,7 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
snap: true,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: _buildFlexibleArea(false),
|
||||
flexibleSpace: _buildFlexibleArea(false, documentsState.filter),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () =>
|
||||
@@ -56,13 +54,12 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
snap: true,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: _buildFlexibleArea(true),
|
||||
title: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
return Text(
|
||||
'${S.of(context).documentsPageTitle} (${_formatDocumentCount(state.count)})',
|
||||
);
|
||||
},
|
||||
flexibleSpace: _buildFlexibleArea(
|
||||
true,
|
||||
documentsState.filter,
|
||||
),
|
||||
title: Text(
|
||||
'${S.of(context).documentsPageTitle} (${_formatDocumentCount(documentsState.count)})',
|
||||
),
|
||||
actions: [
|
||||
...widget.actions,
|
||||
@@ -73,14 +70,18 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFlexibleArea(bool enabled) {
|
||||
Widget _buildFlexibleArea(bool enabled, DocumentFilter filter) {
|
||||
return FlexibleSpaceBar(
|
||||
background: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
SavedViewSelectionWidget(height: 48, enabled: enabled),
|
||||
SavedViewSelectionWidget(
|
||||
height: 48,
|
||||
enabled: enabled,
|
||||
currentFilter: filter,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
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/documents/view/widgets/search/sort_field_selection_bottom_sheet.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
|
||||
class SortDocumentsButton extends StatefulWidget {
|
||||
const SortDocumentsButton({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
class SortDocumentsButton extends StatelessWidget {
|
||||
const SortDocumentsButton({super.key});
|
||||
|
||||
@override
|
||||
State<SortDocumentsButton> createState() => _SortDocumentsButtonState();
|
||||
}
|
||||
|
||||
class _SortDocumentsButtonState extends State<SortDocumentsButton> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.sort),
|
||||
onPressed: _onOpenSortBottomSheet,
|
||||
onPressed: () => _onOpenSortBottomSheet(context),
|
||||
);
|
||||
}
|
||||
|
||||
void _onOpenSortBottomSheet() {
|
||||
void _onOpenSortBottomSheet(BuildContext context) {
|
||||
showModalBottomSheet(
|
||||
elevation: 2,
|
||||
context: context,
|
||||
@@ -32,19 +28,41 @@ class _SortDocumentsButtonState extends State<SortDocumentsButton> {
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
builder: (context) => FractionallySizedBox(
|
||||
heightFactor: .6,
|
||||
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),
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentsCubit>(context),
|
||||
child: FractionallySizedBox(
|
||||
heightFactor: .6,
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<Correspondent>(
|
||||
RepositoryProvider.of<LabelRepository<Correspondent>>(
|
||||
context),
|
||||
),
|
||||
),
|
||||
],
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
91
lib/features/edit_document/cubit/edit_document_cubit.dart
Normal file
91
lib/features/edit_document/cubit/edit_document_cubit.dart
Normal file
@@ -0,0 +1,91 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
part 'edit_document_state.dart';
|
||||
|
||||
class EditDocumentCubit extends Cubit<EditDocumentState> {
|
||||
final DocumentModel _initialDocument;
|
||||
final PaperlessDocumentsApi _docsApi;
|
||||
|
||||
final LabelRepository<Correspondent> _correspondentRepository;
|
||||
final LabelRepository<DocumentType> _documentTypeRepository;
|
||||
final LabelRepository<StoragePath> _storagePathRepository;
|
||||
final LabelRepository<Tag> _tagRepository;
|
||||
|
||||
final List<StreamSubscription> _subscriptions = [];
|
||||
EditDocumentCubit(
|
||||
DocumentModel document, {
|
||||
required PaperlessDocumentsApi documentsApi,
|
||||
required LabelRepository<Correspondent> correspondentRepository,
|
||||
required LabelRepository<DocumentType> documentTypeRepository,
|
||||
required LabelRepository<StoragePath> storagePathRepository,
|
||||
required LabelRepository<Tag> tagRepository,
|
||||
}) : _initialDocument = document,
|
||||
_docsApi = documentsApi,
|
||||
_correspondentRepository = correspondentRepository,
|
||||
_documentTypeRepository = documentTypeRepository,
|
||||
_storagePathRepository = storagePathRepository,
|
||||
_tagRepository = tagRepository,
|
||||
super(
|
||||
EditDocumentState(
|
||||
document: document,
|
||||
correspondents: correspondentRepository.current,
|
||||
documentTypes: documentTypeRepository.current,
|
||||
storagePaths: storagePathRepository.current,
|
||||
tags: tagRepository.current,
|
||||
),
|
||||
) {
|
||||
_subscriptions.add(
|
||||
_correspondentRepository.labels
|
||||
.listen((v) => emit(state.copyWith(correspondents: v))),
|
||||
);
|
||||
_subscriptions.add(
|
||||
_documentTypeRepository.labels
|
||||
.listen((v) => emit(state.copyWith(documentTypes: v))),
|
||||
);
|
||||
_subscriptions.add(
|
||||
_storagePathRepository.labels
|
||||
.listen((v) => emit(state.copyWith(storagePaths: v))),
|
||||
);
|
||||
_subscriptions.add(
|
||||
_tagRepository.labels.listen(
|
||||
(v) => emit(state.copyWith(tags: v)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateDocument(DocumentModel document) async {
|
||||
final updated = await _docsApi.update(document);
|
||||
// Reload changed labels (documentCount property changes with removal/add)
|
||||
if (document.documentType != _initialDocument.documentType) {
|
||||
_documentTypeRepository
|
||||
.find((document.documentType ?? _initialDocument.documentType)!);
|
||||
}
|
||||
if (document.correspondent != _initialDocument.correspondent) {
|
||||
_correspondentRepository
|
||||
.find((document.correspondent ?? _initialDocument.correspondent)!);
|
||||
}
|
||||
if (document.storagePath != _initialDocument.storagePath) {
|
||||
_storagePathRepository
|
||||
.find((document.storagePath ?? _initialDocument.storagePath)!);
|
||||
}
|
||||
if (!const DeepCollectionEquality.unordered()
|
||||
.equals(document.tags, _initialDocument.tags)) {
|
||||
_tagRepository.findAll(document.tags);
|
||||
}
|
||||
emit(state.copyWith(document: updated));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
for (final sub in _subscriptions) {
|
||||
sub.cancel();
|
||||
}
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
43
lib/features/edit_document/cubit/edit_document_state.dart
Normal file
43
lib/features/edit_document/cubit/edit_document_state.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
part of 'edit_document_cubit.dart';
|
||||
|
||||
class EditDocumentState extends Equatable {
|
||||
final DocumentModel document;
|
||||
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
final Map<int, Tag> tags;
|
||||
|
||||
const EditDocumentState({
|
||||
required this.correspondents,
|
||||
required this.documentTypes,
|
||||
required this.storagePaths,
|
||||
required this.tags,
|
||||
required this.document,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
correspondents,
|
||||
documentTypes,
|
||||
storagePaths,
|
||||
tags,
|
||||
document,
|
||||
];
|
||||
|
||||
EditDocumentState copyWith({
|
||||
Map<int, Correspondent>? correspondents,
|
||||
Map<int, DocumentType>? documentTypes,
|
||||
Map<int, StoragePath>? storagePaths,
|
||||
Map<int, Tag>? tags,
|
||||
DocumentModel? document,
|
||||
}) {
|
||||
return EditDocumentState(
|
||||
document: document ?? this.document,
|
||||
correspondents: correspondents ?? this.correspondents,
|
||||
documentTypes: documentTypes ?? this.documentTypes,
|
||||
storagePaths: storagePaths ?? this.storagePaths,
|
||||
tags: tags ?? this.tags,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@ class EditLabelCubit<T extends Label> extends Cubit<EditLabelState<T>> {
|
||||
.listen((labels) => emit(EditLabelState(labels: labels)));
|
||||
}
|
||||
|
||||
Future<void> create(T label) => _repository.create(label);
|
||||
Future<T> create(T label) => _repository.create(label);
|
||||
|
||||
Future<void> update(T label) => _repository.update(label);
|
||||
Future<T> update(T label) => _repository.update(label);
|
||||
|
||||
Future<void> delete(T label) => _repository.delete(label);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class AddLabelPage<T extends Label> extends StatelessWidget {
|
||||
),
|
||||
child: AddLabelFormWidget(
|
||||
pageTitle: pageTitle,
|
||||
label: fromJsonT({'name': initialName}),
|
||||
label: initialName != null ? fromJsonT({'name': initialName}) : null,
|
||||
additionalFields: additionalFields,
|
||||
fromJsonT: fromJsonT,
|
||||
),
|
||||
|
||||
@@ -70,33 +70,38 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _onDelete(BuildContext context) {
|
||||
void _onDelete(BuildContext context) async {
|
||||
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),
|
||||
final shouldDelete = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title:
|
||||
Text(S.of(context).editLabelPageConfirmDeletionDialogTitle),
|
||||
content: Text(
|
||||
S.of(context).editLabelPageDeletionDialogText,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: Text(S.of(context).genericActionCancelLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
child: Text(
|
||||
S.of(context).genericActionDeleteLabel,
|
||||
style: TextStyle(color: Theme.of(context).errorColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
) ??
|
||||
false;
|
||||
if (shouldDelete) {
|
||||
BlocProvider.of<EditLabelCubit<T>>(context).delete(label);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
} else {
|
||||
BlocProvider.of<EditLabelCubit<T>>(context).delete(label);
|
||||
Navigator.pop(context);
|
||||
|
||||
@@ -2,8 +2,8 @@ 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/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
|
||||
class EditDocumentTypePage extends StatelessWidget {
|
||||
final DocumentType documentType;
|
||||
@@ -12,7 +12,7 @@ class EditDocumentTypePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
create: (context) => EditLabelCubit<DocumentType>(
|
||||
RepositoryProvider.of<LabelRepository<DocumentType>>(context),
|
||||
),
|
||||
child: EditLabelPage<DocumentType>(
|
||||
|
||||
@@ -2,8 +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/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';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
|
||||
|
||||
class EditStoragePathPage extends StatelessWidget {
|
||||
@@ -13,7 +13,7 @@ class EditStoragePathPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<StoragePath>(
|
||||
create: (context) => EditLabelCubit<StoragePath>(
|
||||
RepositoryProvider.of<LabelRepository<StoragePath>>(context),
|
||||
),
|
||||
child: EditLabelPage<StoragePath>(
|
||||
|
||||
@@ -4,8 +4,8 @@ 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/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 {
|
||||
@@ -16,7 +16,7 @@ class EditTagPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LabelCubit<Tag>(
|
||||
create: (context) => EditLabelCubit<Tag>(
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
),
|
||||
child: EditLabelPage<Tag>(
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:paperless_mobile/util.dart';
|
||||
class SubmitButtonConfig<T extends Label> {
|
||||
final Widget icon;
|
||||
final Widget label;
|
||||
final Future<void> Function(T) onSubmit;
|
||||
final Future<T> Function(T) onSubmit;
|
||||
|
||||
SubmitButtonConfig({
|
||||
required this.icon,
|
||||
@@ -117,8 +117,9 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
||||
...widget.initialValue?.toJson() ?? {},
|
||||
..._formKey.currentState!.value
|
||||
};
|
||||
await widget.submitButtonConfig.onSubmit(widget.fromJsonT(mergedJson));
|
||||
Navigator.pop(context);
|
||||
final createdLabel = await widget.submitButtonConfig
|
||||
.onSubmit(widget.fromJsonT(mergedJson));
|
||||
Navigator.pop(context, createdLabel);
|
||||
} on PaperlessValidationErrors catch (errorMessages) {
|
||||
setState(() => _errors = errorMessages);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
|
||||
@@ -59,6 +59,11 @@ class _HomePageState extends State<HomePage> {
|
||||
BlocProvider.value(
|
||||
value: DocumentsCubit(getIt<PaperlessDocumentsApi>()),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => SavedViewCubit(
|
||||
RepositoryProvider.of<SavedViewRepository>(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: const DocumentsPage(),
|
||||
),
|
||||
|
||||
@@ -8,11 +8,9 @@ import 'package:paperless_mobile/core/repository/provider/label_repositories_pro
|
||||
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/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';
|
||||
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
@@ -52,7 +50,7 @@ class InfoDrawer extends StatelessWidget {
|
||||
height: 32,
|
||||
width: 32,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
).padded(const EdgeInsets.only(right: 8.0)),
|
||||
).paddedOnly(right: 8.0),
|
||||
Text(
|
||||
S.of(context).appTitleText,
|
||||
style: Theme.of(context).textTheme.headline5?.copyWith(
|
||||
@@ -215,7 +213,7 @@ class InfoDrawer extends StatelessWidget {
|
||||
create: (context) => InboxCubit(
|
||||
RepositoryProvider.of<LabelRepository<Tag>>(context),
|
||||
getIt<PaperlessDocumentsApi>(),
|
||||
),
|
||||
)..loadInbox(),
|
||||
child: const InboxPage(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -54,12 +54,12 @@ class _InboxPageState extends State<InboxPage> {
|
||||
'${state.inboxItems.length} ${S.of(context).inboxPageUnseenText}',
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
).padded(const EdgeInsets.symmetric(horizontal: 4.0)),
|
||||
).paddedSymmetrically(horizontal: 4.0),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).padded(const EdgeInsets.symmetric(horizontal: 8.0)),
|
||||
).paddedSymmetrically(horizontal: 8.0),
|
||||
),
|
||||
),
|
||||
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
||||
@@ -108,7 +108,7 @@ class _InboxPageState extends State<InboxPage> {
|
||||
textAlign: TextAlign.center,
|
||||
).padded(),
|
||||
),
|
||||
).padded(const EdgeInsets.only(top: 8.0)),
|
||||
).paddedOnly(top: 8.0),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
@@ -137,14 +137,7 @@ class _InboxPageState extends State<InboxPage> {
|
||||
S.of(context).inboxPageUsageHintText,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
).padded(
|
||||
const EdgeInsets.only(
|
||||
top: 8.0,
|
||||
left: 8.0,
|
||||
right: 8.0,
|
||||
bottom: 8.0,
|
||||
),
|
||||
),
|
||||
).padded(),
|
||||
),
|
||||
...slivers
|
||||
],
|
||||
|
||||
@@ -11,9 +11,13 @@ class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
||||
|
||||
late StreamSubscription _subscription;
|
||||
|
||||
LabelCubit(this._repository) : super(LabelState.initial()) {
|
||||
LabelCubit(LabelRepository<T> repository)
|
||||
: _repository = repository,
|
||||
super(LabelState(labels: repository.current, isLoaded: true)) {
|
||||
_subscription = _repository.labels.listen(
|
||||
(update) => emit(LabelState(isLoaded: true, labels: update)),
|
||||
(update) => emit(
|
||||
LabelState(isLoaded: true, labels: update),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,6 +32,10 @@ class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reload() {
|
||||
return _repository.findAll();
|
||||
}
|
||||
|
||||
Future<T> replace(T item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await _repository.update(item);
|
||||
|
||||
@@ -4,11 +4,7 @@ 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/bloc/providers/tag_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class TagFormField extends StatefulWidget {
|
||||
@@ -18,6 +14,7 @@ class TagFormField extends StatefulWidget {
|
||||
final bool notAssignedSelectable;
|
||||
final bool anyAssignedSelectable;
|
||||
final bool excludeAllowed;
|
||||
final Map<int, Tag> selectableOptions;
|
||||
|
||||
const TagFormField({
|
||||
super.key,
|
||||
@@ -27,6 +24,7 @@ class TagFormField extends StatefulWidget {
|
||||
this.notAssignedSelectable = true,
|
||||
this.anyAssignedSelectable = true,
|
||||
this.excludeAllowed = true,
|
||||
required this.selectableOptions,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -47,10 +45,7 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
_textEditingController = TextEditingController()
|
||||
..addListener(() {
|
||||
setState(() {
|
||||
_showCreationSuffixIcon = BlocProvider.of<LabelCubit<Tag>>(context)
|
||||
.state
|
||||
.labels
|
||||
.values
|
||||
_showCreationSuffixIcon = widget.selectableOptions.values
|
||||
.where(
|
||||
(item) => item.name.toLowerCase().startsWith(
|
||||
_textEditingController.text.toLowerCase(),
|
||||
@@ -66,122 +61,126 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TagBlocProvider(
|
||||
child: BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
|
||||
builder: (context, tagState) {
|
||||
return FormBuilderField<TagsQuery>(
|
||||
builder: (field) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TypeAheadField<int>(
|
||||
textFieldConfiguration: TextFieldConfiguration(
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(
|
||||
Icons.label_outline,
|
||||
),
|
||||
suffixIcon: _buildSuffixIcon(context, field),
|
||||
labelText: S.of(context).documentTagsPropertyLabel,
|
||||
hintText: S.of(context).tagFormFieldSearchHintText,
|
||||
),
|
||||
controller: _textEditingController,
|
||||
),
|
||||
suggestionsCallback: (query) {
|
||||
final suggestions = tagState.labels.values
|
||||
.where((element) => element.name
|
||||
.toLowerCase()
|
||||
.startsWith(query.toLowerCase()))
|
||||
.map((e) => e.id!)
|
||||
.toList();
|
||||
if (field.value is IdsTagsQuery) {
|
||||
suggestions.removeWhere((element) =>
|
||||
(field.value as IdsTagsQuery)
|
||||
.ids
|
||||
.contains(element));
|
||||
}
|
||||
if (widget.notAssignedSelectable &&
|
||||
field.value is! OnlyNotAssignedTagsQuery) {
|
||||
suggestions.insert(0, _onlyNotAssignedId);
|
||||
}
|
||||
if (widget.anyAssignedSelectable &&
|
||||
field.value is! AnyAssignedTagsQuery) {
|
||||
suggestions.insert(0, _anyAssignedId);
|
||||
}
|
||||
return suggestions;
|
||||
},
|
||||
getImmediateSuggestions: true,
|
||||
animationStart: 1,
|
||||
itemBuilder: (context, data) {
|
||||
if (data == _onlyNotAssignedId) {
|
||||
return ListTile(
|
||||
title: Text(S.of(context).labelNotAssignedText),
|
||||
);
|
||||
} else if (data == _anyAssignedId) {
|
||||
return ListTile(
|
||||
title: Text(S.of(context).labelAnyAssignedText),
|
||||
);
|
||||
}
|
||||
final tag = tagState.getLabel(data)!;
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.circle,
|
||||
color: tag.color,
|
||||
),
|
||||
title: Text(
|
||||
tag.name,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onBackground),
|
||||
),
|
||||
);
|
||||
},
|
||||
onSuggestionSelected: (id) {
|
||||
if (id == _onlyNotAssignedId) {
|
||||
//Not assigned tag
|
||||
field.didChange(const OnlyNotAssignedTagsQuery());
|
||||
return;
|
||||
} else if (id == _anyAssignedId) {
|
||||
field.didChange(const AnyAssignedTagsQuery());
|
||||
} else {
|
||||
final tagsQuery = field.value is IdsTagsQuery
|
||||
? field.value as IdsTagsQuery
|
||||
: const IdsTagsQuery();
|
||||
field.didChange(tagsQuery
|
||||
.withIdQueriesAdded([IncludeTagIdQuery(id)]));
|
||||
}
|
||||
_textEditingController.clear();
|
||||
},
|
||||
direction: AxisDirection.up,
|
||||
final isEnabled = widget.selectableOptions.values.fold<bool>(
|
||||
false,
|
||||
(previousValue, element) =>
|
||||
previousValue || (element.documentCount ?? 0) > 0) ||
|
||||
widget.allowCreation;
|
||||
|
||||
return FormBuilderField<TagsQuery>(
|
||||
enabled: isEnabled,
|
||||
builder: (field) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TypeAheadField<int>(
|
||||
textFieldConfiguration: TextFieldConfiguration(
|
||||
enabled: isEnabled,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(
|
||||
Icons.label_outline,
|
||||
),
|
||||
if (field.value is OnlyNotAssignedTagsQuery) ...[
|
||||
_buildNotAssignedTag(field)
|
||||
] else if (field.value is AnyAssignedTagsQuery) ...[
|
||||
_buildAnyAssignedTag(field)
|
||||
] else ...[
|
||||
// field.value is IdsTagsQuery
|
||||
Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
runAlignment: WrapAlignment.start,
|
||||
spacing: 8.0,
|
||||
children: ((field.value as IdsTagsQuery).queries)
|
||||
.map(
|
||||
(query) => _buildTag(
|
||||
field,
|
||||
query,
|
||||
tagState.getLabel(query.id),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
initialValue: widget.initialValue ?? const IdsTagsQuery(),
|
||||
name: widget.name,
|
||||
);
|
||||
},
|
||||
),
|
||||
suffixIcon: _buildSuffixIcon(context, field),
|
||||
labelText: S.of(context).documentTagsPropertyLabel,
|
||||
hintText: S.of(context).tagFormFieldSearchHintText,
|
||||
),
|
||||
controller: _textEditingController,
|
||||
),
|
||||
suggestionsCallback: (query) {
|
||||
final suggestions = widget.selectableOptions.entries
|
||||
.where(
|
||||
(entry) => entry.value.name
|
||||
.toLowerCase()
|
||||
.startsWith(query.toLowerCase()),
|
||||
)
|
||||
.where((entry) =>
|
||||
widget.allowCreation ||
|
||||
(entry.value.documentCount ?? 0) > 0)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
if (field.value is IdsTagsQuery) {
|
||||
suggestions.removeWhere((element) =>
|
||||
(field.value as IdsTagsQuery).ids.contains(element));
|
||||
}
|
||||
if (widget.notAssignedSelectable &&
|
||||
field.value is! OnlyNotAssignedTagsQuery) {
|
||||
suggestions.insert(0, _onlyNotAssignedId);
|
||||
}
|
||||
if (widget.anyAssignedSelectable &&
|
||||
field.value is! AnyAssignedTagsQuery) {
|
||||
suggestions.insert(0, _anyAssignedId);
|
||||
}
|
||||
return suggestions;
|
||||
},
|
||||
getImmediateSuggestions: true,
|
||||
animationStart: 1,
|
||||
itemBuilder: (context, data) {
|
||||
if (data == _onlyNotAssignedId) {
|
||||
return ListTile(
|
||||
title: Text(S.of(context).labelNotAssignedText),
|
||||
);
|
||||
} else if (data == _anyAssignedId) {
|
||||
return ListTile(
|
||||
title: Text(S.of(context).labelAnyAssignedText),
|
||||
);
|
||||
}
|
||||
final tag = widget.selectableOptions[data]!;
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.circle,
|
||||
color: tag.color,
|
||||
),
|
||||
title: Text(
|
||||
tag.name,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onBackground),
|
||||
),
|
||||
);
|
||||
},
|
||||
onSuggestionSelected: (id) {
|
||||
if (id == _onlyNotAssignedId) {
|
||||
//Not assigned tag
|
||||
field.didChange(const OnlyNotAssignedTagsQuery());
|
||||
return;
|
||||
} else if (id == _anyAssignedId) {
|
||||
field.didChange(const AnyAssignedTagsQuery());
|
||||
} else {
|
||||
final tagsQuery = field.value is IdsTagsQuery
|
||||
? field.value as IdsTagsQuery
|
||||
: const IdsTagsQuery();
|
||||
field.didChange(
|
||||
tagsQuery.withIdQueriesAdded([IncludeTagIdQuery(id)]));
|
||||
}
|
||||
_textEditingController.clear();
|
||||
},
|
||||
direction: AxisDirection.up,
|
||||
),
|
||||
if (field.value is OnlyNotAssignedTagsQuery) ...[
|
||||
_buildNotAssignedTag(field)
|
||||
] else if (field.value is AnyAssignedTagsQuery) ...[
|
||||
_buildAnyAssignedTag(field)
|
||||
] else ...[
|
||||
// field.value is IdsTagsQuery
|
||||
Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
runAlignment: WrapAlignment.start,
|
||||
spacing: 8.0,
|
||||
children: ((field.value as IdsTagsQuery).queries)
|
||||
.map(
|
||||
(query) => _buildTag(
|
||||
field,
|
||||
query,
|
||||
widget.selectableOptions[query.id],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
initialValue: widget.initialValue ?? const IdsTagsQuery(),
|
||||
name: widget.name,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_tabController = TabController(length: 4, vsync: this)
|
||||
..addListener(() => setState(() => _currentIndex = _tabController.index));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
@@ -55,13 +57,15 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
|
||||
super.initState();
|
||||
_showClearSuffixIcon = widget.state.containsKey(widget.initialValue?.id);
|
||||
_textEditingController = TextEditingController(
|
||||
text: widget.state[widget.initialValue?.id]?.name ?? '')
|
||||
..addListener(() {
|
||||
text: widget.state[widget.initialValue?.id]?.name ?? '',
|
||||
)..addListener(() {
|
||||
setState(() {
|
||||
_showCreationSuffixIcon = widget.state.values
|
||||
.where((item) => item.name.toLowerCase().startsWith(
|
||||
_textEditingController.text.toLowerCase(),
|
||||
))
|
||||
.where(
|
||||
(item) => item.name.toLowerCase().startsWith(
|
||||
_textEditingController.text.toLowerCase(),
|
||||
),
|
||||
)
|
||||
.isEmpty;
|
||||
});
|
||||
setState(() =>
|
||||
@@ -71,7 +75,13 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isEnabled = widget.state.values.fold<bool>(
|
||||
false,
|
||||
(previousValue, element) =>
|
||||
previousValue || (element.documentCount ?? 0) > 0) ||
|
||||
widget.labelCreationWidgetBuilder != null;
|
||||
return FormBuilderTypeAhead<IdQueryParameter>(
|
||||
enabled: isEnabled,
|
||||
noItemsFoundBuilder: (context) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Text(
|
||||
@@ -88,13 +98,20 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
|
||||
S.of(context).labelNotAssignedText),
|
||||
),
|
||||
suggestionsCallback: (pattern) {
|
||||
final List<IdQueryParameter> suggestions = widget.state.keys
|
||||
.where((item) =>
|
||||
widget.state[item]!.name
|
||||
.toLowerCase()
|
||||
.startsWith(pattern.toLowerCase()) ||
|
||||
pattern.isEmpty)
|
||||
.map((id) => widget.queryParameterIdBuilder(id))
|
||||
final List<IdQueryParameter> suggestions = widget.state.entries
|
||||
.where(
|
||||
(entry) =>
|
||||
widget.state[entry.key]!.name
|
||||
.toLowerCase()
|
||||
.contains(pattern.toLowerCase()) ||
|
||||
pattern.isEmpty,
|
||||
)
|
||||
.where(
|
||||
(entry) =>
|
||||
widget.labelCreationWidgetBuilder != null ||
|
||||
(entry.value.documentCount ?? 0) > 0,
|
||||
)
|
||||
.map((entry) => widget.queryParameterIdBuilder(entry.key))
|
||||
.toList();
|
||||
if (widget.notAssignedSelectable) {
|
||||
suggestions.insert(0, widget.queryParameterNotAssignedBuilder());
|
||||
@@ -128,21 +145,23 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
|
||||
Widget? _buildSuffixIcon(BuildContext context) {
|
||||
if (_showCreationSuffixIcon && widget.labelCreationWidgetBuilder != null) {
|
||||
return IconButton(
|
||||
onPressed: () => Navigator.of(context)
|
||||
.push<T>(MaterialPageRoute(
|
||||
builder: (context) => widget
|
||||
.labelCreationWidgetBuilder!(_textEditingController.text)))
|
||||
.then((value) {
|
||||
if (value != null) {
|
||||
onPressed: () async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final createdLabel = await showDialog(
|
||||
context: context,
|
||||
builder: (context) => widget.labelCreationWidgetBuilder!(
|
||||
_textEditingController.text,
|
||||
),
|
||||
);
|
||||
if (createdLabel != null) {
|
||||
// If new label has been created, set form field value and text of this form field and unfocus keyboard (we assume user is done).
|
||||
widget.formBuilderState?.fields[widget.name]
|
||||
?.didChange(widget.queryParameterIdBuilder(value.id));
|
||||
_textEditingController.text = value.name;
|
||||
FocusScope.of(context).unfocus();
|
||||
?.didChange(widget.queryParameterIdBuilder(createdLabel.id));
|
||||
_textEditingController.text = createdLabel.name;
|
||||
} else {
|
||||
_reset();
|
||||
}
|
||||
}),
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.new_label,
|
||||
),
|
||||
@@ -158,8 +177,9 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
|
||||
}
|
||||
|
||||
void _reset() {
|
||||
widget.formBuilderState?.fields[widget.name]
|
||||
?.didChange(widget.queryParameterIdBuilder(null));
|
||||
widget.formBuilderState?.fields[widget.name]?.didChange(
|
||||
widget.queryParameterIdBuilder(null), // equivalnt to IdQueryParam.unset()
|
||||
);
|
||||
_textEditingController.clear();
|
||||
}
|
||||
|
||||
@@ -169,9 +189,7 @@ class _LabelFormFieldState<T extends Label, R extends IdQueryParameter>
|
||||
} else if (T == DocumentType) {
|
||||
return S.of(context).documentTypeFormFieldSearchHintText;
|
||||
} else {
|
||||
return S
|
||||
.of(context)
|
||||
.tagFormFieldSearchHintText; //TODO: Update tag form field once there is multi selection support.
|
||||
return S.of(context).tagFormFieldSearchHintText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,18 +62,21 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
return ListView(
|
||||
children: labels
|
||||
.map((l) => LabelItem<T>(
|
||||
name: l.name,
|
||||
content:
|
||||
contentBuilder?.call(l) ?? Text(l.match ?? '-'),
|
||||
onOpenEditPage: onEdit,
|
||||
filterBuilder: filterBuilder,
|
||||
leading: leadingBuilder?.call(l),
|
||||
label: l,
|
||||
))
|
||||
.toList(),
|
||||
return RefreshIndicator(
|
||||
onRefresh: BlocProvider.of<LabelCubit<T>>(context).reload,
|
||||
child: ListView(
|
||||
children: labels
|
||||
.map((l) => LabelItem<T>(
|
||||
name: l.name,
|
||||
content:
|
||||
contentBuilder?.call(l) ?? Text(l.match ?? '-'),
|
||||
onOpenEditPage: onEdit,
|
||||
filterBuilder: filterBuilder,
|
||||
leading: leadingBuilder?.call(l),
|
||||
label: l,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -81,7 +81,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
appSettings = ApplicationSettingsState.defaultSettings;
|
||||
}
|
||||
if (storedAuth == null || !storedAuth.isValid) {
|
||||
emit(AuthenticationState(isAuthenticated: false, wasLoginStored: false));
|
||||
return emit(
|
||||
AuthenticationState(isAuthenticated: false, wasLoginStored: false));
|
||||
} else {
|
||||
if (appSettings.isLocalAuthenticationEnabled) {
|
||||
final localAuthSuccess = await _localAuthService
|
||||
@@ -103,8 +104,13 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
wasLocalAuthenticationSuccessful: false,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return emit(AuthenticationState(
|
||||
isAuthenticated: true,
|
||||
authentication: storedAuth,
|
||||
wasLoginStored: true,
|
||||
));
|
||||
}
|
||||
emit(AuthenticationState(isAuthenticated: false, wasLoginStored: true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
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/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/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class SavedViewSelectionWidget extends StatelessWidget {
|
||||
final DocumentFilter currentFilter;
|
||||
const SavedViewSelectionWidget({
|
||||
Key? key,
|
||||
required this.height,
|
||||
required this.enabled,
|
||||
required this.currentFilter,
|
||||
}) : super(key: key);
|
||||
|
||||
final double height;
|
||||
@@ -64,10 +65,18 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
S.of(context).savedViewsLabel,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: enabled ? () => _onCreatePressed(context) : null,
|
||||
label: Text(S.of(context).savedViewCreateNewLabel),
|
||||
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.filter != current.filter,
|
||||
builder: (context, docState) {
|
||||
return TextButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: enabled
|
||||
? () => _onCreatePressed(context, docState.filter)
|
||||
: null,
|
||||
label: Text(S.of(context).savedViewCreateNewLabel),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -75,11 +84,11 @@ class SavedViewSelectionWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _onCreatePressed(BuildContext context) async {
|
||||
void _onCreatePressed(BuildContext context, DocumentFilter filter) async {
|
||||
final newView = await Navigator.of(context).push<SavedView?>(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AddSavedViewPage(
|
||||
currentFilter: getIt<DocumentsCubit>().state.filter,
|
||||
currentFilter: filter,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -107,6 +107,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
if (kDebugMode) {
|
||||
dev.log('[ScannerPage] Created temporary file: ${file.path}');
|
||||
}
|
||||
|
||||
final success = await EdgeDetection.detectEdge(file.path);
|
||||
if (!success) {
|
||||
if (kDebugMode) {
|
||||
|
||||
@@ -120,6 +120,10 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
),
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: Colors.lightGreen[50],
|
||||
@@ -134,8 +138,11 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
scrolledUnderElevation: 0.0,
|
||||
),
|
||||
inputDecorationTheme: const InputDecorationTheme(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 16.0,
|
||||
)),
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: Colors.green[900],
|
||||
),
|
||||
|
||||
@@ -25,7 +25,7 @@ class DocumentFilter extends Equatable {
|
||||
final DocumentTypeQuery documentType;
|
||||
final CorrespondentQuery correspondent;
|
||||
final StoragePathQuery storagePath;
|
||||
final AsnQuery asn;
|
||||
final AsnQuery asnQuery;
|
||||
final TagsQuery tags;
|
||||
final SortField sortField;
|
||||
final SortOrder sortOrder;
|
||||
@@ -42,7 +42,7 @@ class DocumentFilter extends Equatable {
|
||||
this.documentType = const DocumentTypeQuery.unset(),
|
||||
this.correspondent = const CorrespondentQuery.unset(),
|
||||
this.storagePath = const StoragePathQuery.unset(),
|
||||
this.asn = const AsnQuery.unset(),
|
||||
this.asnQuery = const AsnQuery.unset(),
|
||||
this.tags = const IdsTagsQuery(),
|
||||
this.sortField = SortField.created,
|
||||
this.sortOrder = SortOrder.descending,
|
||||
@@ -60,7 +60,7 @@ class DocumentFilter extends Equatable {
|
||||
sb.write(correspondent.toQueryParameter());
|
||||
sb.write(tags.toQueryParameter());
|
||||
sb.write(storagePath.toQueryParameter());
|
||||
sb.write(asn.toQueryParameter());
|
||||
sb.write(asnQuery.toQueryParameter());
|
||||
|
||||
if (queryText?.isNotEmpty ?? false) {
|
||||
sb.write("&${queryType.queryParam}=$queryText");
|
||||
@@ -104,6 +104,7 @@ class DocumentFilter extends Equatable {
|
||||
DocumentTypeQuery? documentType,
|
||||
CorrespondentQuery? correspondent,
|
||||
StoragePathQuery? storagePath,
|
||||
AsnQuery? asnQuery,
|
||||
TagsQuery? tags,
|
||||
SortField? sortField,
|
||||
SortOrder? sortOrder,
|
||||
@@ -129,6 +130,7 @@ class DocumentFilter extends Equatable {
|
||||
queryText: queryText ?? this.queryText,
|
||||
createdDateBefore: createdDateBefore ?? this.createdDateBefore,
|
||||
createdDateAfter: createdDateAfter ?? this.createdDateAfter,
|
||||
asnQuery: asnQuery ?? this.asnQuery,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -153,6 +155,19 @@ class DocumentFilter extends Equatable {
|
||||
return null;
|
||||
}
|
||||
|
||||
int get appliedFiltersCount => [
|
||||
documentType != initial.documentType,
|
||||
correspondent != initial.correspondent,
|
||||
storagePath != initial.storagePath,
|
||||
tags != initial.tags,
|
||||
(addedDateAfter != initial.addedDateAfter ||
|
||||
addedDateBefore != initial.addedDateBefore),
|
||||
(createdDateAfter != initial.createdDateAfter ||
|
||||
createdDateBefore != initial.createdDateBefore),
|
||||
asnQuery != initial.asnQuery,
|
||||
(queryType != initial.queryType || queryText != initial.queryText),
|
||||
].fold(0, (previousValue, element) => previousValue += element ? 1 : 0);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
pageSize,
|
||||
@@ -160,7 +175,7 @@ class DocumentFilter extends Equatable {
|
||||
documentType,
|
||||
correspondent,
|
||||
storagePath,
|
||||
asn,
|
||||
asnQuery,
|
||||
tags,
|
||||
sortField,
|
||||
sortOrder,
|
||||
|
||||
@@ -51,9 +51,9 @@ class DocumentModel extends Equatable {
|
||||
: id = json[idKey],
|
||||
title = json[titleKey],
|
||||
content = json[contentKey],
|
||||
created = DateTime.parse(json[createdKey]),
|
||||
modified = DateTime.parse(json[modifiedKey]),
|
||||
added = DateTime.parse(json[addedKey]),
|
||||
created = DateTime.parse(json[createdKey]).toLocal(),
|
||||
modified = DateTime.parse(json[modifiedKey]).toLocal(),
|
||||
added = DateTime.parse(json[addedKey]).toLocal(),
|
||||
archiveSerialNumber = json[asnKey],
|
||||
originalFileName = json[originalFileNameKey],
|
||||
archivedFileName = json[archivedFileNameKey],
|
||||
@@ -71,9 +71,9 @@ class DocumentModel extends Equatable {
|
||||
contentKey: content,
|
||||
correspondentKey: correspondent,
|
||||
documentTypeKey: documentType,
|
||||
createdKey: created.toUtc().toIso8601String(),
|
||||
modifiedKey: modified.toUtc().toIso8601String(),
|
||||
addedKey: added.toUtc().toIso8601String(),
|
||||
createdKey: created.toIso8601String(),
|
||||
modifiedKey: modified.toIso8601String(),
|
||||
addedKey: added.toIso8601String(),
|
||||
originalFileNameKey: originalFileName,
|
||||
tagsKey: tags.toList(),
|
||||
storagePathKey: storagePath,
|
||||
|
||||
@@ -187,7 +187,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||
const DocumentFilter asnQueryFilter = DocumentFilter(
|
||||
sortField: SortField.archiveSerialNumber,
|
||||
sortOrder: SortOrder.descending,
|
||||
asn: AsnQuery.anyAssigned(),
|
||||
asnQuery: AsnQuery.anyAssigned(),
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
);
|
||||
|
||||
171
pubspec.lock
171
pubspec.lock
@@ -35,7 +35,7 @@ packages:
|
||||
name: asn1lib
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.4.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -43,13 +43,20 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
badges:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: badges
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
barcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: barcode
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
version: "2.2.3"
|
||||
bloc:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -77,14 +84,14 @@ packages:
|
||||
name: build
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
version: "2.3.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.1.1"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -98,21 +105,21 @@ packages:
|
||||
name: build_resolvers
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.9"
|
||||
version: "2.0.10"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.11"
|
||||
version: "2.3.0"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "7.2.3"
|
||||
version: "7.2.7"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -126,28 +133,28 @@ packages:
|
||||
name: built_value
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.3.2"
|
||||
version: "8.4.2"
|
||||
cached_network_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cached_network_image
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.3"
|
||||
cached_network_image_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "2.0.0"
|
||||
cached_network_image_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
version: "1.0.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -210,7 +217,7 @@ packages:
|
||||
name: connectivity_plus_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.2.3"
|
||||
connectivity_plus_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -231,7 +238,7 @@ packages:
|
||||
name: convert
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "3.1.1"
|
||||
coverage:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -266,7 +273,7 @@ packages:
|
||||
name: dart_style
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
version: "2.2.4"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -343,13 +350,13 @@ packages:
|
||||
name: dropdown_search
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.3"
|
||||
version: "5.0.5"
|
||||
edge_detection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: master
|
||||
resolved-ref: "19fbebef99360e9cf0b59c6a90ff7cd26d4d6e7d"
|
||||
resolved-ref: "2d417dd77e075cb12e82a390e50cc4554e877ec4"
|
||||
url: "https://github.com/sawankumarbundelkhandi/edge_detection"
|
||||
source: git
|
||||
version: "1.1.1"
|
||||
@@ -366,7 +373,7 @@ packages:
|
||||
name: encrypted_shared_preferences
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
version: "3.0.1"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -474,7 +481,7 @@ packages:
|
||||
name: flutter_form_builder
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "7.5.0"
|
||||
version: "7.7.0"
|
||||
flutter_keyboard_visibility:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -535,14 +542,14 @@ packages:
|
||||
name: flutter_native_splash
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.11"
|
||||
version: "2.2.16"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.6"
|
||||
version: "2.0.7"
|
||||
flutter_rating_bar:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -556,7 +563,7 @@ packages:
|
||||
name: flutter_svg
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1+1"
|
||||
version: "1.1.6"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -587,20 +594,20 @@ packages:
|
||||
name: fluttertoast
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.1.1"
|
||||
version: "8.1.2"
|
||||
font_awesome_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: font_awesome_flutter
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "10.1.0"
|
||||
version: "10.3.0"
|
||||
form_builder_extra_fields:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: main
|
||||
resolved-ref: "33ba0a4407086275ac4357badc631be550fb3bcc"
|
||||
resolved-ref: b02de7dad9c00ece575ad4b8dfba73a3e1239e9c
|
||||
url: "https://github.com/flutter-form-builder-ecosystem/form_builder_extra_fields.git"
|
||||
source: git
|
||||
version: "8.4.0"
|
||||
@@ -636,14 +643,14 @@ packages:
|
||||
name: glob
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.1.0"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.2.0"
|
||||
hive:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -657,7 +664,7 @@ packages:
|
||||
name: html
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.15.0"
|
||||
version: "0.15.1"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -671,28 +678,28 @@ packages:
|
||||
name: http_interceptor
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0-beta.5"
|
||||
version: "2.0.0-beta.6"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "3.2.1"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.1"
|
||||
version: "4.0.2"
|
||||
image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "3.2.2"
|
||||
infinite_scroll_pagination:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -713,7 +720,7 @@ packages:
|
||||
name: injectable_generator
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
integration_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -739,7 +746,7 @@ packages:
|
||||
name: introduction_screen
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "3.1.1"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -781,7 +788,7 @@ packages:
|
||||
name: local_auth_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.13"
|
||||
version: "1.0.15"
|
||||
local_auth_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -809,7 +816,7 @@ packages:
|
||||
name: logging
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "1.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -837,7 +844,7 @@ packages:
|
||||
name: mime
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "1.0.3"
|
||||
mockito:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -886,7 +893,7 @@ packages:
|
||||
name: package_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.1.0"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -921,7 +928,7 @@ packages:
|
||||
name: package_info_plus_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
version: "1.0.6"
|
||||
package_info_plus_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -949,14 +956,14 @@ packages:
|
||||
name: path_drawing
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.0.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.0.1"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -970,14 +977,14 @@ packages:
|
||||
name: path_provider_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.14"
|
||||
version: "2.0.22"
|
||||
path_provider_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.9"
|
||||
version: "2.0.11"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -998,7 +1005,7 @@ packages:
|
||||
name: path_provider_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
version: "2.0.5"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1012,7 +1019,7 @@ packages:
|
||||
name: pdf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.8.1"
|
||||
version: "3.8.4"
|
||||
pdfx:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1047,28 +1054,28 @@ packages:
|
||||
name: permission_handler_apple
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "9.0.4"
|
||||
version: "9.0.7"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.7.0"
|
||||
version: "3.9.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
version: "0.1.2"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
version: "5.1.0"
|
||||
photo_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1089,21 +1096,21 @@ packages:
|
||||
name: plugin_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.6.0"
|
||||
version: "3.6.2"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
version: "1.5.1"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1117,21 +1124,21 @@ packages:
|
||||
name: provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
version: "6.0.4"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.3"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.1"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1139,6 +1146,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
recase:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: recase
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
receive_sharing_intent:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1159,7 +1173,7 @@ packages:
|
||||
name: share_plus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.2.0"
|
||||
version: "6.3.0"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1180,7 +1194,7 @@ packages:
|
||||
name: shared_preferences_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.12"
|
||||
version: "2.0.14"
|
||||
shared_preferences_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1208,7 +1222,7 @@ packages:
|
||||
name: shared_preferences_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.1.0"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1229,7 +1243,7 @@ packages:
|
||||
name: shelf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.0"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1250,7 +1264,7 @@ packages:
|
||||
name: shelf_web_socket
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
version: "1.0.3"
|
||||
shimmer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1264,47 +1278,40 @@ packages:
|
||||
name: signature
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
version: "5.3.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
sliding_up_panel:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sliding_up_panel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0+1"
|
||||
sliver_tools:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sliver_tools
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.7"
|
||||
version: "0.2.8"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
version: "1.2.6"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.1"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.10.10"
|
||||
version: "0.10.11"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1318,14 +1325,14 @@ packages:
|
||||
name: sqflite
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2+1"
|
||||
version: "2.2.2"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.1+1"
|
||||
version: "2.4.0+2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1346,7 +1353,7 @@ packages:
|
||||
name: stream_transform
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
version: "2.1.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1367,7 +1374,7 @@ packages:
|
||||
name: synchronized
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0+2"
|
||||
version: "3.0.0+3"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1430,14 +1437,14 @@ packages:
|
||||
name: url_launcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
version: "6.1.7"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.17"
|
||||
version: "6.0.22"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1486,7 +1493,7 @@ packages:
|
||||
name: uuid
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "3.0.7"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1507,7 +1514,7 @@ packages:
|
||||
name: watcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
version: "1.0.2"
|
||||
web_socket_channel:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1535,14 +1542,14 @@ packages:
|
||||
name: win32
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.1.2"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0+1"
|
||||
version: "0.2.0+2"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1559,4 +1566,4 @@ packages:
|
||||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=2.18.5 <3.0.0"
|
||||
flutter: ">=3.0.0"
|
||||
flutter: ">=3.3.0"
|
||||
|
||||
@@ -64,7 +64,6 @@ dependencies:
|
||||
ref: main
|
||||
form_builder_validators: ^8.4.0
|
||||
infinite_scroll_pagination: ^3.2.0
|
||||
sliding_up_panel: ^2.0.0+1
|
||||
package_info_plus: ^1.4.3+1
|
||||
font_awesome_flutter: ^10.1.0
|
||||
local_auth: ^2.1.2
|
||||
@@ -82,6 +81,7 @@ dependencies:
|
||||
path: packages/paperless_api
|
||||
hive: ^2.2.3
|
||||
rxdart: ^0.27.7
|
||||
badges: ^2.0.3
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
Reference in New Issue
Block a user