fix: Refactor labels structure

This commit is contained in:
Anton Stubenbord
2023-04-05 19:44:58 +02:00
parent a2388b014b
commit 79ccdd0946
35 changed files with 357 additions and 234 deletions

View File

@@ -23,8 +23,8 @@ class DocumentChangedNotifier {
_deleted.add(deleted);
}
void subscribe(
dynamic subscriber, {
void addListener(
Object subscriber, {
DocumentChangedCallback? onUpdated,
DocumentChangedCallback? onDeleted,
}) {
@@ -41,7 +41,7 @@ class DocumentChangedNotifier {
);
}
void unsubscribe(dynamic subscriber) {
void removeListener(Object subscriber) {
_subscribers[subscriber]?.forEach((element) {
element.cancel();
});

View File

@@ -8,7 +8,9 @@ class LabelRepository extends HydratedCubit<LabelRepositoryState> {
final PaperlessLabelsApi _api;
final Map<Object, StreamSubscription> _subscribers = {};
void subscribe(
LabelRepository(this._api) : super(const LabelRepositoryState());
void addListener(
Object source, {
required void Function(LabelRepositoryState) onChanged,
}) {
@@ -18,12 +20,19 @@ class LabelRepository extends HydratedCubit<LabelRepositoryState> {
});
}
void unsubscribe(Object source) async {
void removeListener(Object source) async {
await _subscribers[source]?.cancel();
_subscribers.remove(source);
}
LabelRepository(this._api) : super(const LabelRepositoryState());
Future<void> initialize() {
return Future.wait([
findAllCorrespondents(),
findAllDocumentTypes(),
findAllStoragePaths(),
findAllTags(),
]);
}
Future<Tag> createTag(Tag object) async {
final created = await _api.saveTag(object);

View File

@@ -30,7 +30,7 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
tags: _labelRepository.state.tags,
),
) {
_notifier.subscribe(
_notifier.addListener(
this,
onDeleted: (document) {
// Remove items from internal selection after the document was deleted.
@@ -43,7 +43,7 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
);
},
);
_labelRepository.subscribe(
_labelRepository.addListener(
this,
onChanged: (labels) {
emit(
@@ -142,8 +142,8 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
@override
Future<void> close() {
_notifier.unsubscribe(this);
_labelRepository.unsubscribe(this);
_notifier.removeListener(this);
_labelRepository.removeListener(this);
return super.close();
}
}

View File

@@ -31,8 +31,8 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
}) : super(DocumentDetailsState(
document: initialDocument,
)) {
_notifier.subscribe(this, onUpdated: replace);
_labelRepository.subscribe(
_notifier.addListener(this, onUpdated: replace);
_labelRepository.addListener(
this,
onChanged: (labels) => emit(
state.copyWith(
@@ -210,7 +210,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
for (final element in _subscriptions) {
await element.cancel();
}
_notifier.unsubscribe(this);
_notifier.removeListener(this);
await super.close();
}
}

View File

@@ -176,6 +176,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
builder: (context, state) {
return BlocProvider(
create: (context) => SimilarDocumentsCubit(
context.read(),
context.read(),
context.read(),
documentId: state.document.id,
@@ -186,7 +187,10 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
document: state.document,
itemSpacing: _itemSpacing,
queryString: widget.titleAndContentQueryString,
availableCorrespondents: state.correspondents,
availableDocumentTypes: state.documentTypes,
availableTags: state.tags,
availableStoragePaths: state.storagePaths,
),
DocumentContentWidget(
isFullContentLoaded: state.isFullContentLoaded,
@@ -215,7 +219,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
return BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
builder: (context, state) {
final _filteredSuggestions =
state.suggestions.documentDifference(state.document);
state.suggestions?.documentDifference(state.document);
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivityState) {
if (!connectivityState.isConnected) {
@@ -223,7 +227,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
}
return b.Badge(
position: b.BadgePosition.topEnd(top: -12, end: -6),
showBadge: _filteredSuggestions.hasSuggestions,
showBadge: _filteredSuggestions?.hasSuggestions ?? false,
child: Tooltip(
message: S.of(context)!.editDocumentTooltip,
preferBelow: false,
@@ -234,7 +238,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
),
),
badgeContent: Text(
'${_filteredSuggestions.suggestionsCount}',
'${_filteredSuggestions?.suggestionsCount ?? 0}',
style: const TextStyle(
color: Colors.white,
),
@@ -300,13 +304,10 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
providers: [
BlocProvider.value(
value: DocumentEditCubit(
document,
documentsApi: context.read(),
correspondentRepository: context.read(),
documentTypeRepository: context.read(),
storagePathRepository: context.read(),
tagRepository: context.read(),
notifier: context.read(),
context.read(),
context.read(),
context.read(),
document: document,
),
),
BlocProvider<DocumentDetailsCubit>.value(

View File

@@ -32,10 +32,10 @@ class DocumentEditCubit extends Cubit<DocumentEditState> {
tags: _labelRepository.state.tags,
),
) {
_notifier.subscribe(this, onUpdated: replace);
_labelRepository.subscribe(
_notifier.addListener(this, onUpdated: replace);
_labelRepository.addListener(
this,
onStateChanged: (labels) => emit(state.copyWith()),
onChanged: (labels) => emit(state.copyWith()),
);
}
@@ -71,7 +71,7 @@ class DocumentEditCubit extends Cubit<DocumentEditState> {
for (final sub in _subscriptions) {
sub.cancel();
}
_notifier.unsubscribe(this);
_notifier.removeListener(this);
return super.close();
}
}

View File

@@ -21,7 +21,7 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
class DocumentEditPage extends StatefulWidget {
final FieldSuggestions suggestions;
final FieldSuggestions? suggestions;
const DocumentEditPage({
Key? key,
required this.suggestions,
@@ -43,13 +43,13 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
bool _isSubmitLoading = false;
late final FieldSuggestions _filteredSuggestions;
late final FieldSuggestions? _filteredSuggestions;
@override
void initState() {
super.initState();
_filteredSuggestions = widget.suggestions
.documentDifference(context.read<DocumentEditCubit>().state.document);
?.documentDifference(context.read<DocumentEditCubit>().state.document);
}
@override
@@ -115,12 +115,14 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
excludeAllowed: false,
name: fkTags,
selectableOptions: state.tags,
suggestions: _filteredSuggestions.tags
.toSet()
suggestions: (_filteredSuggestions?.tags.toSet() ??
{})
.difference(state.document.tags.toSet())
.isNotEmpty
? _buildSuggestionsSkeleton<int>(
suggestions: _filteredSuggestions.tags,
suggestions:
(_filteredSuggestions?.tags.toSet() ??
{}),
itemBuilder: (context, itemData) {
final tag = state.tags[itemData]!;
return ActionChip(
@@ -216,8 +218,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
LabelFormField<Correspondent>(
notAssignedSelectable: false,
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider(
create: (context) => context.read<LabelRepository<Correspondent>>(),
labelCreationWidgetBuilder: (initialValue) =>
RepositoryProvider.value(
value: context.read<LabelRepository>(),
child: AddCorrespondentPage(initialName: initialValue),
),
textFieldLabel: S.of(context)!.correspondent,
@@ -226,9 +229,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
name: fkCorrespondent,
prefixIcon: const Icon(Icons.person_outlined),
),
if (_filteredSuggestions.hasSuggestedCorrespondents)
if (_filteredSuggestions?.hasSuggestedCorrespondents ?? false)
_buildSuggestionsSkeleton<int>(
suggestions: _filteredSuggestions.correspondents,
suggestions: _filteredSuggestions!.correspondents,
itemBuilder: (context, itemData) => ActionChip(
label: Text(options[itemData]!.name),
onPressed: () => _formKey.currentState?.fields[fkCorrespondent]
@@ -248,8 +251,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
LabelFormField<DocumentType>(
notAssignedSelectable: false,
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (currentInput) => RepositoryProvider(
create: (context) => context.read<LabelRepository<DocumentType>>(),
labelCreationWidgetBuilder: (currentInput) =>
RepositoryProvider.value(
value: context.read<LabelRepository>(),
child: AddDocumentTypePage(
initialName: currentInput,
),
@@ -260,9 +264,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
name: fkDocumentType,
prefixIcon: const Icon(Icons.description_outlined),
),
if (_filteredSuggestions.hasSuggestedDocumentTypes)
if (_filteredSuggestions?.hasSuggestedDocumentTypes ?? false)
_buildSuggestionsSkeleton<int>(
suggestions: _filteredSuggestions.documentTypes,
suggestions: _filteredSuggestions!.documentTypes,
itemBuilder: (context, itemData) => ActionChip(
label: Text(options[itemData]!.name),
onPressed: () => _formKey.currentState?.fields[fkDocumentType]
@@ -327,9 +331,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
format: DateFormat.yMMMMd(),
initialEntryMode: DatePickerEntryMode.calendar,
),
if (_filteredSuggestions.hasSuggestedDates)
if (_filteredSuggestions?.hasSuggestedDates ?? false)
_buildSuggestionsSkeleton<DateTime>(
suggestions: _filteredSuggestions.dates,
suggestions: _filteredSuggestions!.dates,
itemBuilder: (context, itemData) => ActionChip(
label: Text(DateFormat.yMMMd().format(itemData)),
onPressed: () => _formKey.currentState?.fields[fkCreatedDate]

View File

@@ -12,7 +12,6 @@ import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
import 'package:paperless_mobile/core/global/constants.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
import 'package:paperless_mobile/core/service/file_description.dart';
import 'package:paperless_mobile/core/service/file_service.dart';
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
@@ -198,15 +197,10 @@ class _ScannerPageState extends State<ScannerPage>
);
final uploadResult = await Navigator.of(context).push<DocumentUploadResult>(
MaterialPageRoute(
builder: (_) => LabelRepositoriesProvider(
child: BlocProvider(
builder: (_) => BlocProvider(
create: (context) => DocumentUploadCubit(
documentApi: context.read<PaperlessDocumentsApi>(),
correspondentRepository:
context.read<LabelRepository<Correspondent>>(),
documentTypeRepository:
context.read<LabelRepository<DocumentType>>(),
tagRepository: context.read<LabelRepository<Tag>>(),
context.read(),
context.read(),
),
child: DocumentUploadPreparationPage(
fileBytes: file.bytes,
@@ -214,7 +208,6 @@ class _ScannerPageState extends State<ScannerPage>
),
),
),
),
);
if ((uploadResult?.success ?? false) && uploadResult?.taskId != null) {
// For paperless version older than 1.11.3, task id will always be null!
@@ -316,15 +309,10 @@ class _ScannerPageState extends State<ScannerPage>
}
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => LabelRepositoriesProvider(
child: BlocProvider(
builder: (_) => BlocProvider(
create: (context) => DocumentUploadCubit(
documentApi: context.read<PaperlessDocumentsApi>(),
correspondentRepository:
context.read<LabelRepository<Correspondent>>(),
documentTypeRepository:
context.read<LabelRepository<DocumentType>>(),
tagRepository: context.read<LabelRepository<Tag>>(),
context.read(),
context.read(),
),
child: DocumentUploadPreparationPage(
fileBytes: file.readAsBytesSync(),
@@ -334,7 +322,6 @@ class _ScannerPageState extends State<ScannerPage>
),
),
),
),
);
}
}

View File

@@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
@@ -15,12 +16,22 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
with DocumentPagingBlocMixin {
@override
final PaperlessDocumentsApi api;
final LabelRepository _labelRepository;
@override
final DocumentChangedNotifier notifier;
DocumentSearchCubit(this.api, this.notifier)
DocumentSearchCubit(this.api, this.notifier, this._labelRepository)
: super(const DocumentSearchState()) {
notifier.subscribe(
_labelRepository.addListener(this, onChanged: (labels) {
emit(state.copyWith(
correspondents: labels.correspondents,
documentTypes: labels.documentTypes,
tags: labels.tags,
storagePaths: labels.storagePaths,
));
});
notifier.addListener(
this,
onDeleted: remove,
onUpdated: replace,
@@ -89,7 +100,7 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
@override
Future<void> close() {
notifier.unsubscribe(this);
notifier.removeListener(this);
return super.close();
}

View File

@@ -13,6 +13,12 @@ class DocumentSearchState extends DocumentPagingState {
final List<String> suggestions;
@JsonKey()
final ViewType viewType;
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, Tag> tags;
final Map<int, StoragePath> storagePaths;
const DocumentSearchState({
this.view = SearchView.suggestions,
this.searchHistory = const [],
@@ -22,6 +28,10 @@ class DocumentSearchState extends DocumentPagingState {
super.hasLoaded,
super.isLoading,
super.value,
this.correspondents = const {},
this.documentTypes = const {},
this.tags = const {},
this.storagePaths = const {},
});
@override
@@ -31,6 +41,10 @@ class DocumentSearchState extends DocumentPagingState {
suggestions,
view,
viewType,
correspondents,
documentTypes,
tags,
storagePaths,
];
@override
@@ -57,6 +71,10 @@ class DocumentSearchState extends DocumentPagingState {
List<String>? suggestions,
SearchView? view,
ViewType? viewType,
Map<int, Correspondent>? correspondents,
Map<int, DocumentType>? documentTypes,
Map<int, Tag>? tags,
Map<int, StoragePath>? storagePaths,
}) {
return DocumentSearchState(
value: value ?? this.value,
@@ -67,6 +85,10 @@ class DocumentSearchState extends DocumentPagingState {
view: view ?? this.view,
suggestions: suggestions ?? this.suggestions,
viewType: viewType ?? this.viewType,
correspondents: correspondents ?? this.correspondents,
documentTypes: documentTypes ?? this.documentTypes,
tags: tags ?? this.tags,
storagePaths: storagePaths ?? this.storagePaths,
);
}

View File

@@ -21,6 +21,7 @@ Future<void> showDocumentSearchPage(BuildContext context) {
create: (context) => DocumentSearchCubit(
context.read(),
context.read(),
context.read(),
),
child: const DocumentSearchPage(),
),
@@ -229,6 +230,10 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
),
);
},
correspondents: state.correspondents,
documentTypes: state.documentTypes,
tags: state.tags,
storagePaths: state.storagePaths,
)
],
);

View File

@@ -5,42 +5,26 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
part 'document_upload_state.dart';
class DocumentUploadCubit extends Cubit<DocumentUploadState> {
final PaperlessDocumentsApi _documentApi;
final LabelRepository<Tag> _tagRepository;
final LabelRepository<Correspondent> _correspondentRepository;
final LabelRepository<DocumentType> _documentTypeRepository;
final LabelRepository _labelRepository;
final List<StreamSubscription> _subs = [];
DocumentUploadCubit({
required PaperlessDocumentsApi documentApi,
required LabelRepository<Tag> tagRepository,
required LabelRepository<Correspondent> correspondentRepository,
required LabelRepository<DocumentType> documentTypeRepository,
}) : _documentApi = documentApi,
_tagRepository = tagRepository,
_correspondentRepository = correspondentRepository,
_documentTypeRepository = documentTypeRepository,
super(const DocumentUploadState()) {
_subs.add(_tagRepository.values.listen(
(tags) => emit(state.copyWith(tags: tags?.values)),
));
_subs.add(_correspondentRepository.values.listen(
(correspondents) =>
emit(state.copyWith(correspondents: correspondents?.values)),
));
_subs.add(_documentTypeRepository.values.listen(
(documentTypes) =>
emit(state.copyWith(documentTypes: documentTypes?.values)),
DocumentUploadCubit(this._labelRepository, this._documentApi)
: super(const DocumentUploadState()) {
_labelRepository.addListener(
this,
onChanged: (labels) {
emit(state.copyWith(
correspondents: labels.correspondents,
documentTypes: labels.documentTypes,
tags: labels.tags,
));
},
);
}
Future<String?> upload(
@@ -65,9 +49,7 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
@override
Future<void> close() async {
for (final sub in _subs) {
await sub.cancel();
}
_labelRepository.removeListener(this);
return super.close();
}
}

View File

@@ -192,9 +192,8 @@ class _DocumentUploadPreparationPageState
notAssignedSelectable: false,
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialName) =>
RepositoryProvider(
create: (context) =>
context.read<LabelRepository<Correspondent>>(),
RepositoryProvider.value(
value: context.read<LabelRepository>(),
child: AddCorrespondentPage(initialName: initialName),
),
textFieldLabel: S.of(context)!.correspondent + " *",
@@ -207,9 +206,8 @@ class _DocumentUploadPreparationPageState
notAssignedSelectable: false,
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialName) =>
RepositoryProvider(
create: (context) =>
context.read<LabelRepository<DocumentType>>(),
RepositoryProvider.value(
value: context.read<LabelRepository>(),
child: AddDocumentTypePage(initialName: initialName),
),
textFieldLabel: S.of(context)!.documentType + " *",
@@ -229,7 +227,7 @@ class _DocumentUploadPreparationPageState
"* " + S.of(context)!.uploadInferValuesHint,
style: Theme.of(context).textTheme.bodySmall,
),
SizedBox(height: 300),
const SizedBox(height: 300),
].padded(),
),
);

View File

@@ -1,19 +1,17 @@
import 'dart:async';
import 'dart:developer';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'documents_state.dart';
part 'documents_cubit.g.dart';
part 'documents_state.dart';
class DocumentsCubit extends HydratedCubit<DocumentsState>
with DocumentPagingBlocMixin {
@@ -32,7 +30,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
storagePaths: _labelRepository.state.storagePaths,
tags: _labelRepository.state.tags,
)) {
notifier.subscribe(
notifier.addListener(
this,
onUpdated: (document) {
replace(document);
@@ -54,9 +52,9 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
);
},
);
_labelRepository.subscribe(
_labelRepository.addListener(
this,
onStateChanged: (labels) => emit(
onChanged: (labels) => emit(
state.copyWith(
correspondents: labels.correspondents,
documentTypes: labels.documentTypes,
@@ -120,8 +118,8 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
@override
Future<void> close() {
notifier.unsubscribe(this);
_labelRepository.unsubscribe(this);
notifier.removeListener(this);
_labelRepository.removeListener(this);
return super.close();
}

View File

@@ -12,11 +12,9 @@ import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_panel.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
import 'package:paperless_mobile/features/labels/cubit/providers/labels_bloc_provider.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
import 'package:paperless_mobile/features/saved_view/view/saved_view_list.dart';
@@ -358,6 +356,10 @@ class _DocumentsPageState extends State<DocumentsPage>
isLabelClickable: true,
isLoading: state.isLoading,
selectedDocumentIds: state.selectedIds,
correspondents: state.correspondents,
documentTypes: state.documentTypes,
tags: state.tags,
storagePaths: state.storagePaths,
);
},
),
@@ -391,10 +393,16 @@ class _DocumentsPageState extends State<DocumentsPage>
void _onCreateSavedView(DocumentFilter filter) async {
final newView = await Navigator.of(context).push<SavedView?>(
MaterialPageRoute(
builder: (context) => LabelsBlocProvider(
child: AddSavedViewPage(
builder: (context) => BlocBuilder<SavedViewCubit, SavedViewState>(
builder: (context, state) {
return AddSavedViewPage(
currentFilter: filter,
),
correspondents: state.correspondents,
documentTypes: state.documentTypes,
storagePaths: state.storagePaths,
tags: state.tags,
);
},
),
),
);
@@ -428,12 +436,19 @@ class _DocumentsPageState extends State<DocumentsPage>
snapSizes: const [0.9, 1],
initialChildSize: .9,
maxChildSize: 1,
builder: (context, controller) => LabelsBlocProvider(
child: DocumentFilterPanel(
builder: (context, controller) =>
BlocBuilder<DocumentsCubit, DocumentsState>(
builder: (context, state) {
return DocumentFilterPanel(
initialFilter: context.read<DocumentsCubit>().state.filter,
scrollController: controller,
draggableSheetController: draggableSheetController,
),
correspondents: state.correspondents,
documentTypes: state.documentTypes,
storagePaths: state.storagePaths,
tags: state.tags,
);
},
),
),
),

View File

@@ -119,7 +119,7 @@ class DocumentDetailedItem extends DocumentItem {
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
correspondent: context.read<DocumentsCubit>().correspondent,
correspondent: correspondents[document.correspondent],
),
],
).paddedLTRB(8, 0, 8, 4),
@@ -134,13 +134,13 @@ class DocumentDetailedItem extends DocumentItem {
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
documentTypeId: document.documentType,
documentType: documentTypes[document.documentType],
),
],
).paddedLTRB(8, 0, 8, 4),
TagsWidget(
isMultiLine: false,
tagIds: document.tags,
tags: document.tags.map((e) => tags[e]!).toList(),
).padded(),
if (highlights != null)
Html(

View File

@@ -49,6 +49,11 @@ class DocumentFilterForm extends StatefulWidget {
final DocumentFilter initialFilter;
final ScrollController? scrollController;
final EdgeInsets padding;
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, Tag> tags;
final Map<int, StoragePath> storagePaths;
const DocumentFilterForm({
super.key,
this.header,
@@ -56,6 +61,10 @@ class DocumentFilterForm extends StatefulWidget {
required this.initialFilter,
this.scrollController,
this.padding = const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
required this.correspondents,
required this.documentTypes,
required this.tags,
required this.storagePaths,
});
@override
@@ -145,48 +154,36 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
}
Widget _buildDocumentTypeFormField() {
return BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
builder: (context, state) {
return LabelFormField<DocumentType>(
formBuilderState: widget.formKey.currentState,
name: DocumentFilterForm.fkDocumentType,
labelOptions: state.labels,
labelOptions: widget.documentTypes,
textFieldLabel: S.of(context)!.documentType,
initialValue: widget.initialFilter.documentType,
prefixIcon: const Icon(Icons.description_outlined),
);
},
);
}
Widget _buildCorrespondentFormField() {
return BlocBuilder<LabelCubit<Correspondent>, LabelState<Correspondent>>(
builder: (context, state) {
return LabelFormField<Correspondent>(
formBuilderState: widget.formKey.currentState,
name: DocumentFilterForm.fkCorrespondent,
labelOptions: state.labels,
labelOptions: widget.correspondents,
textFieldLabel: S.of(context)!.correspondent,
initialValue: widget.initialFilter.correspondent,
prefixIcon: const Icon(Icons.person_outline),
);
},
);
}
Widget _buildStoragePathFormField() {
return BlocBuilder<LabelCubit<StoragePath>, LabelState<StoragePath>>(
builder: (context, state) {
return LabelFormField<StoragePath>(
formBuilderState: widget.formKey.currentState,
name: DocumentFilterForm.fkStoragePath,
labelOptions: state.labels,
labelOptions: widget.storagePaths,
textFieldLabel: S.of(context)!.storagePath,
initialValue: widget.initialFilter.storagePath,
prefixIcon: const Icon(Icons.folder_outlined),
);
},
);
}
Widget _buildQueryFormField() {
@@ -197,16 +194,12 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
);
}
BlocBuilder<LabelCubit<Tag>, LabelState<Tag>> _buildTagsFormField() {
return BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
builder: (context, state) {
Widget _buildTagsFormField() {
return TagFormField(
name: DocumentModel.tagsKey,
initialValue: widget.initialFilter.tags,
allowCreation: false,
selectableOptions: state.labels,
);
},
selectableOptions: widget.tags,
);
}
}

View File

@@ -13,11 +13,20 @@ class DocumentFilterPanel extends StatefulWidget {
final DocumentFilter initialFilter;
final ScrollController scrollController;
final DraggableScrollableController draggableSheetController;
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, Tag> tags;
final Map<int, StoragePath> storagePaths;
const DocumentFilterPanel({
Key? key,
required this.initialFilter,
required this.scrollController,
required this.draggableSheetController,
required this.correspondents,
required this.documentTypes,
required this.tags,
required this.storagePaths,
}) : super(key: key);
@override
@@ -38,10 +47,8 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
void animateTitleByDrag() {
setState(
() {
_heightAnimationValue = dp(
((max(0.9, widget.draggableSheetController.size) - 0.9) / 0.1), 5);
},
() => _heightAnimationValue =
dp(((max(0.9, widget.draggableSheetController.size) - 0.9) / 0.1), 5),
);
}
@@ -96,6 +103,10 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
scrollController: widget.scrollController,
initialFilter: widget.initialFilter,
header: _buildPanelHeader(),
correspondents: widget.correspondents,
documentTypes: widget.documentTypes,
storagePaths: widget.storagePaths,
tags: widget.tags,
),
),
);

View File

@@ -10,6 +10,10 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
class SortFieldSelectionBottomSheet extends StatefulWidget {
final SortOrder initialSortOrder;
final SortField? initialSortField;
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, Tag> tags;
final Map<int, StoragePath> storagePaths;
final Future Function(SortField? field, SortOrder order) onSubmit;
@@ -18,6 +22,10 @@ class SortFieldSelectionBottomSheet extends StatefulWidget {
required this.initialSortOrder,
required this.initialSortField,
required this.onSubmit,
required this.correspondents,
required this.documentTypes,
required this.tags,
required this.storagePaths,
});
@override
@@ -67,31 +75,20 @@ class _SortFieldSelectionBottomSheetState
Column(
children: [
_buildSortOption(SortField.archiveSerialNumber),
BlocBuilder<LabelCubit<Correspondent>,
LabelState<Correspondent>>(
builder: (context, state) {
return _buildSortOption(
_buildSortOption(
SortField.correspondentName,
enabled: state.labels.values.fold<bool>(
enabled: widget.correspondents.values.fold<bool>(
false,
(previousValue, element) =>
previousValue ||
(element.documentCount ?? 0) > 0),
);
},
previousValue || (element.documentCount ?? 0) > 0),
),
_buildSortOption(SortField.title),
BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
builder: (context, state) {
return _buildSortOption(
_buildSortOption(
SortField.documentType,
enabled: state.labels.values.fold<bool>(
enabled: widget.documentTypes.values.fold<bool>(
false,
(previousValue, element) =>
previousValue ||
(element.documentCount ?? 0) > 0),
);
},
previousValue || (element.documentCount ?? 0) > 0),
),
_buildSortOption(SortField.created),
_buildSortOption(SortField.added),

View File

@@ -57,6 +57,10 @@ class SortDocumentsButton extends StatelessWidget {
sortOrder: order,
),
),
correspondents: state.correspondents,
documentTypes: state.documentTypes,
storagePaths: state.storagePaths,
tags: state.tags,
),
),
),

View File

@@ -13,7 +13,7 @@ class EditLabelCubit extends Cubit<EditLabelState> with LabelCubitMixin {
final LabelRepository labelRepository;
EditLabelCubit(this.labelRepository) : super(const EditLabelState()) {
labelRepository.subscribe(
labelRepository.addListener(
this,
onChanged: (labels) => state.copyWith(
correspondents: labels.correspondents,
@@ -26,7 +26,7 @@ class EditLabelCubit extends Cubit<EditLabelState> with LabelCubitMixin {
@override
Future<void> close() {
labelRepository.unsubscribe(this);
labelRepository.removeListener(this);
return super.close();
}

View File

@@ -157,10 +157,8 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: DocumentUploadCubit(
documentApi: context.read(),
tagRepository: context.read(),
correspondentRepository: context.read(),
documentTypeRepository: context.read(),
context.read(),
context.read(),
),
child: DocumentUploadPreparationPage(
fileBytes: bytes,
@@ -239,11 +237,13 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
create: (context) => DocumentsCubit(
context.read(),
context.read(),
context.read(),
),
),
BlocProvider(
create: (context) => SavedViewCubit(
context.read(),
context.read(),
),
),
],

View File

@@ -32,7 +32,7 @@ class InboxCubit extends HydratedCubit<InboxState>
this._labelRepository,
this.notifier,
) : super(InboxState(labels: _labelRepository.state)) {
notifier.subscribe(
notifier.addListener(
this,
onDeleted: remove,
onUpdated: (document) {
@@ -47,7 +47,7 @@ class InboxCubit extends HydratedCubit<InboxState>
}
},
);
_labelRepository.subscribe(
_labelRepository.addListener(
this,
onChanged: (labels) {
emit(state.copyWith(labels: labels));
@@ -209,7 +209,7 @@ class InboxCubit extends HydratedCubit<InboxState>
@override
Future<void> close() {
_labelRepository.unsubscribe(this);
_labelRepository.removeListener(this);
return super.close();
}
}

View File

@@ -12,7 +12,7 @@ class LabelCubit extends Cubit<LabelState> with LabelCubitMixin<LabelState> {
final LabelRepository labelRepository;
LabelCubit(this.labelRepository) : super(const LabelState()) {
labelRepository.subscribe(
labelRepository.addListener(
this,
onChanged: (labels) {
emit(state.copyWith(
@@ -27,7 +27,7 @@ class LabelCubit extends Cubit<LabelState> with LabelCubitMixin<LabelState> {
@override
Future<void> close() {
labelRepository.unsubscribe(this);
labelRepository.removeListener(this);
return super.close();
}

View File

@@ -27,7 +27,7 @@ class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
this._labelRepository,
) : super(const LinkedDocumentsState()) {
updateFilter(filter: filter);
_labelRepository.subscribe(
_labelRepository.addListener(
this,
onChanged: (labels) {
emit(state.copyWith(
@@ -38,7 +38,7 @@ class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
));
},
);
notifier.subscribe(
notifier.addListener(
this,
onUpdated: replace,
onDeleted: remove,

View File

@@ -172,7 +172,7 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
@override
Future<void> close() {
notifier.unsubscribe(this);
notifier.removeListener(this);
return super.close();
}
}

View File

@@ -21,7 +21,7 @@ class SavedViewCubit extends Cubit<SavedViewState> {
storagePaths: _labelRepository.state.storagePaths,
tags: _labelRepository.state.tags,
)) {
_labelRepository.subscribe(
_labelRepository.addListener(
this,
onChanged: (labels) {
emit(
@@ -76,7 +76,7 @@ class SavedViewCubit extends Cubit<SavedViewState> {
@override
Future<void> close() {
_savedViewRepository.unsubscribe(this);
_labelRepository.unsubscribe(this);
_labelRepository.removeListener(this);
return super.close();
}
}

View File

@@ -8,7 +8,18 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
class AddSavedViewPage extends StatefulWidget {
final DocumentFilter currentFilter;
const AddSavedViewPage({super.key, required this.currentFilter});
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, Tag> tags;
final Map<int, StoragePath> storagePaths;
const AddSavedViewPage({
super.key,
required this.currentFilter,
required this.correspondents,
required this.documentTypes,
required this.tags,
required this.storagePaths,
});
@override
State<AddSavedViewPage> createState() => _AddSavedViewPageState();
@@ -72,6 +83,10 @@ class _AddSavedViewPageState extends State<AddSavedViewPage> {
padding: const EdgeInsets.symmetric(vertical: 8),
formKey: _filterFormKey,
initialFilter: widget.currentFilter,
correspondents: widget.correspondents,
documentTypes: widget.documentTypes,
storagePaths: widget.storagePaths,
tags: widget.tags,
),
),
],

View File

@@ -23,7 +23,7 @@ class SavedViewDetailsCubit extends HydratedCubit<SavedViewDetailsState>
this.notifier, {
required this.savedView,
}) : super(const SavedViewDetailsState()) {
notifier.subscribe(
notifier.addListener(
this,
onDeleted: remove,
onUpdated: replace,

View File

@@ -5,17 +5,30 @@ class SavedViewDetailsState extends DocumentPagingState {
@JsonKey()
final ViewType viewType;
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, Tag> tags;
final Map<int, StoragePath> storagePaths;
const SavedViewDetailsState({
this.viewType = ViewType.list,
super.filter,
super.hasLoaded,
super.isLoading,
super.value,
this.correspondents = const {},
this.documentTypes = const {},
this.tags = const {},
this.storagePaths = const {},
});
@override
List<Object?> get props => [
viewType,
correspondents,
documentTypes,
tags,
storagePaths,
...super.props,
];
@@ -40,6 +53,10 @@ class SavedViewDetailsState extends DocumentPagingState {
List<PagedSearchResult<DocumentModel>>? value,
DocumentFilter? filter,
ViewType? viewType,
Map<int, Correspondent>? correspondents,
Map<int, DocumentType>? documentTypes,
Map<int, Tag>? tags,
Map<int, StoragePath>? storagePaths,
}) {
return SavedViewDetailsState(
hasLoaded: hasLoaded ?? this.hasLoaded,
@@ -47,6 +64,10 @@ class SavedViewDetailsState extends DocumentPagingState {
value: value ?? this.value,
filter: filter ?? this.filter,
viewType: viewType ?? this.viewType,
correspondents: correspondents ?? this.correspondents,
documentTypes: documentTypes ?? this.documentTypes,
tags: tags ?? this.tags,
storagePaths: storagePaths ?? this.storagePaths,
);
}

View File

@@ -86,6 +86,10 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
);
},
viewType: state.viewType,
correspondents: state.correspondents,
documentTypes: state.documentTypes,
tags: state.tags,
storagePaths: state.storagePaths,
),
if (state.hasLoaded && state.isLoading)
const SliverToBoxAdapter(

View File

@@ -1,6 +1,7 @@
import 'package:bloc/bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
@@ -13,19 +14,33 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
@override
final PaperlessDocumentsApi api;
final LabelRepository _labelRepository;
@override
final DocumentChangedNotifier notifier;
SimilarDocumentsCubit(
this.api,
this.notifier, {
this.notifier,
this._labelRepository, {
required this.documentId,
}) : super(const SimilarDocumentsState()) {
notifier.subscribe(
notifier.addListener(
this,
onDeleted: remove,
onUpdated: replace,
);
_labelRepository.addListener(
this,
onChanged: (labels) {
emit(state.copyWith(
correspondents: labels.correspondents,
documentTypes: labels.documentTypes,
tags: labels.tags,
storagePaths: labels.storagePaths,
));
},
);
}
Future<void> initialize() async {
@@ -38,4 +53,11 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
);
}
}
@override
Future<void> close() {
notifier.removeListener(this);
_labelRepository.removeListener(this);
return super.close();
}
}

View File

@@ -1,11 +1,20 @@
part of 'similar_documents_cubit.dart';
class SimilarDocumentsState extends DocumentPagingState {
final Map<int, Correspondent> correspondents;
final Map<int, DocumentType> documentTypes;
final Map<int, Tag> tags;
final Map<int, StoragePath> storagePaths;
const SimilarDocumentsState({
super.filter,
super.hasLoaded,
super.isLoading,
super.value,
this.correspondents = const {},
this.documentTypes = const {},
this.tags = const {},
this.storagePaths = const {},
});
@override
@@ -14,6 +23,10 @@ class SimilarDocumentsState extends DocumentPagingState {
hasLoaded,
isLoading,
value,
correspondents,
documentTypes,
tags,
storagePaths,
];
@override
@@ -36,12 +49,20 @@ class SimilarDocumentsState extends DocumentPagingState {
bool? isLoading,
List<PagedSearchResult<DocumentModel>>? value,
DocumentFilter? filter,
Map<int, Correspondent>? correspondents,
Map<int, DocumentType>? documentTypes,
Map<int, Tag>? tags,
Map<int, StoragePath>? storagePaths,
}) {
return SimilarDocumentsState(
hasLoaded: hasLoaded ?? this.hasLoaded,
isLoading: isLoading ?? this.isLoading,
value: value ?? this.value,
filter: filter ?? this.filter,
correspondents: correspondents ?? this.correspondents,
documentTypes: documentTypes ?? this.documentTypes,
tags: tags ?? this.tags,
storagePaths: storagePaths ?? this.storagePaths,
);
}
}

View File

@@ -78,6 +78,10 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
),
);
},
correspondents: state.correspondents,
documentTypes: state.documentTypes,
tags: state.tags,
storagePaths: state.storagePaths,
);
},
);

View File

@@ -21,7 +21,6 @@ import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.da
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/impl/saved_view_repository_impl.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/security/session_manager.dart';
@@ -101,7 +100,7 @@ void main() async {
// Create repositories
final labelRepository = LabelRepository(labelsApi);
final savedViewRepository = SavedViewRepositoryImpl(savedViewsApi);
final savedViewRepository = SavedViewRepository(savedViewsApi);
//Create cubits/blocs
final authCubit = AuthenticationCubit(