Removed suggestions from inbox, added translations, added paging to inbox, visual updates, changed default matching algorithm to auto

This commit is contained in:
Anton Stubenbord
2023-01-20 00:34:18 +01:00
parent bfbf0a6f0e
commit f9dfddf704
56 changed files with 1748 additions and 766 deletions

View File

@@ -36,7 +36,6 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/pdf" /> <data android:mimeType="application/pdf" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to This is used by the Flutter tool to
@@ -51,4 +50,11 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" /> android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="file" />
</intent>
</queries>
</manifest> </manifest>

View File

@@ -26,6 +26,18 @@ class ConnectivityCubit extends Cubit<ConnectivityState> {
} }
} }
void reload() async {
if (_sub == null) {
initialize();
} else {
final bool isConnected =
await connectivityStatusService.isConnectedToInternet();
emit(isConnected
? ConnectivityState.connected
: ConnectivityState.notConnected);
}
}
@override @override
Future<void> close() { Future<void> close() {
_sub?.cancel(); _sub?.cancel();

View File

@@ -8,8 +8,8 @@ class SavedViewRepositoryImpl extends SavedViewRepository {
SavedViewRepositoryImpl(this._api) : super(const SavedViewRepositoryState()); SavedViewRepositoryImpl(this._api) : super(const SavedViewRepositoryState());
@override @override
Future<SavedView> create(SavedView view) async { Future<SavedView> create(SavedView object) async {
final created = await _api.save(view); final created = await _api.save(object);
final updatedState = {...state.values} final updatedState = {...state.values}
..putIfAbsent(created.id!, () => created); ..putIfAbsent(created.id!, () => created);
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true)); emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));

View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/generated/l10n.dart';
String translateMatchingAlgorithm(
BuildContext context, MatchingAlgorithm algorithm) {
switch (algorithm) {
case MatchingAlgorithm.anyWord:
return S.of(context).matchingAlgorithmAnyDescription;
case MatchingAlgorithm.allWords:
return S.of(context).matchingAlgorithmAllDescription;
case MatchingAlgorithm.exactMatch:
return S.of(context).matchingAlgorithmExactDescription;
case MatchingAlgorithm.regex:
return S.of(context).matchingAlgorithmRegexDescription;
case MatchingAlgorithm.fuzzy:
return S.of(context).matchingAlgorithmFuzzyDescription;
case MatchingAlgorithm.auto:
return S.of(context).matchingAlgorithmAutoDescription;
}
}

View File

@@ -1,6 +1,12 @@
import 'dart:developer';
import 'dart:io';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/service/file_service.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
part 'document_details_state.dart'; part 'document_details_state.dart';
@@ -41,6 +47,23 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
} }
} }
Future<bool> openDocumentInSystemViewer() async {
final downloadDir = await FileService.temporaryDirectory;
final metaData = await _api.getMetaData(state.document);
final docBytes = await _api.download(state.document);
File f = File('${downloadDir.path}/${metaData.mediaFilename}');
f.writeAsBytes(docBytes);
final url = Uri(
scheme: "file",
path: f.path,
);
log(url.toString());
if (await canLaunchUrl(url)) {
return launchUrl(url);
}
return false;
}
void replaceDocument(DocumentModel document) { void replaceDocument(DocumentModel document) {
emit(state.copyWith(document: document)); emit(state.copyWith(document: document));
} }

View File

@@ -8,6 +8,7 @@ import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart'; import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/service/file_service.dart';
import 'package:paperless_mobile/core/widgets/highlighted_text.dart'; import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
import 'package:paperless_mobile/core/widgets/hint_card.dart'; import 'package:paperless_mobile/core/widgets/hint_card.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart'; import 'package:paperless_mobile/core/widgets/offline_widget.dart';
@@ -64,6 +65,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
floatingActionButton: widget.allowEdit floatingActionButton: widget.allowEdit
? BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>( ? BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
builder: (context, state) { builder: (context, state) {
final _filteredSuggestions =
state.suggestions.documentDifference(state.document);
return BlocBuilder<ConnectivityCubit, ConnectivityState>( return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivityState) { builder: (context, connectivityState) {
if (!connectivityState.isConnected) { if (!connectivityState.isConnected) {
@@ -71,13 +74,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
} }
return b.Badge( return b.Badge(
position: b.BadgePosition.topEnd(top: -12, end: -6), position: b.BadgePosition.topEnd(top: -12, end: -6),
showBadge: state.suggestions.hasSuggestions, showBadge: _filteredSuggestions.hasSuggestions,
child: FloatingActionButton( child: FloatingActionButton(
child: const Icon(Icons.edit), child: const Icon(Icons.edit),
onPressed: () => _onEdit(state.document), onPressed: () => _onEdit(state.document),
), ),
badgeContent: Text( badgeContent: Text(
'${state.suggestions.suggestionsCount}', '${_filteredSuggestions.suggestionsCount}',
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
), ),
@@ -106,16 +109,27 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
? () => _onDelete(state.document) ? () => _onDelete(state.document)
: null, : null,
).paddedSymmetrically(horizontal: 4), ).paddedSymmetrically(horizontal: 4),
DocumentDownloadButton( Tooltip(
document: state.document, message: "Download",
enabled: isConnected, child: DocumentDownloadButton(
document: state.document,
enabled: isConnected,
),
), ),
IconButton( IconButton(
icon: const Icon(Icons.open_in_new), icon: const Icon(Icons.visibility),
onPressed: isConnected onPressed: isConnected
? () => _onOpen(state.document) ? () => _onOpen(state.document)
: null, : null,
).paddedOnly(right: 4.0), ).paddedOnly(right: 4.0),
// IconButton(
// icon: const Icon(Icons.open_in_new),
// onPressed: isConnected
// ? context
// .read<DocumentDetailsCubit>()
// .openDocumentInSystemViewer
// : null,
// ).paddedOnly(right: 4.0),
IconButton( IconButton(
icon: const Icon(Icons.share), icon: const Icon(Icons.share),
onPressed: isConnected onPressed: isConnected

View File

@@ -153,12 +153,11 @@ class _DocumentUploadPreparationPageState
S S
.of(context) .of(context)
.documentUploadPageSynchronizeTitleAndFilenameLabel, .documentUploadPageSynchronizeTitleAndFilenameLabel,
), //TODO: INTL ),
), ),
FormBuilderDateTimePicker( FormBuilderDateTimePicker(
enabled: false,
autovalidateMode: AutovalidateMode.always, autovalidateMode: AutovalidateMode.always,
format: DateFormat("dd. MMMM yyyy"), //TODO: INTL format: DateFormat.yMMMMd(),
inputType: InputType.date, inputType: InputType.date,
name: DocumentModel.createdKey, name: DocumentModel.createdKey,
initialValue: null, initialValue: null,
@@ -168,11 +167,6 @@ class _DocumentUploadPreparationPageState
S.of(context).documentCreatedPropertyLabel + " *", S.of(context).documentCreatedPropertyLabel + " *",
), ),
), ),
const HintCard(
hintText:
"Due to an apparent parsing bug with Paperless, setting the 'created at' date will cause the document consumption to fail! Therefore this field is disabled for now until this is fixed or I find a workaround!",
show: true,
),
LabelFormField<DocumentType>( LabelFormField<DocumentType>(
notAssignedSelectable: false, notAssignedSelectable: false,
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,

View File

@@ -5,19 +5,21 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/documents_paging_mixin.dart';
class DocumentsCubit extends HydratedCubit<DocumentsState>
with DocumentsPagingMixin {
@override
final PaperlessDocumentsApi api;
class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
final PaperlessDocumentsApi _api;
final SavedViewRepository _savedViewRepository; final SavedViewRepository _savedViewRepository;
DocumentsCubit(this._api, this._savedViewRepository) DocumentsCubit(this.api, this._savedViewRepository)
: super(const DocumentsState()) { : super(const DocumentsState());
hydrate();
}
Future<void> bulkRemove(List<DocumentModel> documents) async { Future<void> bulkRemove(List<DocumentModel> documents) async {
log("[DocumentsCubit] bulkRemove"); log("[DocumentsCubit] bulkRemove");
await _api.bulkAction( await api.bulkAction(
BulkDeleteAction(documents.map((doc) => doc.id)), BulkDeleteAction(documents.map((doc) => doc.id)),
); );
await reload(); await reload();
@@ -29,7 +31,7 @@ class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
Iterable<int> removeTags = const [], Iterable<int> removeTags = const [],
}) async { }) async {
log("[DocumentsCubit] bulkEditTags"); log("[DocumentsCubit] bulkEditTags");
await _api.bulkAction(BulkModifyTagsAction( await api.bulkAction(BulkModifyTagsAction(
documents.map((doc) => doc.id), documents.map((doc) => doc.id),
addTags: addTags, addTags: addTags,
removeTags: removeTags, removeTags: removeTags,
@@ -37,132 +39,6 @@ class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
await reload(); await reload();
} }
Future<void> update(
DocumentModel document, [
bool updateRemote = true,
]) async {
log("[DocumentsCubit] update");
if (updateRemote) {
await _api.update(document);
}
await reload();
}
Future<void> load() async {
log("[DocumentsCubit] load");
emit(state.copyWith(isLoading: true));
try {
final result = await _api.findAll(state.filter);
emit(state.copyWith(
isLoading: false,
hasLoaded: true,
value: [...state.value, result],
));
} finally {
emit(state.copyWith(isLoading: false));
}
}
Future<void> reload() async {
log("[DocumentsCubit] reload");
emit(state.copyWith(isLoading: true));
try {
final filter = state.filter.copyWith(page: 1);
final result = await _api.findAll(filter);
emit(state.copyWith(
hasLoaded: true,
value: [result],
isLoading: false,
filter: filter,
));
} finally {
emit(state.copyWith(isLoading: false));
}
}
Future<void> _bulkReloadDocuments() async {
emit(state.copyWith(isLoading: true));
try {
final result = await _api.findAll(
state.filter.copyWith(
page: 1,
pageSize: state.documents.length,
),
);
emit(DocumentsState(
hasLoaded: true,
value: [result],
filter: state.filter,
isLoading: false,
));
} finally {
emit(state.copyWith(isLoading: false));
}
}
Future<void> loadMore() async {
log("[DocumentsCubit] loadMore");
if (state.isLastPageLoaded) {
return;
}
emit(state.copyWith(isLoading: true));
final newFilter = state.filter.copyWith(page: state.filter.page + 1);
try {
final result = await _api.findAll(newFilter);
emit(
DocumentsState(
hasLoaded: true,
value: [...state.value, result],
filter: newFilter,
isLoading: false,
),
);
} finally {
emit(state.copyWith(isLoading: false));
}
}
///
/// Updates document filter and automatically reloads documents. Always resets page to 1.
/// Use [loadMore] to load more data.
Future<void> updateFilter({
final DocumentFilter filter = DocumentFilter.initial,
}) async {
log("[DocumentsCubit] updateFilter");
try {
emit(state.copyWith(isLoading: true));
final result = await _api.findAll(filter.copyWith(page: 1));
emit(
DocumentsState(
filter: filter,
value: [result],
hasLoaded: true,
isLoading: false,
),
);
} finally {
emit(state.copyWith(isLoading: false));
}
}
Future<void> resetFilter() {
log("[DocumentsCubit] resetFilter");
final filter = DocumentFilter.initial.copyWith(
sortField: state.filter.sortField,
sortOrder: state.filter.sortOrder,
);
return updateFilter(filter: filter);
}
///
/// Convenience method which allows to directly use [DocumentFilter.copyWith] on the current filter.
///
Future<void> updateCurrentFilter(
final DocumentFilter Function(DocumentFilter) transformFn,
) async =>
updateFilter(filter: transformFn(state.filter));
void toggleDocumentSelection(DocumentModel model) { void toggleDocumentSelection(DocumentModel model) {
log("[DocumentsCubit] toggleSelection"); log("[DocumentsCubit] toggleSelection");
if (state.selectedIds.contains(model.id)) { if (state.selectedIds.contains(model.id)) {
@@ -185,12 +61,6 @@ class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
emit(state.copyWith(selection: [])); emit(state.copyWith(selection: []));
} }
@override
void onChange(Change<DocumentsState> change) {
super.onChange(change);
log("[DocumentsCubit] state changed: ${change.currentState.selection.map((e) => e.id).join(",")} to ${change.nextState.selection.map((e) => e.id).join(",")}");
}
void reset() { void reset() {
log("[DocumentsCubit] reset"); log("[DocumentsCubit] reset");
emit(const DocumentsState()); emit(const DocumentsState());
@@ -204,7 +74,7 @@ class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
if (filter == null) { if (filter == null) {
return; return;
} }
final results = await _api.findAll(filter.copyWith(page: 1)); final results = await api.findAll(filter.copyWith(page: 1));
emit( emit(
DocumentsState( DocumentsState(
filter: filter, filter: filter,
@@ -220,7 +90,7 @@ class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
} }
Future<Iterable<String>> autocomplete(String query) async { Future<Iterable<String>> autocomplete(String query) async {
final res = await _api.autocomplete(query); final res = await api.autocomplete(query);
return res; return res;
} }

View File

@@ -1,72 +1,25 @@
import 'dart:developer';
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/paged_document_view/model/documents_paged_state.dart';
class DocumentsState extends Equatable { class DocumentsState extends DocumentsPagedState {
final bool isLoading;
final bool hasLoaded;
final DocumentFilter filter;
final List<PagedSearchResult<DocumentModel>> value;
final int? selectedSavedViewId; final int? selectedSavedViewId;
@JsonKey(ignore: true) @JsonKey(ignore: true)
final List<DocumentModel> selection; final List<DocumentModel> selection;
const DocumentsState({ const DocumentsState({
this.hasLoaded = false,
this.isLoading = false,
this.value = const [],
this.filter = const DocumentFilter(),
this.selection = const [], this.selection = const [],
this.selectedSavedViewId, this.selectedSavedViewId,
super.value = const [],
super.filter = const DocumentFilter(),
super.hasLoaded = false,
super.isLoading = false,
}); });
List<int> get selectedIds => selection.map((e) => e.id).toList(); List<int> get selectedIds => selection.map((e) => e.id).toList();
int get currentPageNumber {
return filter.page;
}
int? get nextPageNumber {
return isLastPageLoaded ? null : currentPageNumber + 1;
}
int get count {
if (value.isEmpty) {
return 0;
}
return value.first.count;
}
bool get isLastPageLoaded {
if (!hasLoaded) {
return false;
}
if (value.isNotEmpty) {
return value.last.next == null;
}
return true;
}
int inferPageCount({required int pageSize}) {
if (!hasLoaded) {
return 100000;
}
if (value.isEmpty) {
return 0;
}
return value.first.inferPageCount(pageSize: pageSize);
}
List<DocumentModel> get documents {
return value.fold(
[], (previousValue, element) => [...previousValue, ...element.results]);
}
DocumentsState copyWith({ DocumentsState copyWith({
bool overwrite = false,
bool? hasLoaded, bool? hasLoaded,
bool? isLoading, bool? isLoading,
List<PagedSearchResult<DocumentModel>>? value, List<PagedSearchResult<DocumentModel>>? value,
@@ -118,4 +71,19 @@ class DocumentsState extends Equatable {
filter: DocumentFilter.fromJson(json['filter']), filter: DocumentFilter.fromJson(json['filter']),
); );
} }
@override
DocumentsState copyWithPaged({
bool? hasLoaded,
bool? isLoading,
List<PagedSearchResult<DocumentModel>>? value,
DocumentFilter? filter,
}) {
return copyWith(
filter: filter,
hasLoaded: hasLoaded,
isLoading: isLoading,
value: value,
);
}
} }

View File

@@ -16,8 +16,6 @@ import 'package:paperless_mobile/features/edit_document/cubit/edit_document_cubi
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_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/label_state.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.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/labels/view/widgets/label_form_field.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
@@ -45,6 +43,15 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
final GlobalKey<FormBuilderState> _formKey = GlobalKey(); final GlobalKey<FormBuilderState> _formKey = GlobalKey();
bool _isSubmitLoading = false; bool _isSubmitLoading = false;
late final FieldSuggestions _filteredSuggestions;
@override
void initState() {
super.initState();
_filteredSuggestions = widget.suggestions
.documentDifference(context.read<EditDocumentCubit>().state.document);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<EditDocumentCubit, EditDocumentState>( return BlocBuilder<EditDocumentCubit, EditDocumentState>(
@@ -99,25 +106,35 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
excludeAllowed: false, excludeAllowed: false,
name: fkTags, name: fkTags,
selectableOptions: state.tags, selectableOptions: state.tags,
suggestions: widget.suggestions.hasSuggestedTags suggestions: _filteredSuggestions.tags
.toSet()
.difference(state.document.tags.toSet())
.isNotEmpty
? _buildSuggestionsSkeleton<int>( ? _buildSuggestionsSkeleton<int>(
suggestions: widget.suggestions.storagePaths, suggestions: _filteredSuggestions.tags,
itemBuilder: (context, itemData) => ActionChip( itemBuilder: (context, itemData) {
label: Text(state.tags[itemData]!.name), final tag = state.tags[itemData]!;
onPressed: () { return ActionChip(
final currentTags = _formKey.currentState label: Text(
?.fields[fkTags] as TagsQuery; tag.name,
if (currentTags is IdsTagsQuery) { style: TextStyle(color: tag.textColor),
_formKey.currentState?.fields[fkTags] ),
?.didChange((IdsTagsQuery.fromIds( backgroundColor: tag.color,
[...currentTags.ids, itemData]))); onPressed: () {
} else { final currentTags = _formKey.currentState
_formKey.currentState?.fields[fkTags] ?.fields[fkTags]?.value as TagsQuery;
?.didChange( if (currentTags is IdsTagsQuery) {
(IdsTagsQuery.fromIds([itemData]))); _formKey.currentState?.fields[fkTags]
} ?.didChange((IdsTagsQuery.fromIds(
}, {...currentTags.ids, itemData})));
), } else {
_formKey.currentState?.fields[fkTags]
?.didChange((IdsTagsQuery.fromIds(
{itemData})));
}
},
);
},
) )
: null, : null,
).padded(), ).padded(),
@@ -151,15 +168,6 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
name: fkStoragePath, name: fkStoragePath,
prefixIcon: const Icon(Icons.folder_outlined), prefixIcon: const Icon(Icons.folder_outlined),
), ),
if (widget.suggestions.hasSuggestedStoragePaths)
_buildSuggestionsSkeleton<int>(
suggestions: widget.suggestions.storagePaths,
itemBuilder: (context, itemData) => ActionChip(
label: Text(options[itemData]!.name),
onPressed: () => _formKey.currentState?.fields[fkStoragePath]
?.didChange((IdQueryParameter.fromId(itemData))),
),
),
], ],
); );
} }
@@ -182,9 +190,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
name: fkCorrespondent, name: fkCorrespondent,
prefixIcon: const Icon(Icons.person_outlined), prefixIcon: const Icon(Icons.person_outlined),
), ),
if (widget.suggestions.hasSuggestedCorrespondents) if (_filteredSuggestions.hasSuggestedCorrespondents)
_buildSuggestionsSkeleton<int>( _buildSuggestionsSkeleton<int>(
suggestions: widget.suggestions.correspondents, suggestions: _filteredSuggestions.correspondents,
itemBuilder: (context, itemData) => ActionChip( itemBuilder: (context, itemData) => ActionChip(
label: Text(options[itemData]!.name), label: Text(options[itemData]!.name),
onPressed: () => _formKey.currentState?.fields[fkCorrespondent] onPressed: () => _formKey.currentState?.fields[fkCorrespondent]
@@ -217,9 +225,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
name: fkDocumentType, name: fkDocumentType,
prefixIcon: const Icon(Icons.description_outlined), prefixIcon: const Icon(Icons.description_outlined),
), ),
if (widget.suggestions.hasSuggestedDocumentTypes) if (_filteredSuggestions.hasSuggestedDocumentTypes)
_buildSuggestionsSkeleton<int>( _buildSuggestionsSkeleton<int>(
suggestions: widget.suggestions.documentTypes, suggestions: _filteredSuggestions.documentTypes,
itemBuilder: (context, itemData) => ActionChip( itemBuilder: (context, itemData) => ActionChip(
label: Text(options[itemData]!.name), label: Text(options[itemData]!.name),
onPressed: () => _formKey.currentState?.fields[fkDocumentType] onPressed: () => _formKey.currentState?.fields[fkDocumentType]
@@ -236,13 +244,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
var mergedDocument = document.copyWith( var mergedDocument = document.copyWith(
title: values[fkTitle], title: values[fkTitle],
created: values[fkCreatedDate], created: values[fkCreatedDate],
overwriteDocumentType: true, documentType: () => (values[fkDocumentType] as IdQueryParameter).id,
documentType: (values[fkDocumentType] as IdQueryParameter).id, correspondent: () => (values[fkCorrespondent] as IdQueryParameter).id,
overwriteCorrespondent: true, storagePath: () => (values[fkStoragePath] as IdQueryParameter).id,
correspondent: (values[fkCorrespondent] as IdQueryParameter).id,
overwriteStoragePath: true,
storagePath: (values[fkStoragePath] as IdQueryParameter).id,
overwriteTags: true,
tags: (values[fkTags] as IdsTagsQuery).includedIds, tags: (values[fkTags] as IdsTagsQuery).includedIds,
); );
setState(() { setState(() {
@@ -288,9 +292,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
format: DateFormat("dd. MMMM yyyy"), //TODO: Localized date format format: DateFormat("dd. MMMM yyyy"), //TODO: Localized date format
initialEntryMode: DatePickerEntryMode.calendar, initialEntryMode: DatePickerEntryMode.calendar,
), ),
if (widget.suggestions.hasSuggestedDates) if (_filteredSuggestions.hasSuggestedDates)
_buildSuggestionsSkeleton<DateTime>( _buildSuggestionsSkeleton<DateTime>(
suggestions: widget.suggestions.dates, suggestions: _filteredSuggestions.dates,
itemBuilder: (context, itemData) => ActionChip( itemBuilder: (context, itemData) => ActionChip(
label: Text(DateFormat.yMMMd().format(itemData)), label: Text(DateFormat.yMMMd().format(itemData)),
onPressed: () => _formKey.currentState?.fields[fkCreatedDate] onPressed: () => _formKey.currentState?.fields[fkCreatedDate]

View File

@@ -63,13 +63,13 @@ class _DocumentsPageState extends State<DocumentsPage> {
} }
_scrollController _scrollController
..addListener(_listenForScrollChanges) ..addListener(_listenForScrollChanges)
..addListener(_listenForLoadDataTrigger); ..addListener(_listenForLoadNewData);
} }
void _listenForLoadDataTrigger() { void _listenForLoadNewData() {
final currState = context.read<DocumentsCubit>().state; final currState = context.read<DocumentsCubit>().state;
if (_scrollController.offset >= if (_scrollController.offset >=
_scrollController.position.maxScrollExtent && _scrollController.position.maxScrollExtent * 0.75 &&
!currState.isLoading && !currState.isLoading &&
!currState.isLastPageLoaded) { !currState.isLastPageLoaded) {
_loadNewPage(); _loadNewPage();
@@ -173,7 +173,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight( preferredSize: const Size.fromHeight(
linearProgressIndicatorHeight), linearProgressIndicatorHeight),
child: state.isLoading child: state.isLoading && state.hasLoaded
? const LinearProgressIndicator() ? const LinearProgressIndicator()
: const SizedBox(height: 4.0), : const SizedBox(height: 4.0),
), ),

View File

@@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@@ -108,8 +110,8 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
context.read<EditLabelCubit<T>>().delete(label); context.read<EditLabelCubit<T>>().delete(label);
} on PaperlessServerException catch (error) { } on PaperlessServerException catch (error) {
showErrorMessage(context, error); showErrorMessage(context, error);
} catch (error) { } catch (error, stackTrace) {
print(error); log("An error occurred!", error: error, stackTrace: stackTrace);
} }
Navigator.pop(context); Navigator.pop(context);
} }

View File

@@ -1,8 +1,8 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
import 'package:paperless_mobile/core/type/types.dart'; import 'package:paperless_mobile/core/type/types.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
@@ -81,10 +81,9 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
onChanged: (val) => setState(() => _errors = {}), onChanged: (val) => setState(() => _errors = {}),
), ),
FormBuilderDropdown<int?>( FormBuilderDropdown<int?>(
//TODO: Extract to own widget.
name: Label.matchingAlgorithmKey, name: Label.matchingAlgorithmKey,
initialValue: widget.initialValue?.matchingAlgorithm?.value ?? initialValue: widget.initialValue?.matchingAlgorithm.value ??
MatchingAlgorithm.allWords.value, MatchingAlgorithm.auto.value,
decoration: InputDecoration( decoration: InputDecoration(
labelText: S.of(context).labelMatchingAlgorithmPropertyLabel, labelText: S.of(context).labelMatchingAlgorithmPropertyLabel,
errorText: _errors[Label.matchingAlgorithmKey], errorText: _errors[Label.matchingAlgorithmKey],
@@ -93,7 +92,7 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
items: MatchingAlgorithm.values items: MatchingAlgorithm.values
.map( .map(
(algo) => DropdownMenuItem<int?>( (algo) => DropdownMenuItem<int?>(
child: Text(algo.name), //TODO: INTL child: Text(translateMatchingAlgorithm(context, algo)),
value: algo.value, value: algo.value,
), ),
) )

View File

@@ -8,7 +8,7 @@ import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart'; import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/global/constants.dart'; import 'package:paperless_mobile/core/global/constants.dart';
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart'; import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart'; import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
@@ -50,6 +50,7 @@ class _HomePageState extends State<HomePage> {
void initState() { void initState() {
super.initState(); super.initState();
_initializeData(context); _initializeData(context);
context.read<ConnectivityCubit>().reload();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_listenForReceivedFiles(); _listenForReceivedFiles();
}); });

View File

@@ -361,7 +361,7 @@ class _InfoDrawerState extends State<InfoDrawer> {
applicationName: 'Paperless Mobile', applicationName: 'Paperless Mobile',
applicationVersion: snapshot.version + '+' + snapshot.buildNumber, applicationVersion: snapshot.version + '+' + snapshot.buildNumber,
children: [ children: [
Text('${S.of(context).aboutDialogDevelopedByText} Anton Stubenbord'), Text(S.of(context).aboutDialogDevelopedByText('Anton Stubenbord')),
Link( Link(
uri: Uri.parse('https://github.com/astubenbord/paperless-mobile'), uri: Uri.parse('https://github.com/astubenbord/paperless-mobile'),
builder: (context, followLink) => GestureDetector( builder: (context, followLink) => GestureDetector(

View File

@@ -1,7 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
@@ -9,8 +7,9 @@ import 'package:paperless_mobile/core/repository/state/impl/correspondent_reposi
import 'package:paperless_mobile/core/repository/state/impl/document_type_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'; import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart'; import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
import 'package:paperless_mobile/features/paged_document_view/documents_paging_mixin.dart';
class InboxCubit extends HydratedCubit<InboxState> { class InboxCubit extends HydratedCubit<InboxState> with DocumentsPagingMixin {
final LabelRepository<Tag, TagRepositoryState> _tagsRepository; final LabelRepository<Tag, TagRepositoryState> _tagsRepository;
final LabelRepository<Correspondent, CorrespondentRepositoryState> final LabelRepository<Correspondent, CorrespondentRepositoryState>
_correspondentRepository; _correspondentRepository;
@@ -21,6 +20,9 @@ class InboxCubit extends HydratedCubit<InboxState> {
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
@override
PaperlessDocumentsApi get api => _documentsApi;
InboxCubit( InboxCubit(
this._tagsRepository, this._tagsRepository,
this._documentsApi, this._documentsApi,
@@ -67,105 +69,83 @@ class InboxCubit extends HydratedCubit<InboxState> {
final inboxTags = await _tagsRepository.findAll().then( final inboxTags = await _tagsRepository.findAll().then(
(tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!), (tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!),
); );
if (inboxTags.isEmpty) { if (inboxTags.isEmpty) {
// no inbox tags = no inbox items. // no inbox tags = no inbox items.
return emit( return emit(
state.copyWith( state.copyWith(
isLoaded: true, hasLoaded: true,
inboxItems: [], value: [],
inboxTags: [], inboxTags: [],
), ),
); );
} }
final inboxDocuments = await _documentsApi return updateFilter(
.findAll(DocumentFilter( filter: DocumentFilter(
tags: AnyAssignedTagsQuery(tagIds: inboxTags), sortField: SortField.added,
sortField: SortField.added, tags: IdsTagsQuery.fromIds(inboxTags),
)) ),
.then((psr) => psr.results);
final newState = state.copyWith(
isLoaded: true,
inboxItems: inboxDocuments,
inboxTags: inboxTags,
); );
emit(newState);
} }
/// ///
/// Updates the document with all inbox tags removed and removes the document /// Updates the document with all inbox tags removed and removes the document
/// from the currently loaded inbox documents. /// from the inbox.
/// ///
Future<Iterable<int>> remove(DocumentModel document) async { Future<Iterable<int>> removeFromInbox(DocumentModel document) async {
final tagsToRemove = final tagsToRemove =
document.tags.toSet().intersection(state.inboxTags.toSet()); document.tags.toSet().intersection(state.inboxTags.toSet());
final updatedTags = {...document.tags}..removeAll(tagsToRemove); final updatedTags = {...document.tags}..removeAll(tagsToRemove);
await api.update(
await _documentsApi.update( document.copyWith(tags: updatedTags),
document.copyWith(
tags: updatedTags,
overwriteTags: true,
),
); );
emit( await remove(document);
state.copyWith(
isLoaded: true,
inboxItems: state.inboxItems.where((doc) => doc.id != document.id),
),
);
return tagsToRemove; return tagsToRemove;
} }
/// ///
/// Adds the previously removed tags to the document and performs an update. /// Adds the previously removed tags to the document and performs an update.
/// ///
Future<void> undoRemove( Future<void> undoRemoveFromInbox(
DocumentModel document, DocumentModel document,
Iterable<int> removedTags, Iterable<int> removedTags,
) async { ) async {
final updatedDoc = document.copyWith( final updatedDoc = document.copyWith(
tags: {...document.tags, ...removedTags}, tags: {...document.tags, ...removedTags},
overwriteTags: true,
); );
await _documentsApi.update(updatedDoc); await _documentsApi.update(updatedDoc);
emit(state.copyWith( return reload();
isLoaded: true,
inboxItems: [...state.inboxItems, updatedDoc]
..sort((d1, d2) => d2.added.compareTo(d1.added)),
));
} }
/// ///
/// Removes inbox tags from all documents in the inbox. /// Removes inbox tags from all documents in the inbox.
/// ///
Future<void> clearInbox() async { Future<void> clearInbox() async {
await _documentsApi.bulkAction( emit(state.copyWith(isLoading: true));
BulkModifyTagsAction.removeTags( try {
state.inboxItems.map((e) => e.id), await _documentsApi.bulkAction(
state.inboxTags, BulkModifyTagsAction.removeTags(
), state.documents.map((e) => e.id),
); state.inboxTags,
emit(state.copyWith( ),
isLoaded: true, );
inboxItems: [], emit(state.copyWith(
)); hasLoaded: true,
value: [],
));
} finally {
emit(state.copyWith(isLoading: false));
}
} }
void replaceUpdatedDocument(DocumentModel document) { void replaceUpdatedDocument(DocumentModel document) {
if (document.tags.any((id) => state.inboxTags.contains(id))) { if (document.tags.any((id) => state.inboxTags.contains(id))) {
// If replaced document still has inbox tag assigned: // If replaced document still has inbox tag assigned:
emit(state.copyWith( replace(document);
inboxItems:
state.inboxItems.map((e) => e.id == document.id ? document : e),
));
} else { } else {
// Remove tag from inbox. // Remove document from inbox.
emit( remove(document);
state.copyWith(
inboxItems:
state.inboxItems.where((element) => element.id != document.id)),
);
} }
} }
@@ -174,48 +154,10 @@ class InboxCubit extends HydratedCubit<InboxState> {
final int asn = await _documentsApi.findNextAsn(); final int asn = await _documentsApi.findNextAsn();
final updatedDocument = await _documentsApi final updatedDocument = await _documentsApi
.update(document.copyWith(archiveSerialNumber: asn)); .update(document.copyWith(archiveSerialNumber: asn));
emit( replace(updatedDocument);
state.copyWith(
inboxItems: state.inboxItems
.map((e) => e.id == document.id ? updatedDocument : e)),
);
} }
} }
Future<void> updateDocument(DocumentModel document) async {
final updatedDocument = await _documentsApi.update(document);
emit(
state.copyWith(
inboxItems: state.inboxItems.map(
(e) => e.id == document.id ? updatedDocument : e,
),
),
);
}
Future<void> deleteDocument(DocumentModel document) async {
int deletedId = await _documentsApi.delete(document);
emit(
state.copyWith(
inboxItems: state.inboxItems.where(
(element) => element.id != deletedId,
),
),
);
}
void loadSuggestions() {
state.inboxItems
.whereNot((doc) => state.suggestions.containsKey(doc.id))
.map((e) => _documentsApi.findSuggestions(e))
.forEach((suggestion) async {
final s = await suggestion;
emit(state.copyWith(
suggestions: {...state.suggestions, s.documentId!: s},
));
});
}
void acknowledgeHint() { void acknowledgeHint() {
emit(state.copyWith(isHintAcknowledged: true)); emit(state.copyWith(isHintAcknowledged: true));
} }

View File

@@ -1,57 +1,56 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:paperless_mobile/features/paged_document_view/model/documents_paged_state.dart';
part 'inbox_state.g.dart'; part 'inbox_state.g.dart';
@JsonSerializable( @JsonSerializable(
ignoreUnannotated: true, ignoreUnannotated: true,
) )
class InboxState with EquatableMixin { class InboxState extends DocumentsPagedState {
final bool isLoaded;
final Iterable<int> inboxTags; final Iterable<int> inboxTags;
final Iterable<DocumentModel> inboxItems;
final Map<int, Tag> availableTags; final Map<int, Tag> availableTags;
final Map<int, DocumentType> availableDocumentTypes; final Map<int, DocumentType> availableDocumentTypes;
final Map<int, Correspondent> availableCorrespondents; final Map<int, Correspondent> availableCorrespondents;
final Map<int, FieldSuggestions> suggestions;
@JsonKey() @JsonKey()
final bool isHintAcknowledged; final bool isHintAcknowledged;
const InboxState({ const InboxState({
this.isLoaded = false, super.hasLoaded = false,
super.isLoading = false,
super.value = const [],
super.filter = const DocumentFilter(),
this.inboxTags = const [], this.inboxTags = const [],
this.inboxItems = const [],
this.isHintAcknowledged = false, this.isHintAcknowledged = false,
this.availableTags = const {}, this.availableTags = const {},
this.availableDocumentTypes = const {}, this.availableDocumentTypes = const {},
this.availableCorrespondents = const {}, this.availableCorrespondents = const {},
this.suggestions = const {},
}); });
@override @override
List<Object?> get props => [ List<Object?> get props => [
isLoaded, hasLoaded,
isLoading,
value,
filter,
inboxTags, inboxTags,
inboxItems, documents,
isHintAcknowledged, isHintAcknowledged,
availableTags, availableTags,
availableDocumentTypes, availableDocumentTypes,
availableCorrespondents, availableCorrespondents,
suggestions,
]; ];
InboxState copyWith({ InboxState copyWith({
bool? isLoaded, bool? hasLoaded,
bool? isLoading,
Iterable<int>? inboxTags, Iterable<int>? inboxTags,
Iterable<DocumentModel>? inboxItems, List<PagedSearchResult<DocumentModel>>? value,
DocumentFilter? filter,
bool? isHintAcknowledged, bool? isHintAcknowledged,
Map<int, Tag>? availableTags, Map<int, Tag>? availableTags,
Map<int, Correspondent>? availableCorrespondents, Map<int, Correspondent>? availableCorrespondents,
@@ -59,8 +58,9 @@ class InboxState with EquatableMixin {
Map<int, FieldSuggestions>? suggestions, Map<int, FieldSuggestions>? suggestions,
}) { }) {
return InboxState( return InboxState(
isLoaded: isLoaded ?? this.isLoaded, hasLoaded: hasLoaded ?? super.hasLoaded,
inboxItems: inboxItems ?? this.inboxItems, isLoading: isLoading ?? super.isLoading,
value: value ?? super.value,
inboxTags: inboxTags ?? this.inboxTags, inboxTags: inboxTags ?? this.inboxTags,
isHintAcknowledged: isHintAcknowledged ?? this.isHintAcknowledged, isHintAcknowledged: isHintAcknowledged ?? this.isHintAcknowledged,
availableCorrespondents: availableCorrespondents:
@@ -68,7 +68,7 @@ class InboxState with EquatableMixin {
availableDocumentTypes: availableDocumentTypes:
availableDocumentTypes ?? this.availableDocumentTypes, availableDocumentTypes ?? this.availableDocumentTypes,
availableTags: availableTags ?? this.availableTags, availableTags: availableTags ?? this.availableTags,
suggestions: suggestions ?? this.suggestions, filter: filter ?? super.filter,
); );
} }
@@ -76,4 +76,20 @@ class InboxState with EquatableMixin {
_$InboxStateFromJson(json); _$InboxStateFromJson(json);
Map<String, dynamic> toJson() => _$InboxStateToJson(this); Map<String, dynamic> toJson() => _$InboxStateToJson(this);
@override
InboxState copyWithPaged({
bool? hasLoaded,
bool? isLoading,
List<PagedSearchResult<DocumentModel>>? value,
DocumentFilter?
filter, // Ignored as filter does not change while inbox is open
}) {
return copyWith(
hasLoaded: hasLoaded,
isLoading: isLoading,
value: value,
filter: filter,
);
}
} }

View File

@@ -23,71 +23,104 @@ class InboxPage extends StatefulWidget {
} }
class _InboxPageState extends State<InboxPage> { class _InboxPageState extends State<InboxPage> {
final ScrollController _scrollController = ScrollController();
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>(); final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initializeDateFormatting(); _scrollController.addListener(_listenForLoadNewData);
}
@override
void dispose() {
_scrollController.removeListener(_listenForLoadNewData);
super.dispose();
}
void _listenForLoadNewData() {
final currState = context.read<InboxCubit>().state;
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent * 0.75 &&
!currState.isLoading &&
!currState.isLastPageLoaded) {
try {
context.read<InboxCubit>().loadMore();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const _progressBarHeight = 4.0;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: PreferredSize(
title: Text(S.of(context).bottomNavInboxPageLabel), preferredSize:
leading: IconButton( const Size.fromHeight(kToolbarHeight + _progressBarHeight),
icon: const Icon(Icons.close), child: BlocBuilder<InboxCubit, InboxState>(
onPressed: () => Navigator.pop(context), builder: (context, state) {
return AppBar(
title: Text(S.of(context).bottomNavInboxPageLabel),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
actions: [
if (state.hasLoaded)
Align(
alignment: Alignment.centerRight,
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: ColoredBox(
color: Theme.of(context).colorScheme.secondaryContainer,
child: Text(
state.value.isEmpty
? '0'
: '${state.value.first.count} ' +
S.of(context).inboxPageUnseenText,
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.bodySmall,
).paddedSymmetrically(horizontal: 4.0),
),
),
).paddedSymmetrically(horizontal: 8)
],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(4),
child: state.isLoading && state.hasLoaded
? const LinearProgressIndicator()
: const SizedBox(height: _progressBarHeight),
),
);
},
), ),
actions: [
BlocBuilder<InboxCubit, InboxState>(
builder: (context, state) {
return Align(
alignment: Alignment.centerRight,
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: ColoredBox(
color: Theme.of(context).colorScheme.secondaryContainer,
child: Text(
'${state.inboxItems.length} ${S.of(context).inboxPageUnseenText}',
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.bodySmall,
).paddedSymmetrically(horizontal: 4.0),
),
),
);
},
).paddedSymmetrically(horizontal: 8)
],
), ),
floatingActionButton: BlocBuilder<InboxCubit, InboxState>( floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
builder: (context, state) { builder: (context, state) {
if (!state.isLoaded || state.inboxItems.isEmpty) { if (!state.hasLoaded || state.documents.isEmpty) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return FloatingActionButton.extended( return FloatingActionButton.extended(
label: Text(S.of(context).inboxPageMarkAllAsSeenLabel), label: Text(S.of(context).inboxPageMarkAllAsSeenLabel),
icon: const Icon(Icons.done_all), icon: const Icon(Icons.done_all),
onPressed: state.isLoaded && state.inboxItems.isNotEmpty onPressed: state.hasLoaded && state.documents.isNotEmpty
? () => _onMarkAllAsSeen( ? () => _onMarkAllAsSeen(
state.inboxItems, state.documents,
state.inboxTags, state.inboxTags,
) )
: null, : null,
); );
}, },
), ),
body: BlocConsumer<InboxCubit, InboxState>( body: BlocBuilder<InboxCubit, InboxState>(
listenWhen: (previous, current) =>
!previous.isLoaded && current.isLoaded,
listener: (context, state) =>
context.read<InboxCubit>().loadSuggestions(),
builder: (context, state) { builder: (context, state) {
if (!state.isLoaded) { if (!state.hasLoaded) {
return const DocumentsListLoadingWidget(); return const DocumentsListLoadingWidget();
} }
if (state.inboxItems.isEmpty) { if (state.documents.isEmpty) {
return InboxEmptyWidget( return InboxEmptyWidget(
emptyStateRefreshIndicatorKey: _emptyStateRefreshIndicatorKey, emptyStateRefreshIndicatorKey: _emptyStateRefreshIndicatorKey,
); );
@@ -95,7 +128,7 @@ class _InboxPageState extends State<InboxPage> {
// Build a list of slivers alternating between SliverToBoxAdapter // Build a list of slivers alternating between SliverToBoxAdapter
// (group header) and a SliverList (inbox items). // (group header) and a SliverList (inbox items).
final List<Widget> slivers = _groupByDate(state.inboxItems) final List<Widget> slivers = _groupByDate(state.documents)
.entries .entries
.map( .map(
(entry) => [ (entry) => [
@@ -148,6 +181,7 @@ class _InboxPageState extends State<InboxPage> {
children: [ children: [
Expanded( Expanded(
child: CustomScrollView( child: CustomScrollView(
controller: _scrollController,
slivers: [ slivers: [
SliverToBoxAdapter( SliverToBoxAdapter(
child: HintCard( child: HintCard(
@@ -157,7 +191,7 @@ class _InboxPageState extends State<InboxPage> {
context.read<InboxCubit>().acknowledgeHint(), context.read<InboxCubit>().acknowledgeHint(),
), ),
), ),
...slivers ...slivers,
], ],
), ),
), ),
@@ -234,7 +268,7 @@ class _InboxPageState extends State<InboxPage> {
Future<bool> _onItemDismissed(DocumentModel doc) async { Future<bool> _onItemDismissed(DocumentModel doc) async {
try { try {
final removedTags = await context.read<InboxCubit>().remove(doc); final removedTags = await context.read<InboxCubit>().removeFromInbox(doc);
showSnackBar( showSnackBar(
context, context,
S.of(context).inboxPageDocumentRemovedMessageText, S.of(context).inboxPageDocumentRemovedMessageText,
@@ -261,7 +295,9 @@ class _InboxPageState extends State<InboxPage> {
Iterable<int> removedTags, Iterable<int> removedTags,
) async { ) async {
try { try {
await context.read<InboxCubit>().undoRemove(document, removedTags); await context
.read<InboxCubit>()
.undoRemoveFromInbox(document, removedTags);
} on PaperlessServerException catch (error, stackTrace) { } on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} }

View File

@@ -1,25 +1,18 @@
import 'dart:developer';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart'; import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.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/document_type_repository_state.dart';
import 'package:paperless_mobile/extensions/date_time_extensions.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart'; import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.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/documents/view/widgets/document_preview.dart';
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart'; import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart'; import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart'; import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart';
class InboxItem extends StatefulWidget { class InboxItem extends StatefulWidget {
static const _a4AspectRatio = 1 / 1.4142; static const _a4AspectRatio = 1 / 1.4142;
@@ -37,6 +30,8 @@ class InboxItem extends StatefulWidget {
} }
class _InboxItemState extends State<InboxItem> { class _InboxItemState extends State<InboxItem> {
// late final Future<FieldSuggestions> _fieldSuggestions;
bool _isAsnAssignLoading = false; bool _isAsnAssignLoading = false;
@override @override
@@ -65,7 +60,7 @@ class _InboxItemState extends State<InboxItem> {
} }
}, },
child: SizedBox( child: SizedBox(
height: 180, height: 200,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -128,54 +123,66 @@ class _InboxItemState extends State<InboxItem> {
) ?? ) ??
false; false;
if (shouldDelete) { if (shouldDelete) {
context.read<InboxCubit>().deleteDocument(widget.document); context.read<InboxCubit>().delete(widget.document);
} }
}, },
), ),
]; ];
return BlocBuilder<InboxCubit, InboxState>( // return FutureBuilder<FieldSuggestions>(
builder: (context, state) { // future: _fieldSuggestions,
return Row( // builder: (context, snapshot) {
// List<Widget>? suggestions;
// if (!snapshot.hasData) {
// suggestions = [
// const SizedBox(width: 4),
// ];
// } else {
// if (snapshot.data!.hasSuggestions) {
// suggestions = [
// const SizedBox(width: 4),
// ..._buildSuggestionChips(
// chipShape,
// snapshot.data!,
// context.watch<InboxCubit>().state,
// ),
// ];
// }
// }
return Row(
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( const Icon(Icons.bolt_outlined),
mainAxisSize: MainAxisSize.min, SizedBox(
children: [ width: 40,
const Icon(Icons.bolt_outlined), child: Text(
SizedBox( S.of(context).inboxPageQuickActionsLabel,
width: 40, textAlign: TextAlign.center,
child: Text( maxLines: 2,
S.of(context).inboxPageQuickActionsLabel, style: Theme.of(context).textTheme.labelSmall,
textAlign: TextAlign.center,
maxLines: 2,
style: Theme.of(context).textTheme.labelSmall,
),
),
const VerticalDivider(
indent: 16,
endIndent: 16,
),
],
),
const SizedBox(width: 4.0),
Expanded(
child: ListView(
scrollDirection: Axis.horizontal,
children: [
...actions,
if (state.suggestions[widget.document.id] != null) ...[
const SizedBox(width: 4),
..._buildSuggestionChips(
chipShape,
state.suggestions[widget.document.id]!,
state,
)
]
],
), ),
), ),
const VerticalDivider(
indent: 16,
endIndent: 16,
),
], ],
); ),
}, const SizedBox(width: 4.0),
Expanded(
child: ListView(
scrollDirection: Axis.horizontal,
children: [
...actions,
// if (suggestions != null) ...suggestions,
],
),
),
],
// );
// },
); );
} }
@@ -274,97 +281,103 @@ class _InboxItemState extends State<InboxItem> {
); );
} }
List<Widget> _buildSuggestionChips( // List<Widget> _buildSuggestionChips(
OutlinedBorder chipShape, // OutlinedBorder chipShape,
FieldSuggestions suggestions, // FieldSuggestions suggestions,
InboxState state, // InboxState state,
) { // ) {
return [ // return [
...suggestions.correspondents // ...suggestions.correspondents
.whereNot((e) => widget.document.correspondent == e) // .whereNot((e) => widget.document.correspondent == e)
.map( // .map(
(e) => ActionChip( // (e) => ActionChip(
avatar: const Icon(Icons.person_outline), // avatar: const Icon(Icons.person_outline),
shape: chipShape, // shape: chipShape,
label: Text(state.availableCorrespondents[e]?.name ?? ''), // label: Text(state.availableCorrespondents[e]?.name ?? ''),
onPressed: () { // onPressed: () {
context // context
.read<InboxCubit>() // .read<InboxCubit>()
.updateDocument(widget.document.copyWith( // .update(
correspondent: e, // widget.document.copyWith(correspondent: () => e),
overwriteCorrespondent: true, // )
)) // .then((value) => showSnackBar(
.then((value) => showSnackBar( // context,
context, // S
S // .of(context)
.of(context) // .inboxPageSuggestionSuccessfullyAppliedMessage));
.inboxPageSuggestionSuccessfullyAppliedMessage)); // },
}, // ),
), // )
) // .toList(),
.toList(), // ...suggestions.documentTypes
...suggestions.documentTypes // .whereNot((e) => widget.document.documentType == e)
.whereNot((e) => widget.document.documentType == e) // .map(
.map( // (e) => ActionChip(
(e) => ActionChip( // avatar: const Icon(Icons.description_outlined),
avatar: const Icon(Icons.description_outlined), // shape: chipShape,
shape: chipShape, // label: Text(state.availableDocumentTypes[e]?.name ?? ''),
label: Text(state.availableDocumentTypes[e]?.name ?? ''), // onPressed: () => context
onPressed: () => context // .read<InboxCubit>()
.read<InboxCubit>() // .update(
.updateDocument(widget.document // widget.document.copyWith(documentType: () => e),
.copyWith(documentType: e, overwriteDocumentType: true)) // shouldReload: false,
.then((value) => showSnackBar( // )
context, // .then((value) => showSnackBar(
S // context,
.of(context) // S
.inboxPageSuggestionSuccessfullyAppliedMessage)), // .of(context)
), // .inboxPageSuggestionSuccessfullyAppliedMessage)),
) // ),
.toList(), // )
...suggestions.tags // .toList(),
.whereNot((e) => widget.document.tags.contains(e)) // ...suggestions.tags
.map( // .whereNot((e) => widget.document.tags.contains(e))
(e) => ActionChip( // .map(
avatar: const Icon(Icons.label_outline), // (e) => ActionChip(
shape: chipShape, // avatar: const Icon(Icons.label_outline),
label: Text(state.availableTags[e]?.name ?? ''), // shape: chipShape,
onPressed: () { // label: Text(state.availableTags[e]?.name ?? ''),
context // onPressed: () {
.read<InboxCubit>() // context
.updateDocument(widget.document.copyWith( // .read<InboxCubit>()
tags: {...widget.document.tags, e}.toList(), // .update(
overwriteTags: true, // widget.document.copyWith(
)) // tags: {...widget.document.tags, e}.toList(),
.then((value) => showSnackBar( // ),
context, // shouldReload: false,
S // )
.of(context) // .then((value) => showSnackBar(
.inboxPageSuggestionSuccessfullyAppliedMessage)); // context,
}, // S
), // .of(context)
) // .inboxPageSuggestionSuccessfullyAppliedMessage));
.toList(), // },
...suggestions.dates // ),
.whereNot((e) => widget.document.created.isEqualToIgnoringDate(e)) // )
.map( // .toList(),
(e) => ActionChip( // ...suggestions.dates
avatar: const Icon(Icons.calendar_today_outlined), // .whereNot((e) => widget.document.created.isEqualToIgnoringDate(e))
shape: chipShape, // .map(
label: Text( // (e) => ActionChip(
"${S.of(context).documentCreatedPropertyLabel}: ${DateFormat.yMd().format(e)}", // avatar: const Icon(Icons.calendar_today_outlined),
), // shape: chipShape,
onPressed: () => context // label: Text(
.read<InboxCubit>() // "${S.of(context).documentCreatedPropertyLabel}: ${DateFormat.yMd().format(e)}",
.updateDocument(widget.document.copyWith(created: e)) // ),
.then((value) => showSnackBar( // onPressed: () => context
context, // .read<InboxCubit>()
S // .update(
.of(context) // widget.document.copyWith(created: e),
.inboxPageSuggestionSuccessfullyAppliedMessage)), // shouldReload: false,
), // )
) // .then((value) => showSnackBar(
.toList(), // context,
].expand((element) => [element, const SizedBox(width: 4)]).toList(); // S
} // .of(context)
// .inboxPageSuggestionSuccessfullyAppliedMessage)),
// ),
// )
// .toList(),
// ].expand((element) => [element, const SizedBox(width: 4)]).toList();
// }
} }

View File

@@ -25,7 +25,7 @@ class TagsWidget extends StatelessWidget {
required this.isSelectedPredicate, required this.isSelectedPredicate,
this.onTagSelected, this.onTagSelected,
this.showShortNames = false, this.showShortNames = false,
this.dense = false, this.dense = true,
}) : super(key: key); }) : super(key: key);
@override @override

View File

@@ -30,6 +30,7 @@ class LabelItem<T extends Label> extends StatelessWidget {
leading: leading, leading: leading,
onTap: () => onOpenEditPage(label), onTap: () => onOpenEditPage(label),
trailing: _buildReferencedDocumentsWidget(context), trailing: _buildReferencedDocumentsWidget(context),
isThreeLine: true,
); );
} }

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart'; import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart'; import 'package:paperless_mobile/core/widgets/offline_widget.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart'; import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
@@ -70,8 +71,12 @@ class LabelTabView<T extends Label> extends StatelessWidget {
.map( .map(
(l) => LabelItem<T>( (l) => LabelItem<T>(
name: l.name, name: l.name,
content: content: contentBuilder?.call(l) ??
contentBuilder?.call(l) ?? Text(l.match ?? '-'), Text(
"${translateMatchingAlgorithm(context, l.matchingAlgorithm)}\n"
"${l.match}",
maxLines: 2,
),
onOpenEditPage: onEdit, onOpenEditPage: onEdit,
filterBuilder: filterBuilder, filterBuilder: filterBuilder,
leading: leadingBuilder?.call(l), leading: leadingBuilder?.call(l),

View File

@@ -0,0 +1,163 @@
import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'model/documents_paged_state.dart';
///
/// Mixin which can be used on cubits which handle documents. This implements all paging and filtering logic.
///
mixin DocumentsPagingMixin<State extends DocumentsPagedState>
on BlocBase<State> {
PaperlessDocumentsApi get api;
Future<void> loadMore() async {
if (state.isLastPageLoaded) {
return;
}
emit(state.copyWithPaged(isLoading: true));
final newFilter = state.filter.copyWith(page: state.filter.page + 1);
try {
final result = await api.findAll(newFilter);
emit(state.copyWithPaged(
hasLoaded: true,
filter: newFilter,
value: [...state.value, result],
));
} finally {
emit(state.copyWithPaged(isLoading: false));
}
}
///
/// Updates document filter and automatically reloads documents. Always resets page to 1.
/// Use [loadMore] to load more data.
Future<void> updateFilter({
final DocumentFilter filter = DocumentFilter.initial,
}) async {
try {
emit(state.copyWithPaged(isLoading: true));
final result = await api.findAll(filter.copyWith(page: 1));
emit(state.copyWithPaged(
filter: filter,
value: [result],
hasLoaded: true,
));
} finally {
emit(state.copyWithPaged(isLoading: false));
}
}
///
/// Convenience method which allows to directly use [DocumentFilter.copyWith] on the current filter.
///
Future<void> updateCurrentFilter(
final DocumentFilter Function(DocumentFilter) transformFn,
) async =>
updateFilter(filter: transformFn(state.filter));
Future<void> resetFilter() {
final filter = DocumentFilter.initial.copyWith(
sortField: state.filter.sortField,
sortOrder: state.filter.sortOrder,
);
return updateFilter(filter: filter);
}
Future<void> reload() async {
emit(state.copyWithPaged(isLoading: true));
try {
final filter = state.filter.copyWith(page: 1);
final result = await api.findAll(filter);
emit(state.copyWithPaged(
hasLoaded: true,
value: [result],
isLoading: false,
filter: filter,
));
} finally {
emit(state.copyWithPaged(isLoading: false));
}
}
///
/// Updates a document. If [shouldReload] is false, the updated document will
/// replace the currently loaded one, otherwise all documents will be reloaded.
///
Future<void> update(
DocumentModel document, {
bool shouldReload = true,
}) async {
final updatedDocument = await api.update(document);
if (shouldReload) {
await reload();
} else {
replace(updatedDocument);
}
}
///
/// Deletes a document and removes it from the currently loaded state.
///
Future<void> delete(DocumentModel document) async {
emit(state.copyWithPaged(isLoading: true));
try {
await api.delete(document);
await remove(document);
} finally {
emit(state.copyWithPaged(isLoading: false));
}
}
///
/// Removes [document] from the currently loaded state.
/// Does not delete it from the server!
///
Future<void> remove(DocumentModel document) async {
final index = state.value.indexWhere(
(page) => page.results.any((element) => element.id == document.id),
);
if (index != -1) {
final foundPage = state.value[index];
final replacementPage = foundPage.copyWith(
results: foundPage.results
..removeWhere((element) => element.id == document.id),
);
final newCount = foundPage.count - 1;
emit(
state.copyWithPaged(
value: state.value
.mapIndexed(
(currIndex, element) =>
(currIndex == index ? replacementPage : element)
.copyWith(count: newCount),
)
.toList(),
),
);
}
}
///
/// Replaces the document with the same id as [document] from the currently
/// loaded state.
///
Future<void> replace(DocumentModel document) async {
final index = state.value.indexWhere(
(page) => page.results.any((element) => element.id == document.id),
);
if (index != -1) {
final foundPage = state.value[index];
final replacementPage = foundPage.copyWith(
results: foundPage.results..replaceRange(index, index + 1, [document]),
);
emit(state.copyWithPaged(
value: state.value
.mapIndexed((currIndex, element) =>
currIndex == index ? replacementPage : element)
.toList(),
));
}
}
}

View File

@@ -0,0 +1,70 @@
import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart';
abstract class DocumentsPagedState extends Equatable {
final bool hasLoaded;
final bool isLoading;
final List<PagedSearchResult<DocumentModel>> value;
final DocumentFilter filter;
const DocumentsPagedState({
this.value = const [],
this.hasLoaded = false,
this.isLoading = false,
this.filter = const DocumentFilter(),
});
List<DocumentModel> get documents {
return value.fold(
[],
(previousValue, element) => [
...previousValue,
...element.results,
],
);
}
int get currentPageNumber {
assert(value.isNotEmpty);
return value.last.pageKey;
}
int? get nextPageNumber {
return isLastPageLoaded ? null : currentPageNumber + 1;
}
int get count {
if (value.isEmpty) {
return 0;
}
return value.first.count;
}
bool get isLastPageLoaded {
if (!hasLoaded) {
return false;
}
if (value.isNotEmpty) {
return value.last.next == null;
}
return true;
}
int inferPageCount({required int pageSize}) {
if (!hasLoaded) {
return 100000;
}
if (value.isEmpty) {
return 0;
}
return value.first.inferPageCount(pageSize: pageSize);
}
// Return type has to be dynamic
dynamic copyWithPaged({
bool? hasLoaded,
bool? isLoading,
List<PagedSearchResult<DocumentModel>>? value,
DocumentFilter? filter,
});
}

View File

@@ -18,6 +18,7 @@ class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
'en': 'English', 'en': 'English',
'de': 'Deutsch', 'de': 'Deutsch',
'cs': 'Česky', 'cs': 'Česky',
'tr': 'Türkçe',
}; };
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -42,6 +43,10 @@ class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
RadioOption( RadioOption(
value: 'cs', value: 'cs',
label: _languageOptions['cs']!, label: _languageOptions['cs']!,
),
RadioOption(
value: 'tr',
label: _languageOptions['tr']!,
) )
], ],
initialValue: context initialValue: context

View File

@@ -1,7 +1,11 @@
{ {
"@@locale": "cs", "@@locale": "cs",
"aboutDialogDevelopedByText": "Vyvíjí", "aboutDialogDevelopedByText": "Vyvíjí",
"@aboutDialogDevelopedByText": {}, "@aboutDialogDevelopedByText": {
"placeholders": {
"name": {}
}
},
"addCorrespondentPageTitle": "Nový korespondent", "addCorrespondentPageTitle": "Nový korespondent",
"@addCorrespondentPageTitle": {}, "@addCorrespondentPageTitle": {},
"addDocumentTypePageTitle": "Nový typ dokumentu", "addDocumentTypePageTitle": "Nový typ dokumentu",
@@ -128,10 +132,6 @@
"@documentsFilterPageAdvancedLabel": {}, "@documentsFilterPageAdvancedLabel": {},
"documentsFilterPageApplyFilterLabel": "Použít", "documentsFilterPageApplyFilterLabel": "Použít",
"@documentsFilterPageApplyFilterLabel": {}, "@documentsFilterPageApplyFilterLabel": {},
"documentsFilterPageDateRangeFieldEndLabel": "Do",
"@documentsFilterPageDateRangeFieldEndLabel": {},
"documentsFilterPageDateRangeFieldStartLabel": "Od",
"@documentsFilterPageDateRangeFieldStartLabel": {},
"documentsFilterPageDateRangeLastMonthLabel": "Minulý měsíc", "documentsFilterPageDateRangeLastMonthLabel": "Minulý měsíc",
"@documentsFilterPageDateRangeLastMonthLabel": {}, "@documentsFilterPageDateRangeLastMonthLabel": {},
"documentsFilterPageDateRangeLastSevenDaysLabel": "Posledních 7 dní", "documentsFilterPageDateRangeLastSevenDaysLabel": "Posledních 7 dní",
@@ -294,8 +294,6 @@
"count": {} "count": {}
} }
}, },
"extendedDateRangePickerFromLabel": "Od",
"@extendedDateRangePickerFromLabel": {},
"extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{} few{} many{} other{Posledních 7 dní}}", "extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{} few{} many{} other{Posledních 7 dní}}",
"@extendedDateRangePickerLastDaysLabel": { "@extendedDateRangePickerLastDaysLabel": {
"placeholders": { "placeholders": {
@@ -328,8 +326,6 @@
"count": {} "count": {}
} }
}, },
"extendedDateRangePickerToLabel": "Do",
"@extendedDateRangePickerToLabel": {},
"extendedDateRangePickerWeekText": "{count, plural, other{}}", "extendedDateRangePickerWeekText": "{count, plural, other{}}",
"@extendedDateRangePickerWeekText": { "@extendedDateRangePickerWeekText": {
"placeholders": { "placeholders": {
@@ -492,6 +488,18 @@
"@loginPageUsernameLabel": {}, "@loginPageUsernameLabel": {},
"loginPageUsernameValidatorMessageText": "Jméno uživatele nesmí být prázdné.", "loginPageUsernameValidatorMessageText": "Jméno uživatele nesmí být prázdné.",
"@loginPageUsernameValidatorMessageText": {}, "@loginPageUsernameValidatorMessageText": {},
"matchingAlgorithmAllDescription": "",
"@matchingAlgorithmAllDescription": {},
"matchingAlgorithmAnyDescription": "",
"@matchingAlgorithmAnyDescription": {},
"matchingAlgorithmAutoDescription": "",
"@matchingAlgorithmAutoDescription": {},
"matchingAlgorithmExactDescription": "",
"@matchingAlgorithmExactDescription": {},
"matchingAlgorithmFuzzyDescription": "",
"@matchingAlgorithmFuzzyDescription": {},
"matchingAlgorithmRegexDescription": "",
"@matchingAlgorithmRegexDescription": {},
"offlineWidgetText": "Nezdařilo se vytvořit připojení k internetu.", "offlineWidgetText": "Nezdařilo se vytvořit připojení k internetu.",
"@offlineWidgetText": {}, "@offlineWidgetText": {},
"onboardingDoneButtonLabel": "Hotovo", "onboardingDoneButtonLabel": "Hotovo",

View File

@@ -1,7 +1,11 @@
{ {
"@@locale": "de", "@@locale": "de",
"aboutDialogDevelopedByText": "Entwickelt von", "aboutDialogDevelopedByText": "Entwickelt von",
"@aboutDialogDevelopedByText": {}, "@aboutDialogDevelopedByText": {
"placeholders": {
"name": {}
}
},
"addCorrespondentPageTitle": "Neuer Korrespondent", "addCorrespondentPageTitle": "Neuer Korrespondent",
"@addCorrespondentPageTitle": {}, "@addCorrespondentPageTitle": {},
"addDocumentTypePageTitle": "Neuer Dokumenttyp", "addDocumentTypePageTitle": "Neuer Dokumenttyp",
@@ -128,10 +132,6 @@
"@documentsFilterPageAdvancedLabel": {}, "@documentsFilterPageAdvancedLabel": {},
"documentsFilterPageApplyFilterLabel": "Anwenden", "documentsFilterPageApplyFilterLabel": "Anwenden",
"@documentsFilterPageApplyFilterLabel": {}, "@documentsFilterPageApplyFilterLabel": {},
"documentsFilterPageDateRangeFieldEndLabel": "Bis",
"@documentsFilterPageDateRangeFieldEndLabel": {},
"documentsFilterPageDateRangeFieldStartLabel": "Von",
"@documentsFilterPageDateRangeFieldStartLabel": {},
"documentsFilterPageDateRangeLastMonthLabel": "Letzter Monat", "documentsFilterPageDateRangeLastMonthLabel": "Letzter Monat",
"@documentsFilterPageDateRangeLastMonthLabel": {}, "@documentsFilterPageDateRangeLastMonthLabel": {},
"documentsFilterPageDateRangeLastSevenDaysLabel": "Letzte 7 Tage", "documentsFilterPageDateRangeLastSevenDaysLabel": "Letzte 7 Tage",
@@ -294,8 +294,6 @@
"count": {} "count": {}
} }
}, },
"extendedDateRangePickerFromLabel": "Von",
"@extendedDateRangePickerFromLabel": {},
"extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{Gestern} other{Letzte {count} Tage}}", "extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{Gestern} other{Letzte {count} Tage}}",
"@extendedDateRangePickerLastDaysLabel": { "@extendedDateRangePickerLastDaysLabel": {
"placeholders": { "placeholders": {
@@ -328,8 +326,6 @@
"count": {} "count": {}
} }
}, },
"extendedDateRangePickerToLabel": "Bis",
"@extendedDateRangePickerToLabel": {},
"extendedDateRangePickerWeekText": "{count, plural, zero{} one{Woche} other{Wochen}}", "extendedDateRangePickerWeekText": "{count, plural, zero{} one{Woche} other{Wochen}}",
"@extendedDateRangePickerWeekText": { "@extendedDateRangePickerWeekText": {
"placeholders": { "placeholders": {
@@ -364,7 +360,7 @@
"@genericActionUploadLabel": {}, "@genericActionUploadLabel": {},
"genericMessageOfflineText": "Du bist offline.", "genericMessageOfflineText": "Du bist offline.",
"@genericMessageOfflineText": {}, "@genericMessageOfflineText": {},
"inboxPageAssignAsnLabel": "", "inboxPageAssignAsnLabel": "ASN zuweisen",
"@inboxPageAssignAsnLabel": {}, "@inboxPageAssignAsnLabel": {},
"inboxPageDocumentRemovedMessageText": "Dokument aus Posteingang entfernt.", "inboxPageDocumentRemovedMessageText": "Dokument aus Posteingang entfernt.",
"@inboxPageDocumentRemovedMessageText": {}, "@inboxPageDocumentRemovedMessageText": {},
@@ -492,6 +488,18 @@
"@loginPageUsernameLabel": {}, "@loginPageUsernameLabel": {},
"loginPageUsernameValidatorMessageText": "Nutzername darf nicht leer sein.", "loginPageUsernameValidatorMessageText": "Nutzername darf nicht leer sein.",
"@loginPageUsernameValidatorMessageText": {}, "@loginPageUsernameValidatorMessageText": {},
"matchingAlgorithmAllDescription": "Alle: Dokument enthält alle folgenden Wörter",
"@matchingAlgorithmAllDescription": {},
"matchingAlgorithmAnyDescription": "Irgendein Wort: Dokument enthält eins der folgenden Wörter",
"@matchingAlgorithmAnyDescription": {},
"matchingAlgorithmAutoDescription": "Auto: Zuweisung automatisch erlernen",
"@matchingAlgorithmAutoDescription": {},
"matchingAlgorithmExactDescription": "Exakt: Dokument enthält die folgende Zeichenkette",
"@matchingAlgorithmExactDescription": {},
"matchingAlgorithmFuzzyDescription": "Ungenau: Dokument enthält ein zum folgenden Wort ähnliches Wort",
"@matchingAlgorithmFuzzyDescription": {},
"matchingAlgorithmRegexDescription": "Regulärer Ausdruck: Dokument passt zum folgenden Ausdruck",
"@matchingAlgorithmRegexDescription": {},
"offlineWidgetText": "Es konte keine Verbindung zum Internet hergestellt werden.", "offlineWidgetText": "Es konte keine Verbindung zum Internet hergestellt werden.",
"@offlineWidgetText": {}, "@offlineWidgetText": {},
"onboardingDoneButtonLabel": "Fertig", "onboardingDoneButtonLabel": "Fertig",

View File

@@ -1,7 +1,11 @@
{ {
"@@locale": "en", "@@locale": "en",
"aboutDialogDevelopedByText": "Developed by", "aboutDialogDevelopedByText": "Developed by {name}",
"@aboutDialogDevelopedByText": {}, "@aboutDialogDevelopedByText": {
"placeholders": {
"name": {}
}
},
"addCorrespondentPageTitle": "New Correspondent", "addCorrespondentPageTitle": "New Correspondent",
"@addCorrespondentPageTitle": {}, "@addCorrespondentPageTitle": {},
"addDocumentTypePageTitle": "New Document Type", "addDocumentTypePageTitle": "New Document Type",
@@ -46,7 +50,7 @@
"@deleteViewDialogContentText": {}, "@deleteViewDialogContentText": {},
"deleteViewDialogTitleText": "Delete view ", "deleteViewDialogTitleText": "Delete view ",
"@deleteViewDialogTitleText": {}, "@deleteViewDialogTitleText": {},
"documentAddedPropertyLabel": "Added At", "documentAddedPropertyLabel": "Added at",
"@documentAddedPropertyLabel": {}, "@documentAddedPropertyLabel": {},
"documentArchiveSerialNumberPropertyLongLabel": "Archive Serial Number", "documentArchiveSerialNumberPropertyLongLabel": "Archive Serial Number",
"@documentArchiveSerialNumberPropertyLongLabel": {}, "@documentArchiveSerialNumberPropertyLongLabel": {},
@@ -54,7 +58,7 @@
"@documentArchiveSerialNumberPropertyShortLabel": {}, "@documentArchiveSerialNumberPropertyShortLabel": {},
"documentCorrespondentPropertyLabel": "Correspondent", "documentCorrespondentPropertyLabel": "Correspondent",
"@documentCorrespondentPropertyLabel": {}, "@documentCorrespondentPropertyLabel": {},
"documentCreatedPropertyLabel": "Created At", "documentCreatedPropertyLabel": "Created at",
"@documentCreatedPropertyLabel": {}, "@documentCreatedPropertyLabel": {},
"documentDeleteSuccessMessage": "Document successfully deleted.", "documentDeleteSuccessMessage": "Document successfully deleted.",
"@documentDeleteSuccessMessage": {}, "@documentDeleteSuccessMessage": {},
@@ -104,7 +108,7 @@
"@documentMetaDataOriginalFileSizeLabel": {}, "@documentMetaDataOriginalFileSizeLabel": {},
"documentMetaDataOriginalMimeTypeLabel": "Original MIME-Type", "documentMetaDataOriginalMimeTypeLabel": "Original MIME-Type",
"@documentMetaDataOriginalMimeTypeLabel": {}, "@documentMetaDataOriginalMimeTypeLabel": {},
"documentModifiedPropertyLabel": "Modified At", "documentModifiedPropertyLabel": "Modified at",
"@documentModifiedPropertyLabel": {}, "@documentModifiedPropertyLabel": {},
"documentPreviewPageTitle": "Preview", "documentPreviewPageTitle": "Preview",
"@documentPreviewPageTitle": {}, "@documentPreviewPageTitle": {},
@@ -128,10 +132,6 @@
"@documentsFilterPageAdvancedLabel": {}, "@documentsFilterPageAdvancedLabel": {},
"documentsFilterPageApplyFilterLabel": "Apply", "documentsFilterPageApplyFilterLabel": "Apply",
"@documentsFilterPageApplyFilterLabel": {}, "@documentsFilterPageApplyFilterLabel": {},
"documentsFilterPageDateRangeFieldEndLabel": "To",
"@documentsFilterPageDateRangeFieldEndLabel": {},
"documentsFilterPageDateRangeFieldStartLabel": "From",
"@documentsFilterPageDateRangeFieldStartLabel": {},
"documentsFilterPageDateRangeLastMonthLabel": "Last Month", "documentsFilterPageDateRangeLastMonthLabel": "Last Month",
"@documentsFilterPageDateRangeLastMonthLabel": {}, "@documentsFilterPageDateRangeLastMonthLabel": {},
"documentsFilterPageDateRangeLastSevenDaysLabel": "Last 7 Days", "documentsFilterPageDateRangeLastSevenDaysLabel": "Last 7 Days",
@@ -294,8 +294,6 @@
"count": {} "count": {}
} }
}, },
"extendedDateRangePickerFromLabel": "From",
"@extendedDateRangePickerFromLabel": {},
"extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{Yesterday} other{Last {count} days}}", "extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{Yesterday} other{Last {count} days}}",
"@extendedDateRangePickerLastDaysLabel": { "@extendedDateRangePickerLastDaysLabel": {
"placeholders": { "placeholders": {
@@ -328,8 +326,6 @@
"count": {} "count": {}
} }
}, },
"extendedDateRangePickerToLabel": "To",
"@extendedDateRangePickerToLabel": {},
"extendedDateRangePickerWeekText": "{count, plural, zero{} one{week} other{weeks}}", "extendedDateRangePickerWeekText": "{count, plural, zero{} one{week} other{weeks}}",
"@extendedDateRangePickerWeekText": { "@extendedDateRangePickerWeekText": {
"placeholders": { "placeholders": {
@@ -492,6 +488,18 @@
"@loginPageUsernameLabel": {}, "@loginPageUsernameLabel": {},
"loginPageUsernameValidatorMessageText": "Username must not be empty.", "loginPageUsernameValidatorMessageText": "Username must not be empty.",
"@loginPageUsernameValidatorMessageText": {}, "@loginPageUsernameValidatorMessageText": {},
"matchingAlgorithmAllDescription": "All: Document contains all of these words",
"@matchingAlgorithmAllDescription": {},
"matchingAlgorithmAnyDescription": "Any: Document contains any of these words",
"@matchingAlgorithmAnyDescription": {},
"matchingAlgorithmAutoDescription": "Auto: Learn matching automatically",
"@matchingAlgorithmAutoDescription": {},
"matchingAlgorithmExactDescription": "Exact: Document contains this string",
"@matchingAlgorithmExactDescription": {},
"matchingAlgorithmFuzzyDescription": "Fuzzy: Document contains a word similar to this word",
"@matchingAlgorithmFuzzyDescription": {},
"matchingAlgorithmRegexDescription": "Regular Expression: Document matches this regular expression",
"@matchingAlgorithmRegexDescription": {},
"offlineWidgetText": "An internet connection could not be established.", "offlineWidgetText": "An internet connection could not be established.",
"@offlineWidgetText": {}, "@offlineWidgetText": {},
"onboardingDoneButtonLabel": "Done", "onboardingDoneButtonLabel": "Done",

581
lib/l10n/intl_tr.arb Normal file
View File

@@ -0,0 +1,581 @@
{
"@@locale": "tr",
"aboutDialogDevelopedByText": "",
"@aboutDialogDevelopedByText": {
"placeholders": {
"name": {}
}
},
"addCorrespondentPageTitle": "Yeni ek yazar",
"@addCorrespondentPageTitle": {},
"addDocumentTypePageTitle": "Yeni Belge Türü",
"@addDocumentTypePageTitle": {},
"addStoragePathPageTitle": "Yeni Depolama Yolu",
"@addStoragePathPageTitle": {},
"addTagPageTitle": "Yeni Etiket",
"@addTagPageTitle": {},
"appDrawerAboutInfoLoadingText": "Uygulama bilgileri alınıyor...",
"@appDrawerAboutInfoLoadingText": {},
"appDrawerAboutLabel": "Bu uygulama hakkında",
"@appDrawerAboutLabel": {},
"appDrawerHeaderLoggedInAsText": "Olarak giriş yapıldı",
"@appDrawerHeaderLoggedInAsText": {},
"appDrawerLogoutLabel": "Bağlantıyı kes",
"@appDrawerLogoutLabel": {},
"appDrawerReportBugLabel": "Hata Bildir",
"@appDrawerReportBugLabel": {},
"appDrawerSettingsLabel": "Ayarlar",
"@appDrawerSettingsLabel": {},
"appSettingsBiometricAuthenticationDescriptionText": "Uygulama başlangıcında kimlik doğrulaması yapın",
"@appSettingsBiometricAuthenticationDescriptionText": {},
"appSettingsBiometricAuthenticationLabel": "Biyometrik kimlik doğrulama",
"@appSettingsBiometricAuthenticationLabel": {},
"appSettingsDisableBiometricAuthenticationReasonText": "Biyometrik kimlik doğrulamayı devre dışı bırakmak için kimlik doğrulaması yapın",
"@appSettingsDisableBiometricAuthenticationReasonText": {},
"appSettingsEnableBiometricAuthenticationReasonText": "Biyometrik kimlik doğrulamayı etkinleştirmek için kimlik doğrulaması yapın",
"@appSettingsEnableBiometricAuthenticationReasonText": {},
"appTitleText": "Paperless Mobil",
"@appTitleText": {},
"bottomNavDocumentsPageLabel": "Dökümanlar",
"@bottomNavDocumentsPageLabel": {},
"bottomNavInboxPageLabel": "Gelen Kutusu",
"@bottomNavInboxPageLabel": {},
"bottomNavLabelsPageLabel": "Etiketler",
"@bottomNavLabelsPageLabel": {},
"bottomNavScannerPageLabel": "Tarayıcı",
"@bottomNavScannerPageLabel": {},
"correspondentFormFieldSearchHintText": "Yazmaya başlayın...",
"@correspondentFormFieldSearchHintText": {},
"deleteViewDialogContentText": "Bu görünümü gerçekten silmek istiyor musunuz?",
"@deleteViewDialogContentText": {},
"deleteViewDialogTitleText": "Görünümü sil",
"@deleteViewDialogTitleText": {},
"documentAddedPropertyLabel": "",
"@documentAddedPropertyLabel": {},
"documentArchiveSerialNumberPropertyLongLabel": "Arşiv Seri Numarası",
"@documentArchiveSerialNumberPropertyLongLabel": {},
"documentArchiveSerialNumberPropertyShortLabel": "ASN",
"@documentArchiveSerialNumberPropertyShortLabel": {},
"documentCorrespondentPropertyLabel": "Ek Yazarlar",
"@documentCorrespondentPropertyLabel": {},
"documentCreatedPropertyLabel": "",
"@documentCreatedPropertyLabel": {},
"documentDeleteSuccessMessage": "Doküman başarıyla silindi.",
"@documentDeleteSuccessMessage": {},
"documentDetailsPageAssignAsnButtonLabel": "Ata",
"@documentDetailsPageAssignAsnButtonLabel": {},
"documentDetailsPageLoadFullContentLabel": "Tüm içeriği yükle",
"@documentDetailsPageLoadFullContentLabel": {},
"documentDetailsPageSimilarDocumentsLabel": "Benzer Belgeler",
"@documentDetailsPageSimilarDocumentsLabel": {},
"documentDetailsPageTabContentLabel": "İçerik",
"@documentDetailsPageTabContentLabel": {},
"documentDetailsPageTabMetaDataLabel": "Meta Veri",
"@documentDetailsPageTabMetaDataLabel": {},
"documentDetailsPageTabOverviewLabel": "Genel bakış",
"@documentDetailsPageTabOverviewLabel": {},
"documentDocumentTypePropertyLabel": "Döküman tipi",
"@documentDocumentTypePropertyLabel": {},
"documentDownloadSuccessMessage": "Döküman başarıyla indirildi.",
"@documentDownloadSuccessMessage": {},
"documentEditPageSuggestionsLabel": "Öneriler:",
"@documentEditPageSuggestionsLabel": {},
"documentEditPageTitle": "Dökümanı Düzenle",
"@documentEditPageTitle": {},
"documentFilterAdvancedLabel": "Gelişmiş",
"@documentFilterAdvancedLabel": {},
"documentFilterApplyFilterLabel": "Uygula",
"@documentFilterApplyFilterLabel": {},
"documentFilterQueryOptionsAsnLabel": "ASN",
"@documentFilterQueryOptionsAsnLabel": {},
"documentFilterQueryOptionsExtendedLabel": "Genişletilmiş",
"@documentFilterQueryOptionsExtendedLabel": {},
"documentFilterQueryOptionsTitleAndContentLabel": "Başlık & İçerik",
"@documentFilterQueryOptionsTitleAndContentLabel": {},
"documentFilterQueryOptionsTitleLabel": "Başlık",
"@documentFilterQueryOptionsTitleLabel": {},
"documentFilterResetLabel": "Sıfırla",
"@documentFilterResetLabel": {},
"documentFilterSearchLabel": "Ara",
"@documentFilterSearchLabel": {},
"documentFilterTitle": "Dökümanları Filtrele",
"@documentFilterTitle": {},
"documentMetaDataChecksumLabel": "Orijinal MD5-Sağlaması",
"@documentMetaDataChecksumLabel": {},
"documentMetaDataMediaFilenamePropertyLabel": "Medya Dosya Adı",
"@documentMetaDataMediaFilenamePropertyLabel": {},
"documentMetaDataOriginalFileSizeLabel": "Orijinal Dosya Boyutu",
"@documentMetaDataOriginalFileSizeLabel": {},
"documentMetaDataOriginalMimeTypeLabel": "Orijinal MIME-Tipi",
"@documentMetaDataOriginalMimeTypeLabel": {},
"documentModifiedPropertyLabel": "",
"@documentModifiedPropertyLabel": {},
"documentPreviewPageTitle": "Ön izleme",
"@documentPreviewPageTitle": {},
"documentScannerPageAddScanButtonLabel": "Belge tarat",
"@documentScannerPageAddScanButtonLabel": {},
"documentScannerPageEmptyStateText": "Henüz taranan belge yok.",
"@documentScannerPageEmptyStateText": {},
"documentScannerPageOrText": "yada",
"@documentScannerPageOrText": {},
"documentScannerPageResetButtonTooltipText": "Tüm tarananları sil",
"@documentScannerPageResetButtonTooltipText": {},
"documentScannerPageTitle": "Tara",
"@documentScannerPageTitle": {},
"documentScannerPageUploadButtonTooltip": "Paperless'a Yükle",
"@documentScannerPageUploadButtonTooltip": {},
"documentScannerPageUploadFromThisDeviceButtonLabel": "Bu cihazdan bir döküman yükleyin",
"@documentScannerPageUploadFromThisDeviceButtonLabel": {},
"documentsEmptyStateResetFilterLabel": "Filtreyi sıfırla",
"@documentsEmptyStateResetFilterLabel": {},
"documentsFilterPageAdvancedLabel": "Gelişmiş",
"@documentsFilterPageAdvancedLabel": {},
"documentsFilterPageApplyFilterLabel": "Uygula",
"@documentsFilterPageApplyFilterLabel": {},
"documentsFilterPageDateRangeLastMonthLabel": "Geçen Ay",
"@documentsFilterPageDateRangeLastMonthLabel": {},
"documentsFilterPageDateRangeLastSevenDaysLabel": "Geçmiş 7 Gün",
"@documentsFilterPageDateRangeLastSevenDaysLabel": {},
"documentsFilterPageDateRangeLastThreeMonthsLabel": "Geçmiş 3 Ay",
"@documentsFilterPageDateRangeLastThreeMonthsLabel": {},
"documentsFilterPageDateRangeLastYearLabel": "Geçen sene",
"@documentsFilterPageDateRangeLastYearLabel": {},
"documentsFilterPageQueryOptionsAsnLabel": "ASN",
"@documentsFilterPageQueryOptionsAsnLabel": {},
"documentsFilterPageQueryOptionsExtendedLabel": "",
"@documentsFilterPageQueryOptionsExtendedLabel": {},
"documentsFilterPageQueryOptionsTitleAndContentLabel": "Başlık & İçerik",
"@documentsFilterPageQueryOptionsTitleAndContentLabel": {},
"documentsFilterPageQueryOptionsTitleLabel": "Başlık",
"@documentsFilterPageQueryOptionsTitleLabel": {},
"documentsFilterPageSearchLabel": "Ara",
"@documentsFilterPageSearchLabel": {},
"documentsFilterPageTitle": "Dökümanları Filtrele",
"@documentsFilterPageTitle": {},
"documentsPageBulkDeleteSuccessfulText": "Dokümanlar başarıyla silindi.",
"@documentsPageBulkDeleteSuccessfulText": {},
"documentsPageEmptyStateNothingHereText": "Burada hiçbir şey yok gibi görünüyor...",
"@documentsPageEmptyStateNothingHereText": {},
"documentsPageEmptyStateOopsText": "Hata.",
"@documentsPageEmptyStateOopsText": {},
"documentsPageNewDocumentAvailableText": "Yeni döküman mevcut!",
"@documentsPageNewDocumentAvailableText": {},
"documentsPageOrderByLabel": "Şuna göre sırala",
"@documentsPageOrderByLabel": {},
"documentsPageSelectionBulkDeleteDialogContinueText": "Bu işlem geri alınamaz. Yine de devam etmek istiyor musunuz?",
"@documentsPageSelectionBulkDeleteDialogContinueText": {},
"documentsPageSelectionBulkDeleteDialogTitle": "Silmeyi onayla",
"@documentsPageSelectionBulkDeleteDialogTitle": {},
"documentsPageSelectionBulkDeleteDialogWarningTextMany": "Aşağıdaki belgeleri silmek istediğinizden emin misiniz?",
"@documentsPageSelectionBulkDeleteDialogWarningTextMany": {},
"documentsPageSelectionBulkDeleteDialogWarningTextOne": "Aşağıdaki belgeyi silmek istediğinizden emin misiniz?",
"@documentsPageSelectionBulkDeleteDialogWarningTextOne": {},
"documentsPageTitle": "Dökümanlar",
"@documentsPageTitle": {},
"documentsSelectedText": "seçilmiş",
"@documentsSelectedText": {},
"documentStoragePathPropertyLabel": "Depolama Yolu",
"@documentStoragePathPropertyLabel": {},
"documentsUploadPageTitle": "Belge hazırla",
"@documentsUploadPageTitle": {},
"documentTagsPropertyLabel": "Etiketler",
"@documentTagsPropertyLabel": {},
"documentTitlePropertyLabel": "Başlık",
"@documentTitlePropertyLabel": {},
"documentTypeFormFieldSearchHintText": "Yazmaya başlayın...",
"@documentTypeFormFieldSearchHintText": {},
"documentUpdateSuccessMessage": "Doküman başarıyla güncellendi.",
"@documentUpdateSuccessMessage": {},
"documentUploadFileNameLabel": "Dosya adı",
"@documentUploadFileNameLabel": {},
"documentUploadPageSynchronizeTitleAndFilenameLabel": "Başlığı ve dosya adını senkronize et",
"@documentUploadPageSynchronizeTitleAndFilenameLabel": {},
"documentUploadProcessingSuccessfulReloadActionText": "Yenile",
"@documentUploadProcessingSuccessfulReloadActionText": {},
"documentUploadProcessingSuccessfulText": "Doküman başarıyla işlendi.",
"@documentUploadProcessingSuccessfulText": {},
"documentUploadSuccessText": "Doküman başarıyla yüklendi, işleniyor...",
"@documentUploadSuccessText": {},
"editLabelPageConfirmDeletionDialogTitle": "Silmeyi onayla.",
"@editLabelPageConfirmDeletionDialogTitle": {},
"editLabelPageDeletionDialogText": "Bu etiket, diğer belgelere referanslar içerir. Bu etiketi silerek, tüm referanslar kaldırılacaktır. Devam etmek ister misiniz?",
"@editLabelPageDeletionDialogText": {},
"errorMessageAcknowledgeTasksError": "Görevler kabul edilemedi.",
"@errorMessageAcknowledgeTasksError": {},
"errorMessageAuthenticationFailed": "Kimlik doğrulama başarısız oldu, lütfen tekrar deneyin.",
"@errorMessageAuthenticationFailed": {},
"errorMessageAutocompleteQueryError": "Sorgunuz otomatik olarak tamamlanmaya çalışılırken bir hata oluştu.",
"@errorMessageAutocompleteQueryError": {},
"errorMessageBiometricAuthenticationFailed": "Biyometrik doğrulama başarısız oldu.",
"@errorMessageBiometricAuthenticationFailed": {},
"errorMessageBiotmetricsNotSupported": "Biyometrik kimlik doğrulama bu cihazda desteklenmiyor.",
"@errorMessageBiotmetricsNotSupported": {},
"errorMessageBulkActionFailed": "Dokümanlar toplu düzenlenemedi.",
"@errorMessageBulkActionFailed": {},
"errorMessageCorrespondentCreateFailed": "Ek yazar oluşturulamadı, lütfen tekrar deneyin.",
"@errorMessageCorrespondentCreateFailed": {},
"errorMessageCorrespondentLoadFailed": "Ek yazarlar yüklenemedi.",
"@errorMessageCorrespondentLoadFailed": {},
"errorMessageCreateSavedViewError": "Kayıtlı görünüm oluşturulamadı, lütfen tekrar deneyin.",
"@errorMessageCreateSavedViewError": {},
"errorMessageDeleteSavedViewError": "Kayıtlı görünüm silinemedi, lütfen tekrar deneyin",
"@errorMessageDeleteSavedViewError": {},
"errorMessageDeviceOffline": "Şu anda çevrimdışısınız. Lütfen internete bağlı olduğunuzdan emin olun.",
"@errorMessageDeviceOffline": {},
"errorMessageDocumentAsnQueryFailed": "Arşiv seri numarası atanamadı.",
"@errorMessageDocumentAsnQueryFailed": {},
"errorMessageDocumentDeleteFailed": "Doküman silinemedi, lütfen tekrar deneyin.",
"@errorMessageDocumentDeleteFailed": {},
"errorMessageDocumentLoadFailed": "Dokümanlar yüklenemedi, lütfen tekrar deneyin.",
"@errorMessageDocumentLoadFailed": {},
"errorMessageDocumentPreviewFailed": "Doküman önizlemesi yüklenemedi.",
"@errorMessageDocumentPreviewFailed": {},
"errorMessageDocumentTypeCreateFailed": "Doküman oluşturulamadı, lütfen tekrar deneyin.",
"@errorMessageDocumentTypeCreateFailed": {},
"errorMessageDocumentTypeLoadFailed": "Belge türleri yüklenemedi, lütfen tekrar deneyin.",
"@errorMessageDocumentTypeLoadFailed": {},
"errorMessageDocumentUpdateFailed": "Doküman güncellenemedi, lütfen tekrar deneyin.",
"@errorMessageDocumentUpdateFailed": {},
"errorMessageDocumentUploadFailed": "Doküman yüklenemedi, lütfen tekrar deneyin.",
"@errorMessageDocumentUploadFailed": {},
"errorMessageInvalidClientCertificateConfiguration": "Geçersiz sertifika veya eksik parola, lütfen tekrar deneyin",
"@errorMessageInvalidClientCertificateConfiguration": {},
"errorMessageLoadSavedViewsError": "Kayıtlı görünümler yüklenemedi.",
"@errorMessageLoadSavedViewsError": {},
"errorMessageMissingClientCertificate": "Bir müşteri sertifikası bekleniyordu ancak gönderilmedi. Lütfen geçerli bir müşteri sertifikası sağlayın.",
"@errorMessageMissingClientCertificate": {},
"errorMessageNotAuthenticated": "Kullanıcının kimliği doğrulanmadı.",
"@errorMessageNotAuthenticated": {},
"errorMessageRequestTimedOut": "Sunucuya yapılan istek zaman aşımına uğradı.",
"@errorMessageRequestTimedOut": {},
"errorMessageScanRemoveFailed": "Taramalar kaldırılırken bir hata oluştu.",
"@errorMessageScanRemoveFailed": {},
"errorMessageServerUnreachable": "Paperless sunucunuza erişilemedi, çalışır durumda mı?",
"@errorMessageServerUnreachable": {},
"errorMessageSimilarQueryError": "Benzer belgeler yüklenemedi.",
"@errorMessageSimilarQueryError": {},
"errorMessageStoragePathCreateFailed": "Depolama yolu oluşturulamadı, lütfen tekrar deneyin.",
"@errorMessageStoragePathCreateFailed": {},
"errorMessageStoragePathLoadFailed": "Depolama yolları yüklenemedi.",
"@errorMessageStoragePathLoadFailed": {},
"errorMessageSuggestionsQueryError": "Öneriler yüklenemedi.",
"@errorMessageSuggestionsQueryError": {},
"errorMessageTagCreateFailed": "Etiket oluşturulamadı, lütfen tekrar deneyin.",
"@errorMessageTagCreateFailed": {},
"errorMessageTagLoadFailed": "Etiketler yüklenemedi.",
"@errorMessageTagLoadFailed": {},
"errorMessageUnknonwnError": "Bilinmeyen bir hata oluştu.",
"@errorMessageUnknonwnError": {},
"errorMessageUnsupportedFileFormat": "Bu dosya formatı desteklenmiyor.",
"@errorMessageUnsupportedFileFormat": {},
"errorReportLabel": "RAPORLA",
"@errorReportLabel": {},
"extendedDateRangeDialogAbsoluteLabel": "Mutlak",
"@extendedDateRangeDialogAbsoluteLabel": {},
"extendedDateRangeDialogHintText": "İpucu: Somut tarihlerin yanı sıra, geçerli tarihe göre bir zaman aralığı da belirleyebilirsiniz.",
"@extendedDateRangeDialogHintText": {},
"extendedDateRangeDialogRelativeAmountLabel": "Miktar",
"@extendedDateRangeDialogRelativeAmountLabel": {},
"extendedDateRangeDialogRelativeLabel": "Bağıl",
"@extendedDateRangeDialogRelativeLabel": {},
"extendedDateRangeDialogRelativeLastLabel": "Son",
"@extendedDateRangeDialogRelativeLastLabel": {},
"extendedDateRangeDialogRelativeTimeUnitLabel": "Zaman birimi",
"@extendedDateRangeDialogRelativeTimeUnitLabel": {},
"extendedDateRangeDialogTitle": "Tarih aralığı seçin",
"@extendedDateRangeDialogTitle": {},
"extendedDateRangePickerAfterLabel": "Sonra",
"@extendedDateRangePickerAfterLabel": {},
"extendedDateRangePickerBeforeLabel": "Önce",
"@extendedDateRangePickerBeforeLabel": {},
"extendedDateRangePickerDayText": "{count, plural, zero{} one{gün} other{günler}}",
"@extendedDateRangePickerDayText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{Dün} other{Geçmiş {count} gün}}",
"@extendedDateRangePickerLastDaysLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastMonthsLabel": "{count, plural, zero{} one{Geçen ay} other{Geçmiş {count} ay}}",
"@extendedDateRangePickerLastMonthsLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastText": "Geçen",
"@extendedDateRangePickerLastText": {},
"extendedDateRangePickerLastWeeksLabel": "{count, plural, zero{} one{Geçen hafta} other{Geçmiş {count} hafta}}",
"@extendedDateRangePickerLastWeeksLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerLastYearsLabel": "{count, plural, zero{} one{Geçen yıl} other{Geçmiş {count} yıl}}",
"@extendedDateRangePickerLastYearsLabel": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerMonthText": "{count, plural, zero{} one{ay} other{aylar}}",
"@extendedDateRangePickerMonthText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerWeekText": "{count, plural, zero{} one{hafta} other{haftalar}}",
"@extendedDateRangePickerWeekText": {
"placeholders": {
"count": {}
}
},
"extendedDateRangePickerYearText": "{count, plural, zero{} one{yıl} other{yıllar}}",
"@extendedDateRangePickerYearText": {
"placeholders": {
"count": {}
}
},
"genericAcknowledgeLabel": "Başarılı!",
"@genericAcknowledgeLabel": {},
"genericActionCancelLabel": "İptal",
"@genericActionCancelLabel": {},
"genericActionCreateLabel": "Yarat",
"@genericActionCreateLabel": {},
"genericActionDeleteLabel": "Sil",
"@genericActionDeleteLabel": {},
"genericActionEditLabel": "Düzenle",
"@genericActionEditLabel": {},
"genericActionOkLabel": "Tamam",
"@genericActionOkLabel": {},
"genericActionSaveLabel": "Kaydet",
"@genericActionSaveLabel": {},
"genericActionSelectText": "Seç",
"@genericActionSelectText": {},
"genericActionUpdateLabel": "Değişiklikleri Kaydet",
"@genericActionUpdateLabel": {},
"genericActionUploadLabel": "Yükle",
"@genericActionUploadLabel": {},
"genericMessageOfflineText": "Çevrimdışısınız.",
"@genericMessageOfflineText": {},
"inboxPageAssignAsnLabel": "ASN ata",
"@inboxPageAssignAsnLabel": {},
"inboxPageDocumentRemovedMessageText": "Döküman gelen kutusundan kaldırıldı.",
"@inboxPageDocumentRemovedMessageText": {},
"inboxPageMarkAllAsSeenConfirmationDialogText": "Tüm belgeleri görüntülendi olarak işaretlemek istediğinizden emin misiniz? Bu, tüm gelen kutusu etiketlerini belgelerden kaldırarak, toplu bir düzenleme işlemi gerçekleştirecektir. Bu eylem geri alınamaz! Devam etmek istediğine emin misiniz?",
"@inboxPageMarkAllAsSeenConfirmationDialogText": {},
"inboxPageMarkAllAsSeenConfirmationDialogTitleText": "Tümü görüntülendi olarak işaretlensin mi?",
"@inboxPageMarkAllAsSeenConfirmationDialogTitleText": {},
"inboxPageMarkAllAsSeenLabel": "Hepsi görüntülendi",
"@inboxPageMarkAllAsSeenLabel": {},
"inboxPageMarkAsSeenText": "Görüntülendi olarak işaretle",
"@inboxPageMarkAsSeenText": {},
"inboxPageNoNewDocumentsRefreshLabel": "Yenile",
"@inboxPageNoNewDocumentsRefreshLabel": {},
"inboxPageNoNewDocumentsText": "Görüntülenmemiş belgeniz yok.",
"@inboxPageNoNewDocumentsText": {},
"inboxPageQuickActionsLabel": "Hızlı eylemler",
"@inboxPageQuickActionsLabel": {},
"inboxPageSuggestionSuccessfullyAppliedMessage": "Öneri başarıyla gönderildi.",
"@inboxPageSuggestionSuccessfullyAppliedMessage": {},
"inboxPageTodayText": "Bugün",
"@inboxPageTodayText": {},
"inboxPageUndoRemoveText": "Geri al",
"@inboxPageUndoRemoveText": {},
"inboxPageUnseenText": "görüntülenmemiş",
"@inboxPageUnseenText": {},
"inboxPageUsageHintText": "İpucu: Bir belgeyi görüntülendi olarak işaretlemek ve tüm gelen kutusu etiketlerini belgeden kaldırmak için sola kaydırın.",
"@inboxPageUsageHintText": {},
"inboxPageYesterdayText": "Dün",
"@inboxPageYesterdayText": {},
"labelAnyAssignedText": "Herhangi bir atanan",
"@labelAnyAssignedText": {},
"labelFormFieldNoItemsFoundText": "Hiç bir öğe bulunamadı!",
"@labelFormFieldNoItemsFoundText": {},
"labelIsInsensivitePropertyLabel": "İlişkisiz durum.",
"@labelIsInsensivitePropertyLabel": {},
"labelMatchingAlgorithmPropertyLabel": "Eşleştirme Algoritması",
"@labelMatchingAlgorithmPropertyLabel": {},
"labelMatchPropertyLabel": "",
"@labelMatchPropertyLabel": {},
"labelNamePropertyLabel": "İsim",
"@labelNamePropertyLabel": {},
"labelNotAssignedText": "Atanmadı",
"@labelNotAssignedText": {},
"labelsPageCorrespondentEmptyStateAddNewLabel": "Yeni ek yazar ekle",
"@labelsPageCorrespondentEmptyStateAddNewLabel": {},
"labelsPageCorrespondentEmptyStateDescriptionText": "Herhangi bir ek yazar ayarlamamışsınız gibi görünüyor.",
"@labelsPageCorrespondentEmptyStateDescriptionText": {},
"labelsPageCorrespondentsTitleText": "",
"@labelsPageCorrespondentsTitleText": {},
"labelsPageDocumentTypeEmptyStateAddNewLabel": "Yeni döküman türü ekle",
"@labelsPageDocumentTypeEmptyStateAddNewLabel": {},
"labelsPageDocumentTypeEmptyStateDescriptionText": "Ayarlanmış herhangi bir döküman türünüz yok gibi görünüyor.",
"@labelsPageDocumentTypeEmptyStateDescriptionText": {},
"labelsPageDocumentTypesTitleText": "Döküman Türleri",
"@labelsPageDocumentTypesTitleText": {},
"labelsPageStoragePathEmptyStateAddNewLabel": "Yeni depolama yolu ekle",
"@labelsPageStoragePathEmptyStateAddNewLabel": {},
"labelsPageStoragePathEmptyStateDescriptionText": "Ayarlanmış herhangi bir depolama yolunuz yok gibi görünüyor.",
"@labelsPageStoragePathEmptyStateDescriptionText": {},
"labelsPageStoragePathTitleText": "Depolama Yolları",
"@labelsPageStoragePathTitleText": {},
"labelsPageTagsEmptyStateAddNewLabel": "Yeni etiket ekle",
"@labelsPageTagsEmptyStateAddNewLabel": {},
"labelsPageTagsEmptyStateDescriptionText": "Ayarlanmış herhangi bir etiketiniz yok gibi görünüyor.",
"@labelsPageTagsEmptyStateDescriptionText": {},
"labelsPageTagsTitleText": "Etiketler",
"@labelsPageTagsTitleText": {},
"linkedDocumentsPageTitle": "Bağlantılı Belgeler",
"@linkedDocumentsPageTitle": {},
"loginPageAdvancedLabel": "Gelişmiş Ayarlar",
"@loginPageAdvancedLabel": {},
"loginPageClientCertificatePassphraseLabel": "Gizli şifre",
"@loginPageClientCertificatePassphraseLabel": {},
"loginPageClientCertificateSettingDescriptionText": "Karşılıklı TLS Kimlik Doğrulamasını Yapılandırma",
"@loginPageClientCertificateSettingDescriptionText": {},
"loginPageClientCertificateSettingInvalidFileFormatValidationText": "Geçersiz sertifika biçimi, yalnızca .pfx'e izin verilir",
"@loginPageClientCertificateSettingInvalidFileFormatValidationText": {},
"loginPageClientCertificateSettingLabel": "İstemci Sertifikası",
"@loginPageClientCertificateSettingLabel": {},
"loginPageClientCertificateSettingSelectFileText": "Dosya seç...",
"@loginPageClientCertificateSettingSelectFileText": {},
"loginPageContinueLabel": "Devam",
"@loginPageContinueLabel": {},
"loginPageIncorrectOrMissingCertificatePassphraseErrorMessageText": "Yanlış veya eksik sertifika parolası.",
"@loginPageIncorrectOrMissingCertificatePassphraseErrorMessageText": {},
"loginPageLoginButtonLabel": "Bağlan",
"@loginPageLoginButtonLabel": {},
"loginPagePasswordFieldLabel": "Parola",
"@loginPagePasswordFieldLabel": {},
"loginPagePasswordValidatorMessageText": "Şifre boş bırakılamaz.",
"@loginPagePasswordValidatorMessageText": {},
"loginPageReachabilityConnectionTimeoutText": "Bağlantı zaman aşımına uğradı.",
"@loginPageReachabilityConnectionTimeoutText": {},
"loginPageReachabilityInvalidClientCertificateConfigurationText": "Yanlış veya eksik istemci sertifika parolası.",
"@loginPageReachabilityInvalidClientCertificateConfigurationText": {},
"loginPageReachabilityMissingClientCertificateText": "Bir istemci sertifikası bekleniyordu ancak gönderilmedi. Lütfen bir sertifika sağlayın.",
"@loginPageReachabilityMissingClientCertificateText": {},
"loginPageReachabilityNotReachableText": "Sunucuyla bağlantı kurulamadı.",
"@loginPageReachabilityNotReachableText": {},
"loginPageReachabilitySuccessText": "Bağlantı başarıyla kuruldu.",
"@loginPageReachabilitySuccessText": {},
"loginPageReachabilityUnresolvedHostText": "Ana bilgisayar çözülemedi. Lütfen sunucu adresini ve internet bağlantınızı kontrol edin.",
"@loginPageReachabilityUnresolvedHostText": {},
"loginPageServerUrlFieldLabel": "Sunucu adresi",
"@loginPageServerUrlFieldLabel": {},
"loginPageServerUrlValidatorMessageInvalidAddressText": "Geçersiz adres.",
"@loginPageServerUrlValidatorMessageInvalidAddressText": {},
"loginPageServerUrlValidatorMessageMissingSchemeText": "Sunucu adresi bir şema içermelidir.",
"@loginPageServerUrlValidatorMessageMissingSchemeText": {},
"loginPageServerUrlValidatorMessageRequiredText": "Sunucu adresi boş bırakılamaz.",
"@loginPageServerUrlValidatorMessageRequiredText": {},
"loginPageSignInButtonLabel": "Oturum Aç",
"@loginPageSignInButtonLabel": {},
"loginPageSignInTitle": "Oturum Aç",
"@loginPageSignInTitle": {},
"loginPageSignInToPrefixText": "{serverAddress} adresinde oturum aç",
"@loginPageSignInToPrefixText": {
"placeholders": {
"serverAddress": {}
}
},
"loginPageTitle": "Paperless'a Bağlan",
"@loginPageTitle": {},
"loginPageUsernameLabel": "Kullanıcı adı",
"@loginPageUsernameLabel": {},
"loginPageUsernameValidatorMessageText": "Kullanıcı adı boş bırakılamaz.",
"@loginPageUsernameValidatorMessageText": {},
"matchingAlgorithmAllDescription": "",
"@matchingAlgorithmAllDescription": {},
"matchingAlgorithmAnyDescription": "",
"@matchingAlgorithmAnyDescription": {},
"matchingAlgorithmAutoDescription": "",
"@matchingAlgorithmAutoDescription": {},
"matchingAlgorithmExactDescription": "",
"@matchingAlgorithmExactDescription": {},
"matchingAlgorithmFuzzyDescription": "",
"@matchingAlgorithmFuzzyDescription": {},
"matchingAlgorithmRegexDescription": "",
"@matchingAlgorithmRegexDescription": {},
"offlineWidgetText": "İnternet bağlantısı kurulamadı.",
"@offlineWidgetText": {},
"onboardingDoneButtonLabel": "Bitti",
"@onboardingDoneButtonLabel": {},
"onboardingNextButtonLabel": "İleri",
"@onboardingNextButtonLabel": {},
"receiveSharedFilePermissionDeniedMessage": "Alınan dosyaya erişilemedi. Lütfen paylaşmadan önce uygulamayı açmayı deneyin.",
"@receiveSharedFilePermissionDeniedMessage": {},
"referencedDocumentsReadOnlyHintText": "Bu salt okunur bir görünümdür! Belgeleri düzenleyemez veya kaldıramazsınız. En fazla 100 referanslı belge yüklenecektir.",
"@referencedDocumentsReadOnlyHintText": {},
"savedViewCreateNewLabel": "Yeni görünüm",
"@savedViewCreateNewLabel": {},
"savedViewCreateTooltipText": "Geçerli filtre ölçütlerine göre yeni bir görünüm oluşturur.",
"@savedViewCreateTooltipText": {},
"savedViewNameLabel": "İsim",
"@savedViewNameLabel": {},
"savedViewsEmptyStateText": "Belgelerinizi hızla filtrelemek için görünümler oluşturun.",
"@savedViewsEmptyStateText": {},
"savedViewShowInSidebarLabel": "Kenar çubuğunda göster",
"@savedViewShowInSidebarLabel": {},
"savedViewShowOnDashboardLabel": "Kontrol panelinde göster",
"@savedViewShowOnDashboardLabel": {},
"savedViewsLabel": "Kayıtlı Görünümler",
"@savedViewsLabel": {},
"scannerPageImagePreviewTitle": "Tara",
"@scannerPageImagePreviewTitle": {},
"serverInformationPaperlessVersionText": "Paperless sunucu versiyonu",
"@serverInformationPaperlessVersionText": {},
"settingsPageAppearanceSettingDarkThemeLabel": "Koyu Tema",
"@settingsPageAppearanceSettingDarkThemeLabel": {},
"settingsPageAppearanceSettingLightThemeLabel": "Aydınlık Tema",
"@settingsPageAppearanceSettingLightThemeLabel": {},
"settingsPageAppearanceSettingSystemThemeLabel": "Sistem temasını kullan",
"@settingsPageAppearanceSettingSystemThemeLabel": {},
"settingsPageAppearanceSettingTitle": "Görünüm",
"@settingsPageAppearanceSettingTitle": {},
"settingsPageApplicationSettingsDescriptionText": "Dil ve görsel görünüm",
"@settingsPageApplicationSettingsDescriptionText": {},
"settingsPageApplicationSettingsLabel": "Uygulama",
"@settingsPageApplicationSettingsLabel": {},
"settingsPageLanguageSettingLabel": "Dil",
"@settingsPageLanguageSettingLabel": {},
"settingsPageSecuritySettingsDescriptionText": "Biyometrik kimlik doğrulama",
"@settingsPageSecuritySettingsDescriptionText": {},
"settingsPageSecuritySettingsLabel": "Güvenlik",
"@settingsPageSecuritySettingsLabel": {},
"settingsPageStorageSettingsDescriptionText": "Dosyaları ve depolama alanını yönetin",
"@settingsPageStorageSettingsDescriptionText": {},
"settingsPageStorageSettingsLabel": "Depolama",
"@settingsPageStorageSettingsLabel": {},
"settingsThemeModeDarkLabel": "Koyu",
"@settingsThemeModeDarkLabel": {},
"settingsThemeModeLightLabel": "Aydınlık",
"@settingsThemeModeLightLabel": {},
"settingsThemeModeSystemLabel": "Sistem",
"@settingsThemeModeSystemLabel": {},
"storagePathParameterDayLabel": "gün",
"@storagePathParameterDayLabel": {},
"storagePathParameterMonthLabel": "ay",
"@storagePathParameterMonthLabel": {},
"storagePathParameterYearLabel": "yıl",
"@storagePathParameterYearLabel": {},
"tagColorPropertyLabel": "Renkler",
"@tagColorPropertyLabel": {},
"tagFormFieldSearchHintText": "Etiketleri filtrele...",
"@tagFormFieldSearchHintText": {},
"tagInboxTagPropertyLabel": "Gelen Kutusu-Etiketi",
"@tagInboxTagPropertyLabel": {},
"uploadPageAutomaticallInferredFieldsHintText": "Bu alanlar için değerler belirtirseniz, paperless örneğiniz otomatik olarak bir değer türetmeyecektir. Bu değerlerin sunucunuz tarafından otomatik olarak doldurulmasını istiyorsanız, alanları boş bırakın.",
"@uploadPageAutomaticallInferredFieldsHintText": {},
"verifyIdentityPageDescriptionText": "Belgelerinizin şifresini çözmek ve oturum açmak için biyometrik faktör kullanın.",
"@verifyIdentityPageDescriptionText": {},
"verifyIdentityPageLogoutButtonLabel": "Bağlantıyı kes",
"@verifyIdentityPageLogoutButtonLabel": {},
"verifyIdentityPageTitle": "Kimliğinizi doğrulayın",
"@verifyIdentityPageTitle": {},
"verifyIdentityPageVerifyIdentityButtonLabel": "Kimliği Doğrula",
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
}

View File

@@ -10,7 +10,7 @@ import 'package:flutter/services.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart'; import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/service/github_issue_service.dart'; import 'package:paperless_mobile/core/service/github_issue_service.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';

View File

@@ -0,0 +1,17 @@
import 'dart:developer';
import 'package:json_annotation/json_annotation.dart';
class LocalDateTimeJsonConverter extends JsonConverter<DateTime, String> {
const LocalDateTimeJsonConverter();
@override
DateTime fromJson(String json) {
return DateTime.parse(json).toLocal();
}
@override
String toJson(DateTime object) {
return object.toIso8601String();
}
}

View File

@@ -1,7 +1,13 @@
// ignore_for_file: non_constant_identifier_names // ignore_for_file: non_constant_identifier_names
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
part 'document_model.g.dart';
@LocalDateTimeJsonConverter()
@JsonSerializable(fieldRename: FieldRename.snake)
class DocumentModel extends Equatable { class DocumentModel extends Equatable {
static const idKey = 'id'; static const idKey = 'id';
static const titleKey = 'title'; static const titleKey = 'title';
@@ -47,50 +53,18 @@ class DocumentModel extends Equatable {
this.storagePath, this.storagePath,
}); });
DocumentModel.fromJson(Map<String, dynamic> json) factory DocumentModel.fromJson(Map<String, dynamic> json) =>
: id = json[idKey], _$DocumentModelFromJson(json);
title = json[titleKey],
content = json[contentKey],
created = DateTime.parse(json[createdKey]),
modified = DateTime.parse(json[modifiedKey]),
added = DateTime.parse(json[addedKey]),
archiveSerialNumber = json[asnKey],
originalFileName = json[originalFileNameKey],
archivedFileName = json[archivedFileNameKey],
tags = (json[tagsKey] as List<dynamic>).cast<int>(),
correspondent = json[correspondentKey],
documentType = json[documentTypeKey],
storagePath = json[storagePathKey];
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() => _$DocumentModelToJson(this);
return {
idKey: id,
titleKey: title,
asnKey: archiveSerialNumber,
archivedFileNameKey: archivedFileName,
contentKey: content,
correspondentKey: correspondent,
documentTypeKey: documentType,
createdKey: created.toIso8601String(),
modifiedKey: modified.toIso8601String(),
addedKey: added.toIso8601String(),
originalFileNameKey: originalFileName,
tagsKey: tags.toList(),
storagePathKey: storagePath,
};
}
DocumentModel copyWith({ DocumentModel copyWith({
String? title, String? title,
String? content, String? content,
bool overwriteTags = false,
Iterable<int>? tags, Iterable<int>? tags,
bool overwriteDocumentType = false, int? Function()? documentType,
int? documentType, int? Function()? correspondent,
bool overwriteCorrespondent = false, int? Function()? storagePath,
int? correspondent,
bool overwriteStoragePath = false,
int? storagePath,
DateTime? created, DateTime? created,
DateTime? modified, DateTime? modified,
DateTime? added, DateTime? added,
@@ -102,11 +76,10 @@ class DocumentModel extends Equatable {
id: id, id: id,
title: title ?? this.title, title: title ?? this.title,
content: content ?? this.content, content: content ?? this.content,
documentType: overwriteDocumentType ? documentType : this.documentType, documentType: documentType?.call() ?? this.documentType,
correspondent: correspondent: correspondent?.call() ?? this.correspondent,
overwriteCorrespondent ? correspondent : this.correspondent, storagePath: storagePath?.call() ?? this.storagePath,
storagePath: overwriteDocumentType ? storagePath : this.storagePath, tags: tags ?? this.tags,
tags: overwriteTags ? tags ?? [] : this.tags,
created: created ?? this.created, created: created ?? this.created,
modified: modified ?? this.modified, modified: modified ?? this.modified,
added: added ?? this.added, added: added ?? this.added,

View File

@@ -0,0 +1,45 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'document_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DocumentModel _$DocumentModelFromJson(Map<String, dynamic> json) =>
DocumentModel(
id: json['id'] as int,
title: json['title'] as String,
content: json['content'] as String?,
tags: (json['tags'] as List<dynamic>?)?.map((e) => e as int) ??
const <int>[],
documentType: json['document_type'] as int?,
correspondent: json['correspondent'] as int?,
created: const LocalDateTimeJsonConverter()
.fromJson(json['created'] as String),
modified: const LocalDateTimeJsonConverter()
.fromJson(json['modified'] as String),
added:
const LocalDateTimeJsonConverter().fromJson(json['added'] as String),
archiveSerialNumber: json['archive_serial_number'] as int?,
originalFileName: json['original_file_name'] as String,
archivedFileName: json['archived_file_name'] as String?,
storagePath: json['storage_path'] as int?,
);
Map<String, dynamic> _$DocumentModelToJson(DocumentModel instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
'content': instance.content,
'tags': instance.tags.toList(),
'document_type': instance.documentType,
'correspondent': instance.correspondent,
'storage_path': instance.storagePath,
'created': const LocalDateTimeJsonConverter().toJson(instance.created),
'modified': const LocalDateTimeJsonConverter().toJson(instance.modified),
'added': const LocalDateTimeJsonConverter().toJson(instance.added),
'archive_serial_number': instance.archiveSerialNumber,
'original_file_name': instance.originalFileName,
'archived_file_name': instance.archivedFileName,
};

View File

@@ -1,14 +1,16 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
import 'package:paperless_api/src/models/document_model.dart';
part 'field_suggestions.g.dart'; part 'field_suggestions.g.dart';
@LocalDateTimeJsonConverter()
@JsonSerializable(fieldRename: FieldRename.snake) @JsonSerializable(fieldRename: FieldRename.snake)
class FieldSuggestions { class FieldSuggestions {
final int? documentId; final int? documentId;
final Iterable<int> correspondents; final Iterable<int> correspondents;
final Iterable<int> tags; final Iterable<int> tags;
final Iterable<int> documentTypes; final Iterable<int> documentTypes;
final Iterable<int> storagePaths;
final Iterable<DateTime> dates; final Iterable<DateTime> dates;
const FieldSuggestions({ const FieldSuggestions({
@@ -16,28 +18,24 @@ class FieldSuggestions {
this.correspondents = const [], this.correspondents = const [],
this.tags = const [], this.tags = const [],
this.documentTypes = const [], this.documentTypes = const [],
this.storagePaths = const [],
this.dates = const [], this.dates = const [],
}); });
bool get hasSuggestedCorrespondents => correspondents.isNotEmpty; bool get hasSuggestedCorrespondents => correspondents.isNotEmpty;
bool get hasSuggestedTags => tags.isNotEmpty; bool get hasSuggestedTags => tags.isNotEmpty;
bool get hasSuggestedDocumentTypes => documentTypes.isNotEmpty; bool get hasSuggestedDocumentTypes => documentTypes.isNotEmpty;
bool get hasSuggestedStoragePaths => storagePaths.isNotEmpty;
bool get hasSuggestedDates => dates.isNotEmpty; bool get hasSuggestedDates => dates.isNotEmpty;
bool get hasSuggestions => bool get hasSuggestions =>
hasSuggestedCorrespondents || hasSuggestedCorrespondents ||
hasSuggestedDates || hasSuggestedDates ||
hasSuggestedTags || hasSuggestedTags ||
hasSuggestedStoragePaths ||
hasSuggestedDocumentTypes; hasSuggestedDocumentTypes;
int get suggestionsCount => int get suggestionsCount =>
(correspondents.isNotEmpty ? 1 : 0) + (correspondents.isNotEmpty ? 1 : 0) +
(tags.isNotEmpty ? 1 : 0) + (tags.isNotEmpty ? 1 : 0) +
(documentTypes.isNotEmpty ? 1 : 0) + (documentTypes.isNotEmpty ? 1 : 0) +
(storagePaths.isNotEmpty ? 1 : 0) +
(dates.isNotEmpty ? 1 : 0); (dates.isNotEmpty ? 1 : 0);
FieldSuggestions forDocumentId(int id) => FieldSuggestions( FieldSuggestions forDocumentId(int id) => FieldSuggestions(
@@ -46,9 +44,53 @@ class FieldSuggestions {
dates: dates, dates: dates,
documentTypes: documentTypes, documentTypes: documentTypes,
tags: tags, tags: tags,
storagePaths: storagePaths,
); );
///
/// Removes the suggestions given in the parameters.
///
FieldSuggestions difference({
Iterable<int> tags = const {},
Iterable<int> correspondents = const {},
Iterable<int> documentTypes = const {},
Iterable<DateTime> dates = const {},
}) {
return copyWith(
tags: this.tags.toSet().difference(tags.toSet()),
correspondents:
this.correspondents.toSet().difference(correspondents.toSet()),
documentTypes:
this.documentTypes.toSet().difference(documentTypes.toSet()),
dates: this.dates.toSet().difference(dates.toSet()),
);
}
FieldSuggestions documentDifference(DocumentModel document) {
return difference(
tags: document.tags,
correspondents:
[document.correspondent].where((e) => e != null).map((e) => e!),
documentTypes:
[document.documentType].where((e) => e != null).map((e) => e!),
dates: [document.created],
);
}
FieldSuggestions copyWith({
Iterable<int>? tags,
Iterable<int>? correspondents,
Iterable<int>? documentTypes,
Iterable<DateTime>? dates,
int? documentId,
}) {
return FieldSuggestions(
tags: tags ?? this.tags,
correspondents: correspondents ?? this.correspondents,
dates: dates ?? this.dates,
documentId: documentId ?? this.documentId,
);
}
factory FieldSuggestions.fromJson(Map<String, dynamic> json) => factory FieldSuggestions.fromJson(Map<String, dynamic> json) =>
_$FieldSuggestionsFromJson(json); _$FieldSuggestionsFromJson(json);

View File

@@ -16,11 +16,8 @@ FieldSuggestions _$FieldSuggestionsFromJson(Map<String, dynamic> json) =>
documentTypes: documentTypes:
(json['document_types'] as List<dynamic>?)?.map((e) => e as int) ?? (json['document_types'] as List<dynamic>?)?.map((e) => e as int) ??
const [], const [],
storagePaths: dates: (json['dates'] as List<dynamic>?)?.map((e) =>
(json['storage_paths'] as List<dynamic>?)?.map((e) => e as int) ?? const LocalDateTimeJsonConverter().fromJson(e as String)) ??
const [],
dates: (json['dates'] as List<dynamic>?)
?.map((e) => DateTime.parse(e as String)) ??
const [], const [],
); );
@@ -30,6 +27,7 @@ Map<String, dynamic> _$FieldSuggestionsToJson(FieldSuggestions instance) =>
'correspondents': instance.correspondents.toList(), 'correspondents': instance.correspondents.toList(),
'tags': instance.tags.toList(), 'tags': instance.tags.toList(),
'document_types': instance.documentTypes.toList(), 'document_types': instance.documentTypes.toList(),
'storage_paths': instance.storagePaths.toList(), 'dates': instance.dates
'dates': instance.dates.map((e) => e.toIso8601String()).toList(), .map(const LocalDateTimeJsonConverter().toJson)
.toList(),
}; };

View File

@@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_api/src/constants.dart'; import 'package:paperless_api/src/constants.dart';
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
import 'query_parameters/tags_query/any_assigned_tags_query.dart'; import 'query_parameters/tags_query/any_assigned_tags_query.dart';
import 'query_parameters/tags_query/exclude_tag_id_query.dart'; import 'query_parameters/tags_query/exclude_tag_id_query.dart';
@@ -13,6 +14,7 @@ part 'filter_rule_model.g.dart';
@JsonSerializable() @JsonSerializable()
class FilterRule with EquatableMixin { class FilterRule with EquatableMixin {
static const _dateTimeConverter = LocalDateTimeJsonConverter();
static const int titleRule = 0; static const int titleRule = 0;
static const int asnRule = 2; static const int asnRule = 2;
static const int correspondentRule = 3; static const int correspondentRule = 3;
@@ -95,66 +97,72 @@ class FilterRule with EquatableMixin {
if (filter.created is AbsoluteDateRangeQuery) { if (filter.created is AbsoluteDateRangeQuery) {
return filter.copyWith( return filter.copyWith(
created: (filter.created as AbsoluteDateRangeQuery) created: (filter.created as AbsoluteDateRangeQuery)
.copyWith(before: DateTime.parse(value!)), .copyWith(before: _dateTimeConverter.fromJson(value!)),
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
created: AbsoluteDateRangeQuery(before: DateTime.parse(value!)), created: AbsoluteDateRangeQuery(
before: _dateTimeConverter.fromJson(value!)),
); );
} }
case createdAfterRule: case createdAfterRule:
if (filter.created is AbsoluteDateRangeQuery) { if (filter.created is AbsoluteDateRangeQuery) {
return filter.copyWith( return filter.copyWith(
created: (filter.created as AbsoluteDateRangeQuery) created: (filter.created as AbsoluteDateRangeQuery)
.copyWith(after: DateTime.parse(value!)), .copyWith(after: _dateTimeConverter.fromJson(value!)),
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
created: AbsoluteDateRangeQuery(after: DateTime.parse(value!)), created: AbsoluteDateRangeQuery(
after: _dateTimeConverter.fromJson(value!)),
); );
} }
case addedBeforeRule: case addedBeforeRule:
if (filter.added is AbsoluteDateRangeQuery) { if (filter.added is AbsoluteDateRangeQuery) {
return filter.copyWith( return filter.copyWith(
added: (filter.added as AbsoluteDateRangeQuery) added: (filter.added as AbsoluteDateRangeQuery)
.copyWith(before: DateTime.parse(value!)), .copyWith(before: _dateTimeConverter.fromJson(value!)),
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
added: AbsoluteDateRangeQuery(before: DateTime.parse(value!)), added: AbsoluteDateRangeQuery(
before: _dateTimeConverter.fromJson(value!)),
); );
} }
case addedAfterRule: case addedAfterRule:
if (filter.added is AbsoluteDateRangeQuery) { if (filter.added is AbsoluteDateRangeQuery) {
return filter.copyWith( return filter.copyWith(
added: (filter.added as AbsoluteDateRangeQuery) added: (filter.added as AbsoluteDateRangeQuery)
.copyWith(after: DateTime.parse(value!)), .copyWith(after: _dateTimeConverter.fromJson(value!)),
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
added: AbsoluteDateRangeQuery(after: DateTime.parse(value!)), added: AbsoluteDateRangeQuery(
after: _dateTimeConverter.fromJson(value!)),
); );
} }
case modifiedBeforeRule: case modifiedBeforeRule:
if (filter.modified is AbsoluteDateRangeQuery) { if (filter.modified is AbsoluteDateRangeQuery) {
return filter.copyWith( return filter.copyWith(
modified: (filter.modified as AbsoluteDateRangeQuery) modified: (filter.modified as AbsoluteDateRangeQuery)
.copyWith(before: DateTime.parse(value!)), .copyWith(before: _dateTimeConverter.fromJson(value!)),
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
modified: AbsoluteDateRangeQuery(before: DateTime.parse(value!)), modified: AbsoluteDateRangeQuery(
before: _dateTimeConverter.fromJson(value!)),
); );
} }
case modifiedAfterRule: case modifiedAfterRule:
if (filter.modified is AbsoluteDateRangeQuery) { if (filter.modified is AbsoluteDateRangeQuery) {
return filter.copyWith( return filter.copyWith(
modified: (filter.modified as AbsoluteDateRangeQuery) modified: (filter.modified as AbsoluteDateRangeQuery)
.copyWith(after: DateTime.parse(value!)), .copyWith(after: _dateTimeConverter.fromJson(value!)),
); );
} else { } else {
return filter.copyWith( return filter.copyWith(
added: AbsoluteDateRangeQuery(after: DateTime.parse(value!)), added: AbsoluteDateRangeQuery(
after: _dateTimeConverter.fromJson(value!)),
); );
} }
case titleAndContentRule: case titleAndContentRule:
@@ -347,15 +355,16 @@ class FilterRule with EquatableMixin {
} }
//Join values of all extended filter rules if exist //Join values of all extended filter rules if exist
if (filterRules.isNotEmpty) { if (filterRules.isNotEmpty &&
final FilterRule extendedFilterRule = filterRules filterRules.where((e) => e.ruleType == FilterRule.extendedRule).length >
.where((r) => r.ruleType == extendedRule) 1) {
.reduce((previousValue, element) => previousValue.copyWith( final mergedExtendedRule = filterRules
value: previousValue.value! + element.value!, .where((r) => r.ruleType == FilterRule.extendedRule)
)); .map((e) => e.value)
.join(",");
filterRules filterRules
..removeWhere((element) => element.ruleType == extendedRule) ..removeWhere((element) => element.ruleType == extendedRule)
..add(extendedFilterRule); ..add(FilterRule(FilterRule.extendedRule, mergedExtendedRule));
} }
return filterRules; return filterRules;
} }

View File

@@ -1,9 +1,11 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
import 'package:paperless_api/src/models/labels/label_model.dart'; import 'package:paperless_api/src/models/labels/label_model.dart';
import 'package:paperless_api/src/models/labels/matching_algorithm.dart'; import 'package:paperless_api/src/models/labels/matching_algorithm.dart';
part 'correspondent_model.g.dart'; part 'correspondent_model.g.dart';
@LocalDateTimeJsonConverter()
@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake) @JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
class Correspondent extends Label { class Correspondent extends Label {
final DateTime? lastCorrespondence; final DateTime? lastCorrespondence;
@@ -13,7 +15,7 @@ class Correspondent extends Label {
required super.name, required super.name,
super.slug, super.slug,
super.match, super.match,
super.matchingAlgorithm, required super.matchingAlgorithm,
super.isInsensitive, super.isInsensitive,
super.documentCount, super.documentCount,
this.lastCorrespondence, this.lastCorrespondence,

View File

@@ -12,13 +12,13 @@ Correspondent _$CorrespondentFromJson(Map<String, dynamic> json) =>
name: json['name'] as String, name: json['name'] as String,
slug: json['slug'] as String?, slug: json['slug'] as String?,
match: json['match'] as String?, match: json['match'] as String?,
matchingAlgorithm: $enumDecodeNullable( matchingAlgorithm:
_$MatchingAlgorithmEnumMap, json['matching_algorithm']), $enumDecode(_$MatchingAlgorithmEnumMap, json['matching_algorithm']),
isInsensitive: json['is_insensitive'] as bool?, isInsensitive: json['is_insensitive'] as bool?,
documentCount: json['document_count'] as int?, documentCount: json['document_count'] as int?,
lastCorrespondence: json['last_correspondence'] == null lastCorrespondence: _$JsonConverterFromJson<String, DateTime>(
? null json['last_correspondence'],
: DateTime.parse(json['last_correspondence'] as String), const LocalDateTimeJsonConverter().fromJson),
); );
Map<String, dynamic> _$CorrespondentToJson(Correspondent instance) { Map<String, dynamic> _$CorrespondentToJson(Correspondent instance) {
@@ -34,12 +34,14 @@ Map<String, dynamic> _$CorrespondentToJson(Correspondent instance) {
val['name'] = instance.name; val['name'] = instance.name;
writeNotNull('slug', instance.slug); writeNotNull('slug', instance.slug);
writeNotNull('match', instance.match); writeNotNull('match', instance.match);
writeNotNull('matching_algorithm', val['matching_algorithm'] =
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]); _$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]!;
writeNotNull('is_insensitive', instance.isInsensitive); writeNotNull('is_insensitive', instance.isInsensitive);
writeNotNull('document_count', instance.documentCount); writeNotNull('document_count', instance.documentCount);
writeNotNull( writeNotNull(
'last_correspondence', instance.lastCorrespondence?.toIso8601String()); 'last_correspondence',
_$JsonConverterToJson<String, DateTime>(instance.lastCorrespondence,
const LocalDateTimeJsonConverter().toJson));
return val; return val;
} }
@@ -48,6 +50,18 @@ const _$MatchingAlgorithmEnumMap = {
MatchingAlgorithm.allWords: 2, MatchingAlgorithm.allWords: 2,
MatchingAlgorithm.exactMatch: 3, MatchingAlgorithm.exactMatch: 3,
MatchingAlgorithm.regex: 4, MatchingAlgorithm.regex: 4,
MatchingAlgorithm.similarWord: 5, MatchingAlgorithm.fuzzy: 5,
MatchingAlgorithm.auto: 6, MatchingAlgorithm.auto: 6,
}; };
Value? _$JsonConverterFromJson<Json, Value>(
Object? json,
Value? Function(Json json) fromJson,
) =>
json == null ? null : fromJson(json as Json);
Json? _$JsonConverterToJson<Json, Value>(
Value? value,
Json? Function(Value value) toJson,
) =>
value == null ? null : toJson(value);

View File

@@ -10,7 +10,7 @@ class DocumentType extends Label {
required super.name, required super.name,
super.slug, super.slug,
super.match, super.match,
super.matchingAlgorithm, required super.matchingAlgorithm,
super.isInsensitive, super.isInsensitive,
super.documentCount, super.documentCount,
}); });

View File

@@ -11,8 +11,8 @@ DocumentType _$DocumentTypeFromJson(Map<String, dynamic> json) => DocumentType(
name: json['name'] as String, name: json['name'] as String,
slug: json['slug'] as String?, slug: json['slug'] as String?,
match: json['match'] as String?, match: json['match'] as String?,
matchingAlgorithm: $enumDecodeNullable( matchingAlgorithm:
_$MatchingAlgorithmEnumMap, json['matching_algorithm']), $enumDecode(_$MatchingAlgorithmEnumMap, json['matching_algorithm']),
isInsensitive: json['is_insensitive'] as bool?, isInsensitive: json['is_insensitive'] as bool?,
documentCount: json['document_count'] as int?, documentCount: json['document_count'] as int?,
); );
@@ -30,8 +30,8 @@ Map<String, dynamic> _$DocumentTypeToJson(DocumentType instance) {
val['name'] = instance.name; val['name'] = instance.name;
writeNotNull('slug', instance.slug); writeNotNull('slug', instance.slug);
writeNotNull('match', instance.match); writeNotNull('match', instance.match);
writeNotNull('matching_algorithm', val['matching_algorithm'] =
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]); _$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]!;
writeNotNull('is_insensitive', instance.isInsensitive); writeNotNull('is_insensitive', instance.isInsensitive);
writeNotNull('document_count', instance.documentCount); writeNotNull('document_count', instance.documentCount);
return val; return val;
@@ -42,6 +42,6 @@ const _$MatchingAlgorithmEnumMap = {
MatchingAlgorithm.allWords: 2, MatchingAlgorithm.allWords: 2,
MatchingAlgorithm.exactMatch: 3, MatchingAlgorithm.exactMatch: 3,
MatchingAlgorithm.regex: 4, MatchingAlgorithm.regex: 4,
MatchingAlgorithm.similarWord: 5, MatchingAlgorithm.fuzzy: 5,
MatchingAlgorithm.auto: 6, MatchingAlgorithm.auto: 6,
}; };

View File

@@ -21,7 +21,7 @@ abstract class Label extends Equatable implements Comparable {
@JsonKey() @JsonKey()
final String? match; final String? match;
@JsonKey() @JsonKey()
final MatchingAlgorithm? matchingAlgorithm; final MatchingAlgorithm matchingAlgorithm;
@JsonKey() @JsonKey()
final bool? isInsensitive; final bool? isInsensitive;
@JsonKey() @JsonKey()
@@ -30,8 +30,8 @@ abstract class Label extends Equatable implements Comparable {
const Label({ const Label({
required this.id, required this.id,
required this.name, required this.name,
required this.matchingAlgorithm,
this.match, this.match,
this.matchingAlgorithm,
this.isInsensitive, this.isInsensitive,
this.documentCount, this.documentCount,
this.slug, this.slug,

View File

@@ -6,7 +6,7 @@ enum MatchingAlgorithm {
allWords(2, "All: Match all of the following words"), allWords(2, "All: Match all of the following words"),
exactMatch(3, "Exact: Match the following string"), exactMatch(3, "Exact: Match the following string"),
regex(4, "Regex: Match the regular expression"), regex(4, "Regex: Match the regular expression"),
similarWord(5, "Similar: Match a similar word"), fuzzy(5, "Similar: Match a similar word"),
auto(6, "Auto: Learn automatic assignment"); auto(6, "Auto: Learn automatic assignment");
final int value; final int value;

View File

@@ -13,7 +13,7 @@ class StoragePath extends Label {
required super.name, required super.name,
super.slug, super.slug,
super.match, super.match,
super.matchingAlgorithm, required super.matchingAlgorithm,
super.isInsensitive, super.isInsensitive,
super.documentCount, super.documentCount,
required this.path, required this.path,

View File

@@ -11,8 +11,8 @@ StoragePath _$StoragePathFromJson(Map<String, dynamic> json) => StoragePath(
name: json['name'] as String, name: json['name'] as String,
slug: json['slug'] as String?, slug: json['slug'] as String?,
match: json['match'] as String?, match: json['match'] as String?,
matchingAlgorithm: $enumDecodeNullable( matchingAlgorithm:
_$MatchingAlgorithmEnumMap, json['matching_algorithm']), $enumDecode(_$MatchingAlgorithmEnumMap, json['matching_algorithm']),
isInsensitive: json['is_insensitive'] as bool?, isInsensitive: json['is_insensitive'] as bool?,
documentCount: json['document_count'] as int?, documentCount: json['document_count'] as int?,
path: json['path'] as String?, path: json['path'] as String?,
@@ -31,8 +31,8 @@ Map<String, dynamic> _$StoragePathToJson(StoragePath instance) {
val['name'] = instance.name; val['name'] = instance.name;
writeNotNull('slug', instance.slug); writeNotNull('slug', instance.slug);
writeNotNull('match', instance.match); writeNotNull('match', instance.match);
writeNotNull('matching_algorithm', val['matching_algorithm'] =
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]); _$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]!;
writeNotNull('is_insensitive', instance.isInsensitive); writeNotNull('is_insensitive', instance.isInsensitive);
writeNotNull('document_count', instance.documentCount); writeNotNull('document_count', instance.documentCount);
writeNotNull('path', instance.path); writeNotNull('path', instance.path);
@@ -44,6 +44,6 @@ const _$MatchingAlgorithmEnumMap = {
MatchingAlgorithm.allWords: 2, MatchingAlgorithm.allWords: 2,
MatchingAlgorithm.exactMatch: 3, MatchingAlgorithm.exactMatch: 3,
MatchingAlgorithm.regex: 4, MatchingAlgorithm.regex: 4,
MatchingAlgorithm.similarWord: 5, MatchingAlgorithm.fuzzy: 5,
MatchingAlgorithm.auto: 6, MatchingAlgorithm.auto: 6,
}; };

View File

@@ -27,7 +27,7 @@ class Tag extends Label {
super.documentCount, super.documentCount,
super.isInsensitive, super.isInsensitive,
super.match, super.match,
super.matchingAlgorithm, required super.matchingAlgorithm,
super.slug, super.slug,
Color? color, Color? color,
this.textColor, this.textColor,
@@ -84,13 +84,14 @@ class Tag extends Label {
match, match,
]; ];
//FIXME: Why is this not generated?!
factory Tag.fromJson(Map<String, dynamic> json) { factory Tag.fromJson(Map<String, dynamic> json) {
const $MatchingAlgorithmEnumMap = { const $MatchingAlgorithmEnumMap = {
MatchingAlgorithm.anyWord: 1, MatchingAlgorithm.anyWord: 1,
MatchingAlgorithm.allWords: 2, MatchingAlgorithm.allWords: 2,
MatchingAlgorithm.exactMatch: 3, MatchingAlgorithm.exactMatch: 3,
MatchingAlgorithm.regex: 4, MatchingAlgorithm.regex: 4,
MatchingAlgorithm.similarWord: 5, MatchingAlgorithm.fuzzy: 5,
MatchingAlgorithm.auto: 6, MatchingAlgorithm.auto: 6,
}; };
@@ -100,8 +101,8 @@ class Tag extends Label {
documentCount: json['document_count'] as int?, documentCount: json['document_count'] as int?,
isInsensitive: json['is_insensitive'] as bool?, isInsensitive: json['is_insensitive'] as bool?,
match: json['match'] as String?, match: json['match'] as String?,
matchingAlgorithm: $enumDecodeNullable( matchingAlgorithm:
$MatchingAlgorithmEnumMap, json['matching_algorithm']), $enumDecode($MatchingAlgorithmEnumMap, json['matching_algorithm']),
slug: json['slug'] as String?, slug: json['slug'] as String?,
textColor: _colorFromJson(json['text_color']), textColor: _colorFromJson(json['text_color']),
isInboxTag: json['is_inbox_tag'] as bool?, isInboxTag: json['is_inbox_tag'] as bool?,
@@ -118,7 +119,7 @@ class Tag extends Label {
MatchingAlgorithm.allWords: 2, MatchingAlgorithm.allWords: 2,
MatchingAlgorithm.exactMatch: 3, MatchingAlgorithm.exactMatch: 3,
MatchingAlgorithm.regex: 4, MatchingAlgorithm.regex: 4,
MatchingAlgorithm.similarWord: 5, MatchingAlgorithm.fuzzy: 5,
MatchingAlgorithm.auto: 6, MatchingAlgorithm.auto: 6,
}; };

View File

@@ -45,6 +45,8 @@ class PagedSearchResult<T> extends Equatable {
return 1; return 1;
} }
int get pageSize => results.length;
const PagedSearchResult({ const PagedSearchResult({
required this.count, required this.count,
required this.next, required this.next,
@@ -80,11 +82,11 @@ class PagedSearchResult<T> extends Equatable {
return PagedSearchResult.fromJsonT(serializer.json, serializer.converter); return PagedSearchResult.fromJsonT(serializer.json, serializer.converter);
} }
PagedSearchResult copyWith({ PagedSearchResult<T> copyWith({
int? count, int? count,
String? next, String? next,
String? previous, String? previous,
List<DocumentModel>? results, List<T>? results,
}) { }) {
return PagedSearchResult( return PagedSearchResult(
count: count ?? this.count, count: count ?? this.count,

View File

@@ -1,5 +1,6 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/src/constants.dart'; import 'package:paperless_api/src/constants.dart';
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
import 'date_range_query.dart'; import 'date_range_query.dart';
import 'date_range_query_field.dart'; import 'date_range_query_field.dart';
@@ -8,7 +9,10 @@ part 'absolute_date_range_query.g.dart';
@JsonSerializable() @JsonSerializable()
class AbsoluteDateRangeQuery extends DateRangeQuery { class AbsoluteDateRangeQuery extends DateRangeQuery {
@LocalDateTimeJsonConverter()
final DateTime? after; final DateTime? after;
@LocalDateTimeJsonConverter()
final DateTime? before; final DateTime? before;
const AbsoluteDateRangeQuery({this.after, this.before}); const AbsoluteDateRangeQuery({this.after, this.before});

View File

@@ -9,17 +9,29 @@ part of 'absolute_date_range_query.dart';
AbsoluteDateRangeQuery _$AbsoluteDateRangeQueryFromJson( AbsoluteDateRangeQuery _$AbsoluteDateRangeQueryFromJson(
Map<String, dynamic> json) => Map<String, dynamic> json) =>
AbsoluteDateRangeQuery( AbsoluteDateRangeQuery(
after: json['after'] == null after: _$JsonConverterFromJson<String, DateTime>(
? null json['after'], const LocalDateTimeJsonConverter().fromJson),
: DateTime.parse(json['after'] as String), before: _$JsonConverterFromJson<String, DateTime>(
before: json['before'] == null json['before'], const LocalDateTimeJsonConverter().fromJson),
? null
: DateTime.parse(json['before'] as String),
); );
Map<String, dynamic> _$AbsoluteDateRangeQueryToJson( Map<String, dynamic> _$AbsoluteDateRangeQueryToJson(
AbsoluteDateRangeQuery instance) => AbsoluteDateRangeQuery instance) =>
<String, dynamic>{ <String, dynamic>{
'after': instance.after?.toIso8601String(), 'after': _$JsonConverterToJson<String, DateTime>(
'before': instance.before?.toIso8601String(), instance.after, const LocalDateTimeJsonConverter().toJson),
'before': _$JsonConverterToJson<String, DateTime>(
instance.before, const LocalDateTimeJsonConverter().toJson),
}; };
Value? _$JsonConverterFromJson<Json, Value>(
Object? json,
Value? Function(Json json) fromJson,
) =>
json == null ? null : fromJson(json as Json);
Json? _$JsonConverterToJson<Json, Value>(
Value? value,
Json? Function(Value value) toJson,
) =>
value == null ? null : toJson(value);

View File

@@ -0,0 +1,21 @@
import 'package:json_annotation/json_annotation.dart';
part 'search_hit.g.dart';
@JsonSerializable()
class SearchHit {
final double? score;
final String? highlights;
final int? rank;
SearchHit({
this.score,
required this.highlights,
required this.rank,
});
factory SearchHit.fromJson(Map<String, dynamic> json) =>
_$SearchHitFromJson(json);
Map<String, dynamic> toJson() => _$SearchHitToJson(this);
}

View File

@@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'search_hit.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
SearchHit _$SearchHitFromJson(Map<String, dynamic> json) => SearchHit(
score: (json['score'] as num?)?.toDouble(),
highlights: json['highlights'] as String?,
rank: json['rank'] as int?,
);
Map<String, dynamic> _$SearchHitToJson(SearchHit instance) => <String, dynamic>{
'score': instance.score,
'highlights': instance.highlights,
'rank': instance.rank,
};

View File

@@ -1,6 +1,14 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
import 'package:paperless_api/src/models/document_model.dart'; import 'package:paperless_api/src/models/document_model.dart';
import 'package:paperless_api/src/models/search_hit.dart';
part 'similar_document_model.g.dart';
@LocalDateTimeJsonConverter()
@JsonSerializable()
class SimilarDocumentModel extends DocumentModel { class SimilarDocumentModel extends DocumentModel {
@JsonKey(name: '__search_hit__')
final SearchHit searchHit; final SearchHit searchHit;
const SimilarDocumentModel({ const SimilarDocumentModel({
@@ -20,39 +28,9 @@ class SimilarDocumentModel extends DocumentModel {
super.tags, super.tags,
}); });
factory SimilarDocumentModel.fromJson(Map<String, dynamic> json) =>
_$SimilarDocumentModelFromJson(json);
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() => _$SimilarDocumentModelToJson(this);
final json = super.toJson();
json['__search_hit__'] = searchHit.toJson();
return json;
}
SimilarDocumentModel.fromJson(Map<String, dynamic> json)
: searchHit = SearchHit.fromJson(json),
super.fromJson(json);
}
class SearchHit {
final double? score;
final String? highlights;
final int? rank;
SearchHit({
this.score,
required this.highlights,
required this.rank,
});
Map<String, dynamic> toJson() {
return {
'score': score,
'highlights': highlights,
'rank': rank,
};
}
SearchHit.fromJson(Map<String, dynamic> json)
: score = json['score'],
highlights = json['highlights'],
rank = json['rank'];
} }

View File

@@ -0,0 +1,50 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'similar_document_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
SimilarDocumentModel _$SimilarDocumentModelFromJson(
Map<String, dynamic> json) =>
SimilarDocumentModel(
id: json['id'] as int,
title: json['title'] as String,
documentType: json['documentType'] as int?,
correspondent: json['correspondent'] as int?,
created: const LocalDateTimeJsonConverter()
.fromJson(json['created'] as String),
modified: const LocalDateTimeJsonConverter()
.fromJson(json['modified'] as String),
added:
const LocalDateTimeJsonConverter().fromJson(json['added'] as String),
originalFileName: json['originalFileName'] as String,
searchHit:
SearchHit.fromJson(json['__search_hit__'] as Map<String, dynamic>),
archiveSerialNumber: json['archiveSerialNumber'] as int?,
archivedFileName: json['archivedFileName'] as String?,
content: json['content'] as String?,
storagePath: json['storagePath'] as int?,
tags: (json['tags'] as List<dynamic>?)?.map((e) => e as int) ??
const <int>[],
);
Map<String, dynamic> _$SimilarDocumentModelToJson(
SimilarDocumentModel instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
'content': instance.content,
'tags': instance.tags.toList(),
'documentType': instance.documentType,
'correspondent': instance.correspondent,
'storagePath': instance.storagePath,
'created': const LocalDateTimeJsonConverter().toJson(instance.created),
'modified': const LocalDateTimeJsonConverter().toJson(instance.modified),
'added': const LocalDateTimeJsonConverter().toJson(instance.added),
'archiveSerialNumber': instance.archiveSerialNumber,
'originalFileName': instance.originalFileName,
'archivedFileName': instance.archivedFileName,
'__search_hit__': instance.searchHit,
};

View File

@@ -1,10 +1,12 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
import 'package:paperless_api/src/request_utils.dart'; import 'package:paperless_api/src/request_utils.dart';
import 'task_status.dart'; import 'task_status.dart';
part 'task.g.dart'; part 'task.g.dart';
@LocalDateTimeJsonConverter()
@JsonSerializable(fieldRename: FieldRename.snake) @JsonSerializable(fieldRename: FieldRename.snake)
class Task extends Equatable { class Task extends Equatable {
final int id; final int id;

View File

@@ -10,10 +10,10 @@ Task _$TaskFromJson(Map<String, dynamic> json) => Task(
id: json['id'] as int, id: json['id'] as int,
taskId: json['task_id'] as String?, taskId: json['task_id'] as String?,
taskFileName: json['task_file_name'] as String?, taskFileName: json['task_file_name'] as String?,
dateCreated: DateTime.parse(json['date_created'] as String), dateCreated: const LocalDateTimeJsonConverter()
dateDone: json['date_done'] == null .fromJson(json['date_created'] as String),
? null dateDone: _$JsonConverterFromJson<String, DateTime>(
: DateTime.parse(json['date_done'] as String), json['date_done'], const LocalDateTimeJsonConverter().fromJson),
type: json['type'] as String?, type: json['type'] as String?,
status: $enumDecodeNullable(_$TaskStatusEnumMap, json['status']), status: $enumDecodeNullable(_$TaskStatusEnumMap, json['status']),
acknowledged: json['acknowledged'] as bool? ?? false, acknowledged: json['acknowledged'] as bool? ?? false,
@@ -25,8 +25,10 @@ Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{
'id': instance.id, 'id': instance.id,
'task_id': instance.taskId, 'task_id': instance.taskId,
'task_file_name': instance.taskFileName, 'task_file_name': instance.taskFileName,
'date_created': instance.dateCreated.toIso8601String(), 'date_created':
'date_done': instance.dateDone?.toIso8601String(), const LocalDateTimeJsonConverter().toJson(instance.dateCreated),
'date_done': _$JsonConverterToJson<String, DateTime>(
instance.dateDone, const LocalDateTimeJsonConverter().toJson),
'type': instance.type, 'type': instance.type,
'status': _$TaskStatusEnumMap[instance.status], 'status': _$TaskStatusEnumMap[instance.status],
'result': instance.result, 'result': instance.result,
@@ -34,9 +36,21 @@ Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{
'related_document': instance.relatedDocument, 'related_document': instance.relatedDocument,
}; };
Value? _$JsonConverterFromJson<Json, Value>(
Object? json,
Value? Function(Json json) fromJson,
) =>
json == null ? null : fromJson(json as Json);
const _$TaskStatusEnumMap = { const _$TaskStatusEnumMap = {
TaskStatus.started: 'STARTED', TaskStatus.started: 'STARTED',
TaskStatus.pending: 'PENDING', TaskStatus.pending: 'PENDING',
TaskStatus.failure: 'FAILURE', TaskStatus.failure: 'FAILURE',
TaskStatus.success: 'SUCCESS', TaskStatus.success: 'SUCCESS',
}; };
Json? _$JsonConverterToJson<Json, Value>(
Value? value,
Json? Function(Value value) toJson,
) =>
value == null ? null : toJson(value);

View File

@@ -2,10 +2,14 @@ import 'dart:convert';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_api/src/constants.dart'; import 'package:paperless_api/src/constants.dart';
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi { class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
static const _dateTimeConverter = LocalDateTimeJsonConverter();
final Dio client; final Dio client;
PaperlessDocumentsApiImpl(this.client); PaperlessDocumentsApiImpl(this.client);
@@ -21,16 +25,19 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
int? correspondent, int? correspondent,
Iterable<int> tags = const [], Iterable<int> tags = const [],
}) async { }) async {
final formData = FormData() final formData = FormData();
..files.add( formData.files.add(
MapEntry( MapEntry(
'document', 'document',
MultipartFile.fromBytes(documentBytes, filename: filename), MultipartFile.fromBytes(documentBytes, filename: filename),
), ),
) );
..fields.add(MapEntry('title', title)); formData.fields.add(MapEntry('title', title));
if (createdAt != null) { if (createdAt != null) {
formData.fields.add(MapEntry('created', apiDateFormat.format(createdAt))); formData.fields.add(
MapEntry('created', apiDateFormat.format(createdAt)),
);
} }
if (correspondent != null) { if (correspondent != null) {
formData.fields.add(MapEntry('correspondent', jsonEncode(correspondent))); formData.fields.add(MapEntry('correspondent', jsonEncode(correspondent)));
@@ -42,8 +49,10 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
formData.fields.add(MapEntry('tags', tag.toString())); formData.fields.add(MapEntry('tags', tag.toString()));
} }
try { try {
final response = final response = await client.post(
await client.post('/api/documents/post_document/', data: formData); '/api/documents/post_document/',
data: formData,
);
if (response.statusCode == 200) { if (response.statusCode == 200) {
if (response.data is String && response.data != "OK") { if (response.data is String && response.data != "OK") {
return response.data; return response.data;