mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2026-01-31 20:24:53 -06:00
feat: Replaced old label form fields with full page search, removed badge from edit button in document details
This commit is contained in:
@@ -12,10 +12,8 @@ part 'document_edit_cubit.freezed.dart';
|
||||
class DocumentEditCubit extends Cubit<DocumentEditState> {
|
||||
final DocumentModel _initialDocument;
|
||||
final PaperlessDocumentsApi _docsApi;
|
||||
|
||||
final DocumentChangedNotifier _notifier;
|
||||
final LabelRepository _labelRepository;
|
||||
final List<StreamSubscription> _subscriptions = [];
|
||||
final DocumentChangedNotifier _notifier;
|
||||
|
||||
DocumentEditCubit(
|
||||
this._labelRepository,
|
||||
@@ -23,19 +21,16 @@ class DocumentEditCubit extends Cubit<DocumentEditState> {
|
||||
this._notifier, {
|
||||
required DocumentModel document,
|
||||
}) : _initialDocument = document,
|
||||
super(
|
||||
DocumentEditState(
|
||||
document: document,
|
||||
correspondents: _labelRepository.state.correspondents,
|
||||
documentTypes: _labelRepository.state.documentTypes,
|
||||
storagePaths: _labelRepository.state.storagePaths,
|
||||
tags: _labelRepository.state.tags,
|
||||
),
|
||||
) {
|
||||
super(DocumentEditState(document: document)) {
|
||||
_notifier.addListener(this, onUpdated: replace);
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) => emit(state.copyWith()),
|
||||
onChanged: (labels) => emit(state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
storagePaths: labels.storagePaths,
|
||||
tags: labels.tags,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,10 +63,8 @@ class DocumentEditCubit extends Cubit<DocumentEditState> {
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
for (final sub in _subscriptions) {
|
||||
sub.cancel();
|
||||
}
|
||||
_notifier.removeListener(this);
|
||||
_labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,10 +150,10 @@ class __$$_DocumentEditStateCopyWithImpl<$Res>
|
||||
class _$_DocumentEditState implements _DocumentEditState {
|
||||
const _$_DocumentEditState(
|
||||
{required this.document,
|
||||
required final Map<int, Correspondent> correspondents,
|
||||
required final Map<int, DocumentType> documentTypes,
|
||||
required final Map<int, StoragePath> storagePaths,
|
||||
required final Map<int, Tag> tags})
|
||||
final Map<int, Correspondent> correspondents = const {},
|
||||
final Map<int, DocumentType> documentTypes = const {},
|
||||
final Map<int, StoragePath> storagePaths = const {},
|
||||
final Map<int, Tag> tags = const {}})
|
||||
: _correspondents = correspondents,
|
||||
_documentTypes = documentTypes,
|
||||
_storagePaths = storagePaths,
|
||||
@@ -163,6 +163,7 @@ class _$_DocumentEditState implements _DocumentEditState {
|
||||
final DocumentModel document;
|
||||
final Map<int, Correspondent> _correspondents;
|
||||
@override
|
||||
@JsonKey()
|
||||
Map<int, Correspondent> get correspondents {
|
||||
if (_correspondents is EqualUnmodifiableMapView) return _correspondents;
|
||||
// ignore: implicit_dynamic_type
|
||||
@@ -171,6 +172,7 @@ class _$_DocumentEditState implements _DocumentEditState {
|
||||
|
||||
final Map<int, DocumentType> _documentTypes;
|
||||
@override
|
||||
@JsonKey()
|
||||
Map<int, DocumentType> get documentTypes {
|
||||
if (_documentTypes is EqualUnmodifiableMapView) return _documentTypes;
|
||||
// ignore: implicit_dynamic_type
|
||||
@@ -179,6 +181,7 @@ class _$_DocumentEditState implements _DocumentEditState {
|
||||
|
||||
final Map<int, StoragePath> _storagePaths;
|
||||
@override
|
||||
@JsonKey()
|
||||
Map<int, StoragePath> get storagePaths {
|
||||
if (_storagePaths is EqualUnmodifiableMapView) return _storagePaths;
|
||||
// ignore: implicit_dynamic_type
|
||||
@@ -187,6 +190,7 @@ class _$_DocumentEditState implements _DocumentEditState {
|
||||
|
||||
final Map<int, Tag> _tags;
|
||||
@override
|
||||
@JsonKey()
|
||||
Map<int, Tag> get tags {
|
||||
if (_tags is EqualUnmodifiableMapView) return _tags;
|
||||
// ignore: implicit_dynamic_type
|
||||
@@ -234,10 +238,10 @@ class _$_DocumentEditState implements _DocumentEditState {
|
||||
abstract class _DocumentEditState implements DocumentEditState {
|
||||
const factory _DocumentEditState(
|
||||
{required final DocumentModel document,
|
||||
required final Map<int, Correspondent> correspondents,
|
||||
required final Map<int, DocumentType> documentTypes,
|
||||
required final Map<int, StoragePath> storagePaths,
|
||||
required final Map<int, Tag> tags}) = _$_DocumentEditState;
|
||||
final Map<int, Correspondent> correspondents,
|
||||
final Map<int, DocumentType> documentTypes,
|
||||
final Map<int, StoragePath> storagePaths,
|
||||
final Map<int, Tag> tags}) = _$_DocumentEditState;
|
||||
|
||||
@override
|
||||
DocumentModel get document;
|
||||
|
||||
@@ -4,9 +4,9 @@ part of 'document_edit_cubit.dart';
|
||||
class DocumentEditState with _$DocumentEditState {
|
||||
const factory DocumentEditState({
|
||||
required DocumentModel document,
|
||||
required Map<int, Correspondent> correspondents,
|
||||
required Map<int, DocumentType> documentTypes,
|
||||
required Map<int, StoragePath> storagePaths,
|
||||
required Map<int, Tag> tags,
|
||||
@Default({}) Map<int, Correspondent> correspondents,
|
||||
@Default({}) Map<int, DocumentType> documentTypes,
|
||||
@Default({}) Map<int, StoragePath> storagePaths,
|
||||
@Default({}) Map<int, Tag> tags,
|
||||
}) = _DocumentEditState;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
import 'package: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';
|
||||
@@ -56,6 +57,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DocumentEditCubit, DocumentEditState>(
|
||||
builder: (context, state) {
|
||||
log("Updated state. correspondents have ${state.correspondents.length} items.");
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
@@ -95,18 +97,116 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
_buildTitleFormField(state.document.title).padded(),
|
||||
_buildCreatedAtFormField(state.document.created)
|
||||
.padded(),
|
||||
_buildCorrespondentFormField(
|
||||
state.document.correspondent,
|
||||
state.correspondents,
|
||||
// Correspondent form field
|
||||
Column(
|
||||
children: [
|
||||
LabelFormField<Correspondent>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddCorrespondentPage(
|
||||
initialName: initialValue,
|
||||
),
|
||||
),
|
||||
addLabelText: S.of(context)!.addCorrespondent,
|
||||
labelText: S.of(context)!.correspondent,
|
||||
options: context
|
||||
.watch<DocumentEditCubit>()
|
||||
.state
|
||||
.correspondents,
|
||||
initialValue: IdQueryParameter.fromId(
|
||||
state.document.correspondent,
|
||||
),
|
||||
name: fkCorrespondent,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
),
|
||||
if (_filteredSuggestions
|
||||
?.hasSuggestedCorrespondents ??
|
||||
false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions:
|
||||
_filteredSuggestions!.correspondents,
|
||||
itemBuilder: (context, itemData) =>
|
||||
ActionChip(
|
||||
label: Text(
|
||||
state.correspondents[itemData]!.name),
|
||||
onPressed: () {
|
||||
_formKey
|
||||
.currentState?.fields[fkCorrespondent]
|
||||
?.didChange(
|
||||
IdQueryParameter.fromId(itemData),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
_buildDocumentTypeFormField(
|
||||
state.document.documentType,
|
||||
state.documentTypes,
|
||||
// DocumentType form field
|
||||
Column(
|
||||
children: [
|
||||
LabelFormField<DocumentType>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (currentInput) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddDocumentTypePage(
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
addLabelText: S.of(context)!.addDocumentType,
|
||||
labelText: S.of(context)!.documentType,
|
||||
initialValue: IdQueryParameter.fromId(
|
||||
state.document.documentType),
|
||||
options: state.documentTypes,
|
||||
name: _DocumentEditPageState.fkDocumentType,
|
||||
prefixIcon:
|
||||
const Icon(Icons.description_outlined),
|
||||
),
|
||||
if (_filteredSuggestions
|
||||
?.hasSuggestedDocumentTypes ??
|
||||
false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions:
|
||||
_filteredSuggestions!.documentTypes,
|
||||
itemBuilder: (context, itemData) =>
|
||||
ActionChip(
|
||||
label: Text(
|
||||
state.documentTypes[itemData]!.name),
|
||||
onPressed: () => _formKey
|
||||
.currentState?.fields[fkDocumentType]
|
||||
?.didChange(
|
||||
IdQueryParameter.fromId(itemData),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
_buildStoragePathFormField(
|
||||
state.document.storagePath,
|
||||
state.storagePaths,
|
||||
// StoragePath form field
|
||||
Column(
|
||||
children: [
|
||||
LabelFormField<StoragePath>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
addLabelPageBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddStoragePathPage(
|
||||
initalName: initialValue),
|
||||
),
|
||||
addLabelText: S.of(context)!.addStoragePath,
|
||||
labelText: S.of(context)!.storagePath,
|
||||
options: state.storagePaths,
|
||||
initialValue: IdQueryParameter.fromId(
|
||||
state.document.storagePath),
|
||||
name: fkStoragePath,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
// Tag form field
|
||||
TagFormField(
|
||||
initialValue: IdsTagsQuery.included(
|
||||
state.document.tags.toList()),
|
||||
@@ -187,96 +287,6 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStoragePathFormField(
|
||||
int? initialId,
|
||||
Map<int, StoragePath> options,
|
||||
) {
|
||||
return Column(
|
||||
children: [
|
||||
LabelFormField<StoragePath>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddStoragePathPage(initalName: initialValue),
|
||||
),
|
||||
textFieldLabel: S.of(context)!.storagePath,
|
||||
labelOptions: options,
|
||||
initialValue: IdQueryParameter.fromId(initialId),
|
||||
name: fkStoragePath,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCorrespondentFormField(
|
||||
int? initialId, Map<int, Correspondent> options) {
|
||||
return Column(
|
||||
children: [
|
||||
LabelFormField<Correspondent>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (initialValue) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddCorrespondentPage(initialName: initialValue),
|
||||
),
|
||||
textFieldLabel: S.of(context)!.correspondent,
|
||||
labelOptions: options,
|
||||
initialValue: IdQueryParameter.fromId(initialId),
|
||||
name: fkCorrespondent,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
),
|
||||
if (_filteredSuggestions?.hasSuggestedCorrespondents ?? false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions: _filteredSuggestions!.correspondents,
|
||||
itemBuilder: (context, itemData) => ActionChip(
|
||||
label: Text(options[itemData]!.name),
|
||||
onPressed: () => _formKey.currentState?.fields[fkCorrespondent]
|
||||
?.didChange((IdQueryParameter.fromId(itemData))),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDocumentTypeFormField(
|
||||
int? initialId,
|
||||
Map<int, DocumentType> options,
|
||||
) {
|
||||
return Column(
|
||||
children: [
|
||||
LabelFormField<DocumentType>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
labelCreationWidgetBuilder: (currentInput) =>
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddDocumentTypePage(
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
textFieldLabel: S.of(context)!.documentType,
|
||||
initialValue: IdQueryParameter.fromId(initialId),
|
||||
labelOptions: options,
|
||||
name: fkDocumentType,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
),
|
||||
if (_filteredSuggestions?.hasSuggestedDocumentTypes ?? false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions: _filteredSuggestions!.documentTypes,
|
||||
itemBuilder: (context, itemData) => ActionChip(
|
||||
label: Text(options[itemData]!.name),
|
||||
onPressed: () => _formKey.currentState?.fields[fkDocumentType]
|
||||
?.didChange(IdQueryParameter.fromId(itemData)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onSubmit(DocumentModel document) async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final values = _formKey.currentState!.value;
|
||||
@@ -308,7 +318,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
Widget _buildTitleFormField(String? initialTitle) {
|
||||
return FormBuilderTextField(
|
||||
name: fkTitle,
|
||||
validator: FormBuilderValidators.required(),
|
||||
validator: (value) {
|
||||
if (value?.trim().isEmpty ?? true) {
|
||||
return S.of(context)!.thisFieldIsRequired;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
label: Text(S.of(context)!.title),
|
||||
),
|
||||
@@ -374,3 +389,56 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
).padded();
|
||||
}
|
||||
}
|
||||
|
||||
// class SampleWidget extends StatefulWidget {
|
||||
// const SampleWidget({super.key});
|
||||
|
||||
// @override
|
||||
// State<SampleWidget> createState() => _SampleWidgetState();
|
||||
// }
|
||||
|
||||
// class _SampleWidgetState extends State<SampleWidget> {
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return BlocBuilder<OptionsBloc, OptionsState>(
|
||||
// builder: (context, state) {
|
||||
// return OptionsFormField(
|
||||
// options: state.options,
|
||||
// onAddOption: (option) {
|
||||
// // This will call the repository and will cause a new state containing the new option to be emitted.
|
||||
// context.read<OptionsBloc>().addOption(option);
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class OptionsFormField extends StatefulWidget {
|
||||
// final List<Option> options;
|
||||
// final void Function(Option option) onAddOption;
|
||||
|
||||
|
||||
// const OptionsFormField({
|
||||
// super.key,
|
||||
// required this.options,
|
||||
// required this.onAddOption,
|
||||
// });
|
||||
|
||||
// @override
|
||||
// State<OptionsFormField> createState() => _OptionsFormFieldState();
|
||||
// }
|
||||
|
||||
// class _OptionsFormFieldState extends State<OptionsFormField> {
|
||||
// final TextEditingController _controller;
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return TextFormField(
|
||||
// onTap: () async {
|
||||
// // User creates new option...
|
||||
// final Option option = await showOptionCreationForm();
|
||||
// widget.onAddOption(option);
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user