mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 05:15:51 -06:00
Removed suggestions from inbox, added translations, added paging to inbox, visual updates, changed default matching algorithm to auto
This commit is contained in:
@@ -36,7 +36,6 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/pdf" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to
|
||||
@@ -51,4 +50,11 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="file" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -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
|
||||
Future<void> close() {
|
||||
_sub?.cancel();
|
||||
|
||||
@@ -8,8 +8,8 @@ class SavedViewRepositoryImpl extends SavedViewRepository {
|
||||
SavedViewRepositoryImpl(this._api) : super(const SavedViewRepositoryState());
|
||||
|
||||
@override
|
||||
Future<SavedView> create(SavedView view) async {
|
||||
final created = await _api.save(view);
|
||||
Future<SavedView> create(SavedView object) async {
|
||||
final created = await _api.save(object);
|
||||
final updatedState = {...state.values}
|
||||
..putIfAbsent(created.id!, () => created);
|
||||
emit(SavedViewRepositoryState(values: updatedState, hasLoaded: true));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.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';
|
||||
|
||||
@@ -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) {
|
||||
emit(state.copyWith(document: document));
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.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/service/file_service.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/offline_widget.dart';
|
||||
@@ -64,6 +65,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
floatingActionButton: widget.allowEdit
|
||||
? BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||
builder: (context, state) {
|
||||
final _filteredSuggestions =
|
||||
state.suggestions.documentDifference(state.document);
|
||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
builder: (context, connectivityState) {
|
||||
if (!connectivityState.isConnected) {
|
||||
@@ -71,13 +74,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
}
|
||||
return b.Badge(
|
||||
position: b.BadgePosition.topEnd(top: -12, end: -6),
|
||||
showBadge: state.suggestions.hasSuggestions,
|
||||
showBadge: _filteredSuggestions.hasSuggestions,
|
||||
child: FloatingActionButton(
|
||||
child: const Icon(Icons.edit),
|
||||
onPressed: () => _onEdit(state.document),
|
||||
),
|
||||
badgeContent: Text(
|
||||
'${state.suggestions.suggestionsCount}',
|
||||
'${_filteredSuggestions.suggestionsCount}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
@@ -106,16 +109,27 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
? () => _onDelete(state.document)
|
||||
: null,
|
||||
).paddedSymmetrically(horizontal: 4),
|
||||
DocumentDownloadButton(
|
||||
Tooltip(
|
||||
message: "Download",
|
||||
child: DocumentDownloadButton(
|
||||
document: state.document,
|
||||
enabled: isConnected,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.open_in_new),
|
||||
icon: const Icon(Icons.visibility),
|
||||
onPressed: isConnected
|
||||
? () => _onOpen(state.document)
|
||||
: null,
|
||||
).paddedOnly(right: 4.0),
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.open_in_new),
|
||||
// onPressed: isConnected
|
||||
// ? context
|
||||
// .read<DocumentDetailsCubit>()
|
||||
// .openDocumentInSystemViewer
|
||||
// : null,
|
||||
// ).paddedOnly(right: 4.0),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.share),
|
||||
onPressed: isConnected
|
||||
|
||||
@@ -153,12 +153,11 @@ class _DocumentUploadPreparationPageState
|
||||
S
|
||||
.of(context)
|
||||
.documentUploadPageSynchronizeTitleAndFilenameLabel,
|
||||
), //TODO: INTL
|
||||
),
|
||||
),
|
||||
FormBuilderDateTimePicker(
|
||||
enabled: false,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
format: DateFormat("dd. MMMM yyyy"), //TODO: INTL
|
||||
format: DateFormat.yMMMMd(),
|
||||
inputType: InputType.date,
|
||||
name: DocumentModel.createdKey,
|
||||
initialValue: null,
|
||||
@@ -168,11 +167,6 @@ class _DocumentUploadPreparationPageState
|
||||
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>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
|
||||
@@ -5,19 +5,21 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_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;
|
||||
|
||||
DocumentsCubit(this._api, this._savedViewRepository)
|
||||
: super(const DocumentsState()) {
|
||||
hydrate();
|
||||
}
|
||||
DocumentsCubit(this.api, this._savedViewRepository)
|
||||
: super(const DocumentsState());
|
||||
|
||||
Future<void> bulkRemove(List<DocumentModel> documents) async {
|
||||
log("[DocumentsCubit] bulkRemove");
|
||||
await _api.bulkAction(
|
||||
await api.bulkAction(
|
||||
BulkDeleteAction(documents.map((doc) => doc.id)),
|
||||
);
|
||||
await reload();
|
||||
@@ -29,7 +31,7 @@ class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
|
||||
Iterable<int> removeTags = const [],
|
||||
}) async {
|
||||
log("[DocumentsCubit] bulkEditTags");
|
||||
await _api.bulkAction(BulkModifyTagsAction(
|
||||
await api.bulkAction(BulkModifyTagsAction(
|
||||
documents.map((doc) => doc.id),
|
||||
addTags: addTags,
|
||||
removeTags: removeTags,
|
||||
@@ -37,132 +39,6 @@ class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
|
||||
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) {
|
||||
log("[DocumentsCubit] toggleSelection");
|
||||
if (state.selectedIds.contains(model.id)) {
|
||||
@@ -185,12 +61,6 @@ class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
|
||||
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() {
|
||||
log("[DocumentsCubit] reset");
|
||||
emit(const DocumentsState());
|
||||
@@ -204,7 +74,7 @@ class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
|
||||
if (filter == null) {
|
||||
return;
|
||||
}
|
||||
final results = await _api.findAll(filter.copyWith(page: 1));
|
||||
final results = await api.findAll(filter.copyWith(page: 1));
|
||||
emit(
|
||||
DocumentsState(
|
||||
filter: filter,
|
||||
@@ -220,7 +90,7 @@ class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
|
||||
}
|
||||
|
||||
Future<Iterable<String>> autocomplete(String query) async {
|
||||
final res = await _api.autocomplete(query);
|
||||
final res = await api.autocomplete(query);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,72 +1,25 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.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 {
|
||||
final bool isLoading;
|
||||
final bool hasLoaded;
|
||||
final DocumentFilter filter;
|
||||
final List<PagedSearchResult<DocumentModel>> value;
|
||||
class DocumentsState extends DocumentsPagedState {
|
||||
final int? selectedSavedViewId;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
final List<DocumentModel> selection;
|
||||
|
||||
const DocumentsState({
|
||||
this.hasLoaded = false,
|
||||
this.isLoading = false,
|
||||
this.value = const [],
|
||||
this.filter = const DocumentFilter(),
|
||||
this.selection = const [],
|
||||
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();
|
||||
|
||||
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({
|
||||
bool overwrite = false,
|
||||
bool? hasLoaded,
|
||||
bool? isLoading,
|
||||
List<PagedSearchResult<DocumentModel>>? value,
|
||||
@@ -118,4 +71,19 @@ class DocumentsState extends Equatable {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
@@ -45,6 +43,15 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
|
||||
bool _isSubmitLoading = false;
|
||||
|
||||
late final FieldSuggestions _filteredSuggestions;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_filteredSuggestions = widget.suggestions
|
||||
.documentDifference(context.read<EditDocumentCubit>().state.document);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<EditDocumentCubit, EditDocumentState>(
|
||||
@@ -99,25 +106,35 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
excludeAllowed: false,
|
||||
name: fkTags,
|
||||
selectableOptions: state.tags,
|
||||
suggestions: widget.suggestions.hasSuggestedTags
|
||||
suggestions: _filteredSuggestions.tags
|
||||
.toSet()
|
||||
.difference(state.document.tags.toSet())
|
||||
.isNotEmpty
|
||||
? _buildSuggestionsSkeleton<int>(
|
||||
suggestions: widget.suggestions.storagePaths,
|
||||
itemBuilder: (context, itemData) => ActionChip(
|
||||
label: Text(state.tags[itemData]!.name),
|
||||
suggestions: _filteredSuggestions.tags,
|
||||
itemBuilder: (context, itemData) {
|
||||
final tag = state.tags[itemData]!;
|
||||
return ActionChip(
|
||||
label: Text(
|
||||
tag.name,
|
||||
style: TextStyle(color: tag.textColor),
|
||||
),
|
||||
backgroundColor: tag.color,
|
||||
onPressed: () {
|
||||
final currentTags = _formKey.currentState
|
||||
?.fields[fkTags] as TagsQuery;
|
||||
?.fields[fkTags]?.value as TagsQuery;
|
||||
if (currentTags is IdsTagsQuery) {
|
||||
_formKey.currentState?.fields[fkTags]
|
||||
?.didChange((IdsTagsQuery.fromIds(
|
||||
[...currentTags.ids, itemData])));
|
||||
{...currentTags.ids, itemData})));
|
||||
} else {
|
||||
_formKey.currentState?.fields[fkTags]
|
||||
?.didChange(
|
||||
(IdsTagsQuery.fromIds([itemData])));
|
||||
?.didChange((IdsTagsQuery.fromIds(
|
||||
{itemData})));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
).padded(),
|
||||
@@ -151,15 +168,6 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
name: fkStoragePath,
|
||||
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,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
),
|
||||
if (widget.suggestions.hasSuggestedCorrespondents)
|
||||
if (_filteredSuggestions.hasSuggestedCorrespondents)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions: widget.suggestions.correspondents,
|
||||
suggestions: _filteredSuggestions.correspondents,
|
||||
itemBuilder: (context, itemData) => ActionChip(
|
||||
label: Text(options[itemData]!.name),
|
||||
onPressed: () => _formKey.currentState?.fields[fkCorrespondent]
|
||||
@@ -217,9 +225,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
name: fkDocumentType,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
),
|
||||
if (widget.suggestions.hasSuggestedDocumentTypes)
|
||||
if (_filteredSuggestions.hasSuggestedDocumentTypes)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions: widget.suggestions.documentTypes,
|
||||
suggestions: _filteredSuggestions.documentTypes,
|
||||
itemBuilder: (context, itemData) => ActionChip(
|
||||
label: Text(options[itemData]!.name),
|
||||
onPressed: () => _formKey.currentState?.fields[fkDocumentType]
|
||||
@@ -236,13 +244,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
var mergedDocument = document.copyWith(
|
||||
title: values[fkTitle],
|
||||
created: values[fkCreatedDate],
|
||||
overwriteDocumentType: true,
|
||||
documentType: (values[fkDocumentType] as IdQueryParameter).id,
|
||||
overwriteCorrespondent: true,
|
||||
correspondent: (values[fkCorrespondent] as IdQueryParameter).id,
|
||||
overwriteStoragePath: true,
|
||||
storagePath: (values[fkStoragePath] as IdQueryParameter).id,
|
||||
overwriteTags: true,
|
||||
documentType: () => (values[fkDocumentType] as IdQueryParameter).id,
|
||||
correspondent: () => (values[fkCorrespondent] as IdQueryParameter).id,
|
||||
storagePath: () => (values[fkStoragePath] as IdQueryParameter).id,
|
||||
tags: (values[fkTags] as IdsTagsQuery).includedIds,
|
||||
);
|
||||
setState(() {
|
||||
@@ -288,9 +292,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
format: DateFormat("dd. MMMM yyyy"), //TODO: Localized date format
|
||||
initialEntryMode: DatePickerEntryMode.calendar,
|
||||
),
|
||||
if (widget.suggestions.hasSuggestedDates)
|
||||
if (_filteredSuggestions.hasSuggestedDates)
|
||||
_buildSuggestionsSkeleton<DateTime>(
|
||||
suggestions: widget.suggestions.dates,
|
||||
suggestions: _filteredSuggestions.dates,
|
||||
itemBuilder: (context, itemData) => ActionChip(
|
||||
label: Text(DateFormat.yMMMd().format(itemData)),
|
||||
onPressed: () => _formKey.currentState?.fields[fkCreatedDate]
|
||||
|
||||
@@ -63,13 +63,13 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
}
|
||||
_scrollController
|
||||
..addListener(_listenForScrollChanges)
|
||||
..addListener(_listenForLoadDataTrigger);
|
||||
..addListener(_listenForLoadNewData);
|
||||
}
|
||||
|
||||
void _listenForLoadDataTrigger() {
|
||||
void _listenForLoadNewData() {
|
||||
final currState = context.read<DocumentsCubit>().state;
|
||||
if (_scrollController.offset >=
|
||||
_scrollController.position.maxScrollExtent &&
|
||||
_scrollController.position.maxScrollExtent * 0.75 &&
|
||||
!currState.isLoading &&
|
||||
!currState.isLastPageLoaded) {
|
||||
_loadNewPage();
|
||||
@@ -173,7 +173,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(
|
||||
linearProgressIndicatorHeight),
|
||||
child: state.isLoading
|
||||
child: state.isLoading && state.hasLoaded
|
||||
? const LinearProgressIndicator()
|
||||
: const SizedBox(height: 4.0),
|
||||
),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.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);
|
||||
} on PaperlessServerException catch (error) {
|
||||
showErrorMessage(context, error);
|
||||
} catch (error) {
|
||||
print(error);
|
||||
} catch (error, stackTrace) {
|
||||
log("An error occurred!", error: error, stackTrace: stackTrace);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.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 = {}),
|
||||
),
|
||||
FormBuilderDropdown<int?>(
|
||||
//TODO: Extract to own widget.
|
||||
name: Label.matchingAlgorithmKey,
|
||||
initialValue: widget.initialValue?.matchingAlgorithm?.value ??
|
||||
MatchingAlgorithm.allWords.value,
|
||||
initialValue: widget.initialValue?.matchingAlgorithm.value ??
|
||||
MatchingAlgorithm.auto.value,
|
||||
decoration: InputDecoration(
|
||||
labelText: S.of(context).labelMatchingAlgorithmPropertyLabel,
|
||||
errorText: _errors[Label.matchingAlgorithmKey],
|
||||
@@ -93,7 +92,7 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
||||
items: MatchingAlgorithm.values
|
||||
.map(
|
||||
(algo) => DropdownMenuItem<int?>(
|
||||
child: Text(algo.name), //TODO: INTL
|
||||
child: Text(translateMatchingAlgorithm(context, algo)),
|
||||
value: algo.value,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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/paperless_server_information_cubit.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/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
|
||||
@@ -50,6 +50,7 @@ class _HomePageState extends State<HomePage> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeData(context);
|
||||
context.read<ConnectivityCubit>().reload();
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_listenForReceivedFiles();
|
||||
});
|
||||
|
||||
@@ -361,7 +361,7 @@ class _InfoDrawerState extends State<InfoDrawer> {
|
||||
applicationName: 'Paperless Mobile',
|
||||
applicationVersion: snapshot.version + '+' + snapshot.buildNumber,
|
||||
children: [
|
||||
Text('${S.of(context).aboutDialogDevelopedByText} Anton Stubenbord'),
|
||||
Text(S.of(context).aboutDialogDevelopedByText('Anton Stubenbord')),
|
||||
Link(
|
||||
uri: Uri.parse('https://github.com/astubenbord/paperless-mobile'),
|
||||
builder: (context, followLink) => GestureDetector(
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.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/tag_repository_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<Correspondent, CorrespondentRepositoryState>
|
||||
_correspondentRepository;
|
||||
@@ -21,6 +20,9 @@ class InboxCubit extends HydratedCubit<InboxState> {
|
||||
|
||||
final List<StreamSubscription> _subscriptions = [];
|
||||
|
||||
@override
|
||||
PaperlessDocumentsApi get api => _documentsApi;
|
||||
|
||||
InboxCubit(
|
||||
this._tagsRepository,
|
||||
this._documentsApi,
|
||||
@@ -67,105 +69,83 @@ class InboxCubit extends HydratedCubit<InboxState> {
|
||||
final inboxTags = await _tagsRepository.findAll().then(
|
||||
(tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!),
|
||||
);
|
||||
|
||||
if (inboxTags.isEmpty) {
|
||||
// no inbox tags = no inbox items.
|
||||
return emit(
|
||||
state.copyWith(
|
||||
isLoaded: true,
|
||||
inboxItems: [],
|
||||
hasLoaded: true,
|
||||
value: [],
|
||||
inboxTags: [],
|
||||
),
|
||||
);
|
||||
}
|
||||
final inboxDocuments = await _documentsApi
|
||||
.findAll(DocumentFilter(
|
||||
tags: AnyAssignedTagsQuery(tagIds: inboxTags),
|
||||
return updateFilter(
|
||||
filter: DocumentFilter(
|
||||
sortField: SortField.added,
|
||||
))
|
||||
.then((psr) => psr.results);
|
||||
final newState = state.copyWith(
|
||||
isLoaded: true,
|
||||
inboxItems: inboxDocuments,
|
||||
inboxTags: inboxTags,
|
||||
tags: IdsTagsQuery.fromIds(inboxTags),
|
||||
),
|
||||
);
|
||||
emit(newState);
|
||||
}
|
||||
|
||||
///
|
||||
/// 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 =
|
||||
document.tags.toSet().intersection(state.inboxTags.toSet());
|
||||
|
||||
final updatedTags = {...document.tags}..removeAll(tagsToRemove);
|
||||
|
||||
await _documentsApi.update(
|
||||
document.copyWith(
|
||||
tags: updatedTags,
|
||||
overwriteTags: true,
|
||||
),
|
||||
await api.update(
|
||||
document.copyWith(tags: updatedTags),
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
isLoaded: true,
|
||||
inboxItems: state.inboxItems.where((doc) => doc.id != document.id),
|
||||
),
|
||||
);
|
||||
|
||||
await remove(document);
|
||||
return tagsToRemove;
|
||||
}
|
||||
|
||||
///
|
||||
/// Adds the previously removed tags to the document and performs an update.
|
||||
///
|
||||
Future<void> undoRemove(
|
||||
Future<void> undoRemoveFromInbox(
|
||||
DocumentModel document,
|
||||
Iterable<int> removedTags,
|
||||
) async {
|
||||
final updatedDoc = document.copyWith(
|
||||
tags: {...document.tags, ...removedTags},
|
||||
overwriteTags: true,
|
||||
);
|
||||
await _documentsApi.update(updatedDoc);
|
||||
emit(state.copyWith(
|
||||
isLoaded: true,
|
||||
inboxItems: [...state.inboxItems, updatedDoc]
|
||||
..sort((d1, d2) => d2.added.compareTo(d1.added)),
|
||||
));
|
||||
return reload();
|
||||
}
|
||||
|
||||
///
|
||||
/// Removes inbox tags from all documents in the inbox.
|
||||
///
|
||||
Future<void> clearInbox() async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
try {
|
||||
await _documentsApi.bulkAction(
|
||||
BulkModifyTagsAction.removeTags(
|
||||
state.inboxItems.map((e) => e.id),
|
||||
state.documents.map((e) => e.id),
|
||||
state.inboxTags,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(
|
||||
isLoaded: true,
|
||||
inboxItems: [],
|
||||
hasLoaded: true,
|
||||
value: [],
|
||||
));
|
||||
} finally {
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
}
|
||||
|
||||
void replaceUpdatedDocument(DocumentModel document) {
|
||||
if (document.tags.any((id) => state.inboxTags.contains(id))) {
|
||||
// If replaced document still has inbox tag assigned:
|
||||
emit(state.copyWith(
|
||||
inboxItems:
|
||||
state.inboxItems.map((e) => e.id == document.id ? document : e),
|
||||
));
|
||||
replace(document);
|
||||
} else {
|
||||
// Remove tag from inbox.
|
||||
emit(
|
||||
state.copyWith(
|
||||
inboxItems:
|
||||
state.inboxItems.where((element) => element.id != document.id)),
|
||||
);
|
||||
// Remove document from inbox.
|
||||
remove(document);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,48 +154,10 @@ class InboxCubit extends HydratedCubit<InboxState> {
|
||||
final int asn = await _documentsApi.findNextAsn();
|
||||
final updatedDocument = await _documentsApi
|
||||
.update(document.copyWith(archiveSerialNumber: asn));
|
||||
emit(
|
||||
state.copyWith(
|
||||
inboxItems: state.inboxItems
|
||||
.map((e) => e.id == document.id ? updatedDocument : e)),
|
||||
);
|
||||
replace(updatedDocument);
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
emit(state.copyWith(isHintAcknowledged: true));
|
||||
}
|
||||
|
||||
@@ -1,57 +1,56 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.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';
|
||||
|
||||
@JsonSerializable(
|
||||
ignoreUnannotated: true,
|
||||
)
|
||||
class InboxState with EquatableMixin {
|
||||
final bool isLoaded;
|
||||
|
||||
class InboxState extends DocumentsPagedState {
|
||||
final Iterable<int> inboxTags;
|
||||
|
||||
final Iterable<DocumentModel> inboxItems;
|
||||
|
||||
final Map<int, Tag> availableTags;
|
||||
|
||||
final Map<int, DocumentType> availableDocumentTypes;
|
||||
|
||||
final Map<int, Correspondent> availableCorrespondents;
|
||||
|
||||
final Map<int, FieldSuggestions> suggestions;
|
||||
@JsonKey()
|
||||
final bool isHintAcknowledged;
|
||||
|
||||
const InboxState({
|
||||
this.isLoaded = false,
|
||||
super.hasLoaded = false,
|
||||
super.isLoading = false,
|
||||
super.value = const [],
|
||||
super.filter = const DocumentFilter(),
|
||||
this.inboxTags = const [],
|
||||
this.inboxItems = const [],
|
||||
this.isHintAcknowledged = false,
|
||||
this.availableTags = const {},
|
||||
this.availableDocumentTypes = const {},
|
||||
this.availableCorrespondents = const {},
|
||||
this.suggestions = const {},
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
isLoaded,
|
||||
hasLoaded,
|
||||
isLoading,
|
||||
value,
|
||||
filter,
|
||||
inboxTags,
|
||||
inboxItems,
|
||||
documents,
|
||||
isHintAcknowledged,
|
||||
availableTags,
|
||||
availableDocumentTypes,
|
||||
availableCorrespondents,
|
||||
suggestions,
|
||||
];
|
||||
|
||||
InboxState copyWith({
|
||||
bool? isLoaded,
|
||||
bool? hasLoaded,
|
||||
bool? isLoading,
|
||||
Iterable<int>? inboxTags,
|
||||
Iterable<DocumentModel>? inboxItems,
|
||||
List<PagedSearchResult<DocumentModel>>? value,
|
||||
DocumentFilter? filter,
|
||||
bool? isHintAcknowledged,
|
||||
Map<int, Tag>? availableTags,
|
||||
Map<int, Correspondent>? availableCorrespondents,
|
||||
@@ -59,8 +58,9 @@ class InboxState with EquatableMixin {
|
||||
Map<int, FieldSuggestions>? suggestions,
|
||||
}) {
|
||||
return InboxState(
|
||||
isLoaded: isLoaded ?? this.isLoaded,
|
||||
inboxItems: inboxItems ?? this.inboxItems,
|
||||
hasLoaded: hasLoaded ?? super.hasLoaded,
|
||||
isLoading: isLoading ?? super.isLoading,
|
||||
value: value ?? super.value,
|
||||
inboxTags: inboxTags ?? this.inboxTags,
|
||||
isHintAcknowledged: isHintAcknowledged ?? this.isHintAcknowledged,
|
||||
availableCorrespondents:
|
||||
@@ -68,7 +68,7 @@ class InboxState with EquatableMixin {
|
||||
availableDocumentTypes:
|
||||
availableDocumentTypes ?? this.availableDocumentTypes,
|
||||
availableTags: availableTags ?? this.availableTags,
|
||||
suggestions: suggestions ?? this.suggestions,
|
||||
filter: filter ?? super.filter,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,4 +76,20 @@ class InboxState with EquatableMixin {
|
||||
_$InboxStateFromJson(json);
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,71 +23,104 @@ class InboxPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _InboxPageState extends State<InboxPage> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
|
||||
|
||||
@override
|
||||
void 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
|
||||
Widget build(BuildContext context) {
|
||||
const _progressBarHeight = 4.0;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
appBar: PreferredSize(
|
||||
preferredSize:
|
||||
const Size.fromHeight(kToolbarHeight + _progressBarHeight),
|
||||
child: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
return AppBar(
|
||||
title: Text(S.of(context).bottomNavInboxPageLabel),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
actions: [
|
||||
BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
return Align(
|
||||
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.inboxItems.length} ${S.of(context).inboxPageUnseenText}',
|
||||
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),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
if (!state.isLoaded || state.inboxItems.isEmpty) {
|
||||
if (!state.hasLoaded || state.documents.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return FloatingActionButton.extended(
|
||||
label: Text(S.of(context).inboxPageMarkAllAsSeenLabel),
|
||||
icon: const Icon(Icons.done_all),
|
||||
onPressed: state.isLoaded && state.inboxItems.isNotEmpty
|
||||
onPressed: state.hasLoaded && state.documents.isNotEmpty
|
||||
? () => _onMarkAllAsSeen(
|
||||
state.inboxItems,
|
||||
state.documents,
|
||||
state.inboxTags,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
body: BlocConsumer<InboxCubit, InboxState>(
|
||||
listenWhen: (previous, current) =>
|
||||
!previous.isLoaded && current.isLoaded,
|
||||
listener: (context, state) =>
|
||||
context.read<InboxCubit>().loadSuggestions(),
|
||||
body: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
if (!state.isLoaded) {
|
||||
if (!state.hasLoaded) {
|
||||
return const DocumentsListLoadingWidget();
|
||||
}
|
||||
|
||||
if (state.inboxItems.isEmpty) {
|
||||
if (state.documents.isEmpty) {
|
||||
return InboxEmptyWidget(
|
||||
emptyStateRefreshIndicatorKey: _emptyStateRefreshIndicatorKey,
|
||||
);
|
||||
@@ -95,7 +128,7 @@ class _InboxPageState extends State<InboxPage> {
|
||||
|
||||
// Build a list of slivers alternating between SliverToBoxAdapter
|
||||
// (group header) and a SliverList (inbox items).
|
||||
final List<Widget> slivers = _groupByDate(state.inboxItems)
|
||||
final List<Widget> slivers = _groupByDate(state.documents)
|
||||
.entries
|
||||
.map(
|
||||
(entry) => [
|
||||
@@ -148,6 +181,7 @@ class _InboxPageState extends State<InboxPage> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: HintCard(
|
||||
@@ -157,7 +191,7 @@ class _InboxPageState extends State<InboxPage> {
|
||||
context.read<InboxCubit>().acknowledgeHint(),
|
||||
),
|
||||
),
|
||||
...slivers
|
||||
...slivers,
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -234,7 +268,7 @@ class _InboxPageState extends State<InboxPage> {
|
||||
|
||||
Future<bool> _onItemDismissed(DocumentModel doc) async {
|
||||
try {
|
||||
final removedTags = await context.read<InboxCubit>().remove(doc);
|
||||
final removedTags = await context.read<InboxCubit>().removeFromInbox(doc);
|
||||
showSnackBar(
|
||||
context,
|
||||
S.of(context).inboxPageDocumentRemovedMessageText,
|
||||
@@ -261,7 +295,9 @@ class _InboxPageState extends State<InboxPage> {
|
||||
Iterable<int> removedTags,
|
||||
) async {
|
||||
try {
|
||||
await context.read<InboxCubit>().undoRemove(document, removedTags);
|
||||
await context
|
||||
.read<InboxCubit>()
|
||||
.undoRemoveFromInbox(document, removedTags);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/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/extensions/date_time_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/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/document_preview.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/view/widgets/label_text.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class InboxItem extends StatefulWidget {
|
||||
static const _a4AspectRatio = 1 / 1.4142;
|
||||
@@ -37,6 +30,8 @@ class InboxItem extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _InboxItemState extends State<InboxItem> {
|
||||
// late final Future<FieldSuggestions> _fieldSuggestions;
|
||||
|
||||
bool _isAsnAssignLoading = false;
|
||||
|
||||
@override
|
||||
@@ -65,7 +60,7 @@ class _InboxItemState extends State<InboxItem> {
|
||||
}
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 180,
|
||||
height: 200,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -128,13 +123,32 @@ class _InboxItemState extends State<InboxItem> {
|
||||
) ??
|
||||
false;
|
||||
if (shouldDelete) {
|
||||
context.read<InboxCubit>().deleteDocument(widget.document);
|
||||
context.read<InboxCubit>().delete(widget.document);
|
||||
}
|
||||
},
|
||||
),
|
||||
];
|
||||
return BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
// return FutureBuilder<FieldSuggestions>(
|
||||
// future: _fieldSuggestions,
|
||||
// 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(
|
||||
@@ -162,20 +176,13 @@ class _InboxItemState extends State<InboxItem> {
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: [
|
||||
...actions,
|
||||
if (state.suggestions[widget.document.id] != null) ...[
|
||||
const SizedBox(width: 4),
|
||||
..._buildSuggestionChips(
|
||||
chipShape,
|
||||
state.suggestions[widget.document.id]!,
|
||||
state,
|
||||
)
|
||||
]
|
||||
// if (suggestions != null) ...suggestions,
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
// );
|
||||
// },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -274,97 +281,103 @@ class _InboxItemState extends State<InboxItem> {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildSuggestionChips(
|
||||
OutlinedBorder chipShape,
|
||||
FieldSuggestions suggestions,
|
||||
InboxState state,
|
||||
) {
|
||||
return [
|
||||
...suggestions.correspondents
|
||||
.whereNot((e) => widget.document.correspondent == e)
|
||||
.map(
|
||||
(e) => ActionChip(
|
||||
avatar: const Icon(Icons.person_outline),
|
||||
shape: chipShape,
|
||||
label: Text(state.availableCorrespondents[e]?.name ?? ''),
|
||||
onPressed: () {
|
||||
context
|
||||
.read<InboxCubit>()
|
||||
.updateDocument(widget.document.copyWith(
|
||||
correspondent: e,
|
||||
overwriteCorrespondent: true,
|
||||
))
|
||||
.then((value) => showSnackBar(
|
||||
context,
|
||||
S
|
||||
.of(context)
|
||||
.inboxPageSuggestionSuccessfullyAppliedMessage));
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
...suggestions.documentTypes
|
||||
.whereNot((e) => widget.document.documentType == e)
|
||||
.map(
|
||||
(e) => ActionChip(
|
||||
avatar: const Icon(Icons.description_outlined),
|
||||
shape: chipShape,
|
||||
label: Text(state.availableDocumentTypes[e]?.name ?? ''),
|
||||
onPressed: () => context
|
||||
.read<InboxCubit>()
|
||||
.updateDocument(widget.document
|
||||
.copyWith(documentType: e, overwriteDocumentType: true))
|
||||
.then((value) => showSnackBar(
|
||||
context,
|
||||
S
|
||||
.of(context)
|
||||
.inboxPageSuggestionSuccessfullyAppliedMessage)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
...suggestions.tags
|
||||
.whereNot((e) => widget.document.tags.contains(e))
|
||||
.map(
|
||||
(e) => ActionChip(
|
||||
avatar: const Icon(Icons.label_outline),
|
||||
shape: chipShape,
|
||||
label: Text(state.availableTags[e]?.name ?? ''),
|
||||
onPressed: () {
|
||||
context
|
||||
.read<InboxCubit>()
|
||||
.updateDocument(widget.document.copyWith(
|
||||
tags: {...widget.document.tags, e}.toList(),
|
||||
overwriteTags: true,
|
||||
))
|
||||
.then((value) => showSnackBar(
|
||||
context,
|
||||
S
|
||||
.of(context)
|
||||
.inboxPageSuggestionSuccessfullyAppliedMessage));
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
...suggestions.dates
|
||||
.whereNot((e) => widget.document.created.isEqualToIgnoringDate(e))
|
||||
.map(
|
||||
(e) => ActionChip(
|
||||
avatar: const Icon(Icons.calendar_today_outlined),
|
||||
shape: chipShape,
|
||||
label: Text(
|
||||
"${S.of(context).documentCreatedPropertyLabel}: ${DateFormat.yMd().format(e)}",
|
||||
),
|
||||
onPressed: () => context
|
||||
.read<InboxCubit>()
|
||||
.updateDocument(widget.document.copyWith(created: e))
|
||||
.then((value) => showSnackBar(
|
||||
context,
|
||||
S
|
||||
.of(context)
|
||||
.inboxPageSuggestionSuccessfullyAppliedMessage)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
].expand((element) => [element, const SizedBox(width: 4)]).toList();
|
||||
}
|
||||
// List<Widget> _buildSuggestionChips(
|
||||
// OutlinedBorder chipShape,
|
||||
// FieldSuggestions suggestions,
|
||||
// InboxState state,
|
||||
// ) {
|
||||
// return [
|
||||
// ...suggestions.correspondents
|
||||
// .whereNot((e) => widget.document.correspondent == e)
|
||||
// .map(
|
||||
// (e) => ActionChip(
|
||||
// avatar: const Icon(Icons.person_outline),
|
||||
// shape: chipShape,
|
||||
// label: Text(state.availableCorrespondents[e]?.name ?? ''),
|
||||
// onPressed: () {
|
||||
// context
|
||||
// .read<InboxCubit>()
|
||||
// .update(
|
||||
// widget.document.copyWith(correspondent: () => e),
|
||||
// )
|
||||
// .then((value) => showSnackBar(
|
||||
// context,
|
||||
// S
|
||||
// .of(context)
|
||||
// .inboxPageSuggestionSuccessfullyAppliedMessage));
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
// .toList(),
|
||||
// ...suggestions.documentTypes
|
||||
// .whereNot((e) => widget.document.documentType == e)
|
||||
// .map(
|
||||
// (e) => ActionChip(
|
||||
// avatar: const Icon(Icons.description_outlined),
|
||||
// shape: chipShape,
|
||||
// label: Text(state.availableDocumentTypes[e]?.name ?? ''),
|
||||
// onPressed: () => context
|
||||
// .read<InboxCubit>()
|
||||
// .update(
|
||||
// widget.document.copyWith(documentType: () => e),
|
||||
// shouldReload: false,
|
||||
// )
|
||||
// .then((value) => showSnackBar(
|
||||
// context,
|
||||
// S
|
||||
// .of(context)
|
||||
// .inboxPageSuggestionSuccessfullyAppliedMessage)),
|
||||
// ),
|
||||
// )
|
||||
// .toList(),
|
||||
// ...suggestions.tags
|
||||
// .whereNot((e) => widget.document.tags.contains(e))
|
||||
// .map(
|
||||
// (e) => ActionChip(
|
||||
// avatar: const Icon(Icons.label_outline),
|
||||
// shape: chipShape,
|
||||
// label: Text(state.availableTags[e]?.name ?? ''),
|
||||
// onPressed: () {
|
||||
// context
|
||||
// .read<InboxCubit>()
|
||||
// .update(
|
||||
// widget.document.copyWith(
|
||||
// tags: {...widget.document.tags, e}.toList(),
|
||||
// ),
|
||||
// shouldReload: false,
|
||||
// )
|
||||
// .then((value) => showSnackBar(
|
||||
// context,
|
||||
// S
|
||||
// .of(context)
|
||||
// .inboxPageSuggestionSuccessfullyAppliedMessage));
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
// .toList(),
|
||||
// ...suggestions.dates
|
||||
// .whereNot((e) => widget.document.created.isEqualToIgnoringDate(e))
|
||||
// .map(
|
||||
// (e) => ActionChip(
|
||||
// avatar: const Icon(Icons.calendar_today_outlined),
|
||||
// shape: chipShape,
|
||||
// label: Text(
|
||||
// "${S.of(context).documentCreatedPropertyLabel}: ${DateFormat.yMd().format(e)}",
|
||||
// ),
|
||||
// onPressed: () => context
|
||||
// .read<InboxCubit>()
|
||||
// .update(
|
||||
// widget.document.copyWith(created: e),
|
||||
// shouldReload: false,
|
||||
// )
|
||||
// .then((value) => showSnackBar(
|
||||
// context,
|
||||
// S
|
||||
// .of(context)
|
||||
// .inboxPageSuggestionSuccessfullyAppliedMessage)),
|
||||
// ),
|
||||
// )
|
||||
// .toList(),
|
||||
// ].expand((element) => [element, const SizedBox(width: 4)]).toList();
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class TagsWidget extends StatelessWidget {
|
||||
required this.isSelectedPredicate,
|
||||
this.onTagSelected,
|
||||
this.showShortNames = false,
|
||||
this.dense = false,
|
||||
this.dense = true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
||||
@@ -30,6 +30,7 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
||||
leading: leading,
|
||||
onTap: () => onOpenEditPage(label),
|
||||
trailing: _buildReferencedDocumentsWidget(context),
|
||||
isThreeLine: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
|
||||
@@ -70,8 +71,12 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
||||
.map(
|
||||
(l) => LabelItem<T>(
|
||||
name: l.name,
|
||||
content:
|
||||
contentBuilder?.call(l) ?? Text(l.match ?? '-'),
|
||||
content: contentBuilder?.call(l) ??
|
||||
Text(
|
||||
"${translateMatchingAlgorithm(context, l.matchingAlgorithm)}\n"
|
||||
"${l.match}",
|
||||
maxLines: 2,
|
||||
),
|
||||
onOpenEditPage: onEdit,
|
||||
filterBuilder: filterBuilder,
|
||||
leading: leadingBuilder?.call(l),
|
||||
|
||||
163
lib/features/paged_document_view/documents_paging_mixin.dart
Normal file
163
lib/features/paged_document_view/documents_paging_mixin.dart
Normal 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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -18,6 +18,7 @@ class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
|
||||
'en': 'English',
|
||||
'de': 'Deutsch',
|
||||
'cs': 'Česky',
|
||||
'tr': 'Türkçe',
|
||||
};
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -42,6 +43,10 @@ class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
|
||||
RadioOption(
|
||||
value: 'cs',
|
||||
label: _languageOptions['cs']!,
|
||||
),
|
||||
RadioOption(
|
||||
value: 'tr',
|
||||
label: _languageOptions['tr']!,
|
||||
)
|
||||
],
|
||||
initialValue: context
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"@@locale": "cs",
|
||||
"aboutDialogDevelopedByText": "Vyvíjí",
|
||||
"@aboutDialogDevelopedByText": {},
|
||||
"@aboutDialogDevelopedByText": {
|
||||
"placeholders": {
|
||||
"name": {}
|
||||
}
|
||||
},
|
||||
"addCorrespondentPageTitle": "Nový korespondent",
|
||||
"@addCorrespondentPageTitle": {},
|
||||
"addDocumentTypePageTitle": "Nový typ dokumentu",
|
||||
@@ -128,10 +132,6 @@
|
||||
"@documentsFilterPageAdvancedLabel": {},
|
||||
"documentsFilterPageApplyFilterLabel": "Použít",
|
||||
"@documentsFilterPageApplyFilterLabel": {},
|
||||
"documentsFilterPageDateRangeFieldEndLabel": "Do",
|
||||
"@documentsFilterPageDateRangeFieldEndLabel": {},
|
||||
"documentsFilterPageDateRangeFieldStartLabel": "Od",
|
||||
"@documentsFilterPageDateRangeFieldStartLabel": {},
|
||||
"documentsFilterPageDateRangeLastMonthLabel": "Minulý měsíc",
|
||||
"@documentsFilterPageDateRangeLastMonthLabel": {},
|
||||
"documentsFilterPageDateRangeLastSevenDaysLabel": "Posledních 7 dní",
|
||||
@@ -294,8 +294,6 @@
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"extendedDateRangePickerFromLabel": "Od",
|
||||
"@extendedDateRangePickerFromLabel": {},
|
||||
"extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{} few{} many{} other{Posledních 7 dní}}",
|
||||
"@extendedDateRangePickerLastDaysLabel": {
|
||||
"placeholders": {
|
||||
@@ -328,8 +326,6 @@
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"extendedDateRangePickerToLabel": "Do",
|
||||
"@extendedDateRangePickerToLabel": {},
|
||||
"extendedDateRangePickerWeekText": "{count, plural, other{}}",
|
||||
"@extendedDateRangePickerWeekText": {
|
||||
"placeholders": {
|
||||
@@ -492,6 +488,18 @@
|
||||
"@loginPageUsernameLabel": {},
|
||||
"loginPageUsernameValidatorMessageText": "Jméno uživatele nesmí být prázdné.",
|
||||
"@loginPageUsernameValidatorMessageText": {},
|
||||
"matchingAlgorithmAllDescription": "",
|
||||
"@matchingAlgorithmAllDescription": {},
|
||||
"matchingAlgorithmAnyDescription": "",
|
||||
"@matchingAlgorithmAnyDescription": {},
|
||||
"matchingAlgorithmAutoDescription": "",
|
||||
"@matchingAlgorithmAutoDescription": {},
|
||||
"matchingAlgorithmExactDescription": "",
|
||||
"@matchingAlgorithmExactDescription": {},
|
||||
"matchingAlgorithmFuzzyDescription": "",
|
||||
"@matchingAlgorithmFuzzyDescription": {},
|
||||
"matchingAlgorithmRegexDescription": "",
|
||||
"@matchingAlgorithmRegexDescription": {},
|
||||
"offlineWidgetText": "Nezdařilo se vytvořit připojení k internetu.",
|
||||
"@offlineWidgetText": {},
|
||||
"onboardingDoneButtonLabel": "Hotovo",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"@@locale": "de",
|
||||
"aboutDialogDevelopedByText": "Entwickelt von",
|
||||
"@aboutDialogDevelopedByText": {},
|
||||
"@aboutDialogDevelopedByText": {
|
||||
"placeholders": {
|
||||
"name": {}
|
||||
}
|
||||
},
|
||||
"addCorrespondentPageTitle": "Neuer Korrespondent",
|
||||
"@addCorrespondentPageTitle": {},
|
||||
"addDocumentTypePageTitle": "Neuer Dokumenttyp",
|
||||
@@ -128,10 +132,6 @@
|
||||
"@documentsFilterPageAdvancedLabel": {},
|
||||
"documentsFilterPageApplyFilterLabel": "Anwenden",
|
||||
"@documentsFilterPageApplyFilterLabel": {},
|
||||
"documentsFilterPageDateRangeFieldEndLabel": "Bis",
|
||||
"@documentsFilterPageDateRangeFieldEndLabel": {},
|
||||
"documentsFilterPageDateRangeFieldStartLabel": "Von",
|
||||
"@documentsFilterPageDateRangeFieldStartLabel": {},
|
||||
"documentsFilterPageDateRangeLastMonthLabel": "Letzter Monat",
|
||||
"@documentsFilterPageDateRangeLastMonthLabel": {},
|
||||
"documentsFilterPageDateRangeLastSevenDaysLabel": "Letzte 7 Tage",
|
||||
@@ -294,8 +294,6 @@
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"extendedDateRangePickerFromLabel": "Von",
|
||||
"@extendedDateRangePickerFromLabel": {},
|
||||
"extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{Gestern} other{Letzte {count} Tage}}",
|
||||
"@extendedDateRangePickerLastDaysLabel": {
|
||||
"placeholders": {
|
||||
@@ -328,8 +326,6 @@
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"extendedDateRangePickerToLabel": "Bis",
|
||||
"@extendedDateRangePickerToLabel": {},
|
||||
"extendedDateRangePickerWeekText": "{count, plural, zero{} one{Woche} other{Wochen}}",
|
||||
"@extendedDateRangePickerWeekText": {
|
||||
"placeholders": {
|
||||
@@ -364,7 +360,7 @@
|
||||
"@genericActionUploadLabel": {},
|
||||
"genericMessageOfflineText": "Du bist offline.",
|
||||
"@genericMessageOfflineText": {},
|
||||
"inboxPageAssignAsnLabel": "",
|
||||
"inboxPageAssignAsnLabel": "ASN zuweisen",
|
||||
"@inboxPageAssignAsnLabel": {},
|
||||
"inboxPageDocumentRemovedMessageText": "Dokument aus Posteingang entfernt.",
|
||||
"@inboxPageDocumentRemovedMessageText": {},
|
||||
@@ -492,6 +488,18 @@
|
||||
"@loginPageUsernameLabel": {},
|
||||
"loginPageUsernameValidatorMessageText": "Nutzername darf nicht leer sein.",
|
||||
"@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": {},
|
||||
"onboardingDoneButtonLabel": "Fertig",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"@@locale": "en",
|
||||
"aboutDialogDevelopedByText": "Developed by",
|
||||
"@aboutDialogDevelopedByText": {},
|
||||
"aboutDialogDevelopedByText": "Developed by {name}",
|
||||
"@aboutDialogDevelopedByText": {
|
||||
"placeholders": {
|
||||
"name": {}
|
||||
}
|
||||
},
|
||||
"addCorrespondentPageTitle": "New Correspondent",
|
||||
"@addCorrespondentPageTitle": {},
|
||||
"addDocumentTypePageTitle": "New Document Type",
|
||||
@@ -46,7 +50,7 @@
|
||||
"@deleteViewDialogContentText": {},
|
||||
"deleteViewDialogTitleText": "Delete view ",
|
||||
"@deleteViewDialogTitleText": {},
|
||||
"documentAddedPropertyLabel": "Added At",
|
||||
"documentAddedPropertyLabel": "Added at",
|
||||
"@documentAddedPropertyLabel": {},
|
||||
"documentArchiveSerialNumberPropertyLongLabel": "Archive Serial Number",
|
||||
"@documentArchiveSerialNumberPropertyLongLabel": {},
|
||||
@@ -54,7 +58,7 @@
|
||||
"@documentArchiveSerialNumberPropertyShortLabel": {},
|
||||
"documentCorrespondentPropertyLabel": "Correspondent",
|
||||
"@documentCorrespondentPropertyLabel": {},
|
||||
"documentCreatedPropertyLabel": "Created At",
|
||||
"documentCreatedPropertyLabel": "Created at",
|
||||
"@documentCreatedPropertyLabel": {},
|
||||
"documentDeleteSuccessMessage": "Document successfully deleted.",
|
||||
"@documentDeleteSuccessMessage": {},
|
||||
@@ -104,7 +108,7 @@
|
||||
"@documentMetaDataOriginalFileSizeLabel": {},
|
||||
"documentMetaDataOriginalMimeTypeLabel": "Original MIME-Type",
|
||||
"@documentMetaDataOriginalMimeTypeLabel": {},
|
||||
"documentModifiedPropertyLabel": "Modified At",
|
||||
"documentModifiedPropertyLabel": "Modified at",
|
||||
"@documentModifiedPropertyLabel": {},
|
||||
"documentPreviewPageTitle": "Preview",
|
||||
"@documentPreviewPageTitle": {},
|
||||
@@ -128,10 +132,6 @@
|
||||
"@documentsFilterPageAdvancedLabel": {},
|
||||
"documentsFilterPageApplyFilterLabel": "Apply",
|
||||
"@documentsFilterPageApplyFilterLabel": {},
|
||||
"documentsFilterPageDateRangeFieldEndLabel": "To",
|
||||
"@documentsFilterPageDateRangeFieldEndLabel": {},
|
||||
"documentsFilterPageDateRangeFieldStartLabel": "From",
|
||||
"@documentsFilterPageDateRangeFieldStartLabel": {},
|
||||
"documentsFilterPageDateRangeLastMonthLabel": "Last Month",
|
||||
"@documentsFilterPageDateRangeLastMonthLabel": {},
|
||||
"documentsFilterPageDateRangeLastSevenDaysLabel": "Last 7 Days",
|
||||
@@ -294,8 +294,6 @@
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"extendedDateRangePickerFromLabel": "From",
|
||||
"@extendedDateRangePickerFromLabel": {},
|
||||
"extendedDateRangePickerLastDaysLabel": "{count, plural, zero{} one{Yesterday} other{Last {count} days}}",
|
||||
"@extendedDateRangePickerLastDaysLabel": {
|
||||
"placeholders": {
|
||||
@@ -328,8 +326,6 @@
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"extendedDateRangePickerToLabel": "To",
|
||||
"@extendedDateRangePickerToLabel": {},
|
||||
"extendedDateRangePickerWeekText": "{count, plural, zero{} one{week} other{weeks}}",
|
||||
"@extendedDateRangePickerWeekText": {
|
||||
"placeholders": {
|
||||
@@ -492,6 +488,18 @@
|
||||
"@loginPageUsernameLabel": {},
|
||||
"loginPageUsernameValidatorMessageText": "Username must not be empty.",
|
||||
"@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": {},
|
||||
"onboardingDoneButtonLabel": "Done",
|
||||
|
||||
581
lib/l10n/intl_tr.arb
Normal file
581
lib/l10n/intl_tr.arb
Normal 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": {}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:package_info_plus/package_info_plus.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/generated/l10n.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
|
||||
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 {
|
||||
static const idKey = 'id';
|
||||
static const titleKey = 'title';
|
||||
@@ -47,50 +53,18 @@ class DocumentModel extends Equatable {
|
||||
this.storagePath,
|
||||
});
|
||||
|
||||
DocumentModel.fromJson(Map<String, dynamic> json)
|
||||
: id = json[idKey],
|
||||
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];
|
||||
factory DocumentModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$DocumentModelFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
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,
|
||||
};
|
||||
}
|
||||
Map<String, dynamic> toJson() => _$DocumentModelToJson(this);
|
||||
|
||||
DocumentModel copyWith({
|
||||
String? title,
|
||||
String? content,
|
||||
bool overwriteTags = false,
|
||||
Iterable<int>? tags,
|
||||
bool overwriteDocumentType = false,
|
||||
int? documentType,
|
||||
bool overwriteCorrespondent = false,
|
||||
int? correspondent,
|
||||
bool overwriteStoragePath = false,
|
||||
int? storagePath,
|
||||
int? Function()? documentType,
|
||||
int? Function()? correspondent,
|
||||
int? Function()? storagePath,
|
||||
DateTime? created,
|
||||
DateTime? modified,
|
||||
DateTime? added,
|
||||
@@ -102,11 +76,10 @@ class DocumentModel extends Equatable {
|
||||
id: id,
|
||||
title: title ?? this.title,
|
||||
content: content ?? this.content,
|
||||
documentType: overwriteDocumentType ? documentType : this.documentType,
|
||||
correspondent:
|
||||
overwriteCorrespondent ? correspondent : this.correspondent,
|
||||
storagePath: overwriteDocumentType ? storagePath : this.storagePath,
|
||||
tags: overwriteTags ? tags ?? [] : this.tags,
|
||||
documentType: documentType?.call() ?? this.documentType,
|
||||
correspondent: correspondent?.call() ?? this.correspondent,
|
||||
storagePath: storagePath?.call() ?? this.storagePath,
|
||||
tags: tags ?? this.tags,
|
||||
created: created ?? this.created,
|
||||
modified: modified ?? this.modified,
|
||||
added: added ?? this.added,
|
||||
|
||||
45
packages/paperless_api/lib/src/models/document_model.g.dart
Normal file
45
packages/paperless_api/lib/src/models/document_model.g.dart
Normal 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,
|
||||
};
|
||||
@@ -1,14 +1,16 @@
|
||||
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';
|
||||
|
||||
@LocalDateTimeJsonConverter()
|
||||
@JsonSerializable(fieldRename: FieldRename.snake)
|
||||
class FieldSuggestions {
|
||||
final int? documentId;
|
||||
final Iterable<int> correspondents;
|
||||
final Iterable<int> tags;
|
||||
final Iterable<int> documentTypes;
|
||||
final Iterable<int> storagePaths;
|
||||
final Iterable<DateTime> dates;
|
||||
|
||||
const FieldSuggestions({
|
||||
@@ -16,28 +18,24 @@ class FieldSuggestions {
|
||||
this.correspondents = const [],
|
||||
this.tags = const [],
|
||||
this.documentTypes = const [],
|
||||
this.storagePaths = const [],
|
||||
this.dates = const [],
|
||||
});
|
||||
|
||||
bool get hasSuggestedCorrespondents => correspondents.isNotEmpty;
|
||||
bool get hasSuggestedTags => tags.isNotEmpty;
|
||||
bool get hasSuggestedDocumentTypes => documentTypes.isNotEmpty;
|
||||
bool get hasSuggestedStoragePaths => storagePaths.isNotEmpty;
|
||||
bool get hasSuggestedDates => dates.isNotEmpty;
|
||||
|
||||
bool get hasSuggestions =>
|
||||
hasSuggestedCorrespondents ||
|
||||
hasSuggestedDates ||
|
||||
hasSuggestedTags ||
|
||||
hasSuggestedStoragePaths ||
|
||||
hasSuggestedDocumentTypes;
|
||||
|
||||
int get suggestionsCount =>
|
||||
(correspondents.isNotEmpty ? 1 : 0) +
|
||||
(tags.isNotEmpty ? 1 : 0) +
|
||||
(documentTypes.isNotEmpty ? 1 : 0) +
|
||||
(storagePaths.isNotEmpty ? 1 : 0) +
|
||||
(dates.isNotEmpty ? 1 : 0);
|
||||
|
||||
FieldSuggestions forDocumentId(int id) => FieldSuggestions(
|
||||
@@ -46,9 +44,53 @@ class FieldSuggestions {
|
||||
dates: dates,
|
||||
documentTypes: documentTypes,
|
||||
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) =>
|
||||
_$FieldSuggestionsFromJson(json);
|
||||
|
||||
|
||||
@@ -16,11 +16,8 @@ FieldSuggestions _$FieldSuggestionsFromJson(Map<String, dynamic> json) =>
|
||||
documentTypes:
|
||||
(json['document_types'] as List<dynamic>?)?.map((e) => e as int) ??
|
||||
const [],
|
||||
storagePaths:
|
||||
(json['storage_paths'] as List<dynamic>?)?.map((e) => e as int) ??
|
||||
const [],
|
||||
dates: (json['dates'] as List<dynamic>?)
|
||||
?.map((e) => DateTime.parse(e as String)) ??
|
||||
dates: (json['dates'] as List<dynamic>?)?.map((e) =>
|
||||
const LocalDateTimeJsonConverter().fromJson(e as String)) ??
|
||||
const [],
|
||||
);
|
||||
|
||||
@@ -30,6 +27,7 @@ Map<String, dynamic> _$FieldSuggestionsToJson(FieldSuggestions instance) =>
|
||||
'correspondents': instance.correspondents.toList(),
|
||||
'tags': instance.tags.toList(),
|
||||
'document_types': instance.documentTypes.toList(),
|
||||
'storage_paths': instance.storagePaths.toList(),
|
||||
'dates': instance.dates.map((e) => e.toIso8601String()).toList(),
|
||||
'dates': instance.dates
|
||||
.map(const LocalDateTimeJsonConverter().toJson)
|
||||
.toList(),
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.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/exclude_tag_id_query.dart';
|
||||
@@ -13,6 +14,7 @@ part 'filter_rule_model.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class FilterRule with EquatableMixin {
|
||||
static const _dateTimeConverter = LocalDateTimeJsonConverter();
|
||||
static const int titleRule = 0;
|
||||
static const int asnRule = 2;
|
||||
static const int correspondentRule = 3;
|
||||
@@ -95,66 +97,72 @@ class FilterRule with EquatableMixin {
|
||||
if (filter.created is AbsoluteDateRangeQuery) {
|
||||
return filter.copyWith(
|
||||
created: (filter.created as AbsoluteDateRangeQuery)
|
||||
.copyWith(before: DateTime.parse(value!)),
|
||||
.copyWith(before: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
} else {
|
||||
return filter.copyWith(
|
||||
created: AbsoluteDateRangeQuery(before: DateTime.parse(value!)),
|
||||
created: AbsoluteDateRangeQuery(
|
||||
before: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
}
|
||||
case createdAfterRule:
|
||||
if (filter.created is AbsoluteDateRangeQuery) {
|
||||
return filter.copyWith(
|
||||
created: (filter.created as AbsoluteDateRangeQuery)
|
||||
.copyWith(after: DateTime.parse(value!)),
|
||||
.copyWith(after: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
} else {
|
||||
return filter.copyWith(
|
||||
created: AbsoluteDateRangeQuery(after: DateTime.parse(value!)),
|
||||
created: AbsoluteDateRangeQuery(
|
||||
after: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
}
|
||||
case addedBeforeRule:
|
||||
if (filter.added is AbsoluteDateRangeQuery) {
|
||||
return filter.copyWith(
|
||||
added: (filter.added as AbsoluteDateRangeQuery)
|
||||
.copyWith(before: DateTime.parse(value!)),
|
||||
.copyWith(before: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
} else {
|
||||
return filter.copyWith(
|
||||
added: AbsoluteDateRangeQuery(before: DateTime.parse(value!)),
|
||||
added: AbsoluteDateRangeQuery(
|
||||
before: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
}
|
||||
case addedAfterRule:
|
||||
if (filter.added is AbsoluteDateRangeQuery) {
|
||||
return filter.copyWith(
|
||||
added: (filter.added as AbsoluteDateRangeQuery)
|
||||
.copyWith(after: DateTime.parse(value!)),
|
||||
.copyWith(after: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
} else {
|
||||
return filter.copyWith(
|
||||
added: AbsoluteDateRangeQuery(after: DateTime.parse(value!)),
|
||||
added: AbsoluteDateRangeQuery(
|
||||
after: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
}
|
||||
case modifiedBeforeRule:
|
||||
if (filter.modified is AbsoluteDateRangeQuery) {
|
||||
return filter.copyWith(
|
||||
modified: (filter.modified as AbsoluteDateRangeQuery)
|
||||
.copyWith(before: DateTime.parse(value!)),
|
||||
.copyWith(before: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
} else {
|
||||
return filter.copyWith(
|
||||
modified: AbsoluteDateRangeQuery(before: DateTime.parse(value!)),
|
||||
modified: AbsoluteDateRangeQuery(
|
||||
before: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
}
|
||||
case modifiedAfterRule:
|
||||
if (filter.modified is AbsoluteDateRangeQuery) {
|
||||
return filter.copyWith(
|
||||
modified: (filter.modified as AbsoluteDateRangeQuery)
|
||||
.copyWith(after: DateTime.parse(value!)),
|
||||
.copyWith(after: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
} else {
|
||||
return filter.copyWith(
|
||||
added: AbsoluteDateRangeQuery(after: DateTime.parse(value!)),
|
||||
added: AbsoluteDateRangeQuery(
|
||||
after: _dateTimeConverter.fromJson(value!)),
|
||||
);
|
||||
}
|
||||
case titleAndContentRule:
|
||||
@@ -347,15 +355,16 @@ class FilterRule with EquatableMixin {
|
||||
}
|
||||
|
||||
//Join values of all extended filter rules if exist
|
||||
if (filterRules.isNotEmpty) {
|
||||
final FilterRule extendedFilterRule = filterRules
|
||||
.where((r) => r.ruleType == extendedRule)
|
||||
.reduce((previousValue, element) => previousValue.copyWith(
|
||||
value: previousValue.value! + element.value!,
|
||||
));
|
||||
if (filterRules.isNotEmpty &&
|
||||
filterRules.where((e) => e.ruleType == FilterRule.extendedRule).length >
|
||||
1) {
|
||||
final mergedExtendedRule = filterRules
|
||||
.where((r) => r.ruleType == FilterRule.extendedRule)
|
||||
.map((e) => e.value)
|
||||
.join(",");
|
||||
filterRules
|
||||
..removeWhere((element) => element.ruleType == extendedRule)
|
||||
..add(extendedFilterRule);
|
||||
..add(FilterRule(FilterRule.extendedRule, mergedExtendedRule));
|
||||
}
|
||||
return filterRules;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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/matching_algorithm.dart';
|
||||
|
||||
part 'correspondent_model.g.dart';
|
||||
|
||||
@LocalDateTimeJsonConverter()
|
||||
@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
|
||||
class Correspondent extends Label {
|
||||
final DateTime? lastCorrespondence;
|
||||
@@ -13,7 +15,7 @@ class Correspondent extends Label {
|
||||
required super.name,
|
||||
super.slug,
|
||||
super.match,
|
||||
super.matchingAlgorithm,
|
||||
required super.matchingAlgorithm,
|
||||
super.isInsensitive,
|
||||
super.documentCount,
|
||||
this.lastCorrespondence,
|
||||
|
||||
@@ -12,13 +12,13 @@ Correspondent _$CorrespondentFromJson(Map<String, dynamic> json) =>
|
||||
name: json['name'] as String,
|
||||
slug: json['slug'] as String?,
|
||||
match: json['match'] as String?,
|
||||
matchingAlgorithm: $enumDecodeNullable(
|
||||
_$MatchingAlgorithmEnumMap, json['matching_algorithm']),
|
||||
matchingAlgorithm:
|
||||
$enumDecode(_$MatchingAlgorithmEnumMap, json['matching_algorithm']),
|
||||
isInsensitive: json['is_insensitive'] as bool?,
|
||||
documentCount: json['document_count'] as int?,
|
||||
lastCorrespondence: json['last_correspondence'] == null
|
||||
? null
|
||||
: DateTime.parse(json['last_correspondence'] as String),
|
||||
lastCorrespondence: _$JsonConverterFromJson<String, DateTime>(
|
||||
json['last_correspondence'],
|
||||
const LocalDateTimeJsonConverter().fromJson),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$CorrespondentToJson(Correspondent instance) {
|
||||
@@ -34,12 +34,14 @@ Map<String, dynamic> _$CorrespondentToJson(Correspondent instance) {
|
||||
val['name'] = instance.name;
|
||||
writeNotNull('slug', instance.slug);
|
||||
writeNotNull('match', instance.match);
|
||||
writeNotNull('matching_algorithm',
|
||||
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]);
|
||||
val['matching_algorithm'] =
|
||||
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]!;
|
||||
writeNotNull('is_insensitive', instance.isInsensitive);
|
||||
writeNotNull('document_count', instance.documentCount);
|
||||
writeNotNull(
|
||||
'last_correspondence', instance.lastCorrespondence?.toIso8601String());
|
||||
'last_correspondence',
|
||||
_$JsonConverterToJson<String, DateTime>(instance.lastCorrespondence,
|
||||
const LocalDateTimeJsonConverter().toJson));
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -48,6 +50,18 @@ const _$MatchingAlgorithmEnumMap = {
|
||||
MatchingAlgorithm.allWords: 2,
|
||||
MatchingAlgorithm.exactMatch: 3,
|
||||
MatchingAlgorithm.regex: 4,
|
||||
MatchingAlgorithm.similarWord: 5,
|
||||
MatchingAlgorithm.fuzzy: 5,
|
||||
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);
|
||||
|
||||
@@ -10,7 +10,7 @@ class DocumentType extends Label {
|
||||
required super.name,
|
||||
super.slug,
|
||||
super.match,
|
||||
super.matchingAlgorithm,
|
||||
required super.matchingAlgorithm,
|
||||
super.isInsensitive,
|
||||
super.documentCount,
|
||||
});
|
||||
|
||||
@@ -11,8 +11,8 @@ DocumentType _$DocumentTypeFromJson(Map<String, dynamic> json) => DocumentType(
|
||||
name: json['name'] as String,
|
||||
slug: json['slug'] as String?,
|
||||
match: json['match'] as String?,
|
||||
matchingAlgorithm: $enumDecodeNullable(
|
||||
_$MatchingAlgorithmEnumMap, json['matching_algorithm']),
|
||||
matchingAlgorithm:
|
||||
$enumDecode(_$MatchingAlgorithmEnumMap, json['matching_algorithm']),
|
||||
isInsensitive: json['is_insensitive'] as bool?,
|
||||
documentCount: json['document_count'] as int?,
|
||||
);
|
||||
@@ -30,8 +30,8 @@ Map<String, dynamic> _$DocumentTypeToJson(DocumentType instance) {
|
||||
val['name'] = instance.name;
|
||||
writeNotNull('slug', instance.slug);
|
||||
writeNotNull('match', instance.match);
|
||||
writeNotNull('matching_algorithm',
|
||||
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]);
|
||||
val['matching_algorithm'] =
|
||||
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]!;
|
||||
writeNotNull('is_insensitive', instance.isInsensitive);
|
||||
writeNotNull('document_count', instance.documentCount);
|
||||
return val;
|
||||
@@ -42,6 +42,6 @@ const _$MatchingAlgorithmEnumMap = {
|
||||
MatchingAlgorithm.allWords: 2,
|
||||
MatchingAlgorithm.exactMatch: 3,
|
||||
MatchingAlgorithm.regex: 4,
|
||||
MatchingAlgorithm.similarWord: 5,
|
||||
MatchingAlgorithm.fuzzy: 5,
|
||||
MatchingAlgorithm.auto: 6,
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ abstract class Label extends Equatable implements Comparable {
|
||||
@JsonKey()
|
||||
final String? match;
|
||||
@JsonKey()
|
||||
final MatchingAlgorithm? matchingAlgorithm;
|
||||
final MatchingAlgorithm matchingAlgorithm;
|
||||
@JsonKey()
|
||||
final bool? isInsensitive;
|
||||
@JsonKey()
|
||||
@@ -30,8 +30,8 @@ abstract class Label extends Equatable implements Comparable {
|
||||
const Label({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.matchingAlgorithm,
|
||||
this.match,
|
||||
this.matchingAlgorithm,
|
||||
this.isInsensitive,
|
||||
this.documentCount,
|
||||
this.slug,
|
||||
|
||||
@@ -6,7 +6,7 @@ enum MatchingAlgorithm {
|
||||
allWords(2, "All: Match all of the following words"),
|
||||
exactMatch(3, "Exact: Match the following string"),
|
||||
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");
|
||||
|
||||
final int value;
|
||||
|
||||
@@ -13,7 +13,7 @@ class StoragePath extends Label {
|
||||
required super.name,
|
||||
super.slug,
|
||||
super.match,
|
||||
super.matchingAlgorithm,
|
||||
required super.matchingAlgorithm,
|
||||
super.isInsensitive,
|
||||
super.documentCount,
|
||||
required this.path,
|
||||
|
||||
@@ -11,8 +11,8 @@ StoragePath _$StoragePathFromJson(Map<String, dynamic> json) => StoragePath(
|
||||
name: json['name'] as String,
|
||||
slug: json['slug'] as String?,
|
||||
match: json['match'] as String?,
|
||||
matchingAlgorithm: $enumDecodeNullable(
|
||||
_$MatchingAlgorithmEnumMap, json['matching_algorithm']),
|
||||
matchingAlgorithm:
|
||||
$enumDecode(_$MatchingAlgorithmEnumMap, json['matching_algorithm']),
|
||||
isInsensitive: json['is_insensitive'] as bool?,
|
||||
documentCount: json['document_count'] as int?,
|
||||
path: json['path'] as String?,
|
||||
@@ -31,8 +31,8 @@ Map<String, dynamic> _$StoragePathToJson(StoragePath instance) {
|
||||
val['name'] = instance.name;
|
||||
writeNotNull('slug', instance.slug);
|
||||
writeNotNull('match', instance.match);
|
||||
writeNotNull('matching_algorithm',
|
||||
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]);
|
||||
val['matching_algorithm'] =
|
||||
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]!;
|
||||
writeNotNull('is_insensitive', instance.isInsensitive);
|
||||
writeNotNull('document_count', instance.documentCount);
|
||||
writeNotNull('path', instance.path);
|
||||
@@ -44,6 +44,6 @@ const _$MatchingAlgorithmEnumMap = {
|
||||
MatchingAlgorithm.allWords: 2,
|
||||
MatchingAlgorithm.exactMatch: 3,
|
||||
MatchingAlgorithm.regex: 4,
|
||||
MatchingAlgorithm.similarWord: 5,
|
||||
MatchingAlgorithm.fuzzy: 5,
|
||||
MatchingAlgorithm.auto: 6,
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ class Tag extends Label {
|
||||
super.documentCount,
|
||||
super.isInsensitive,
|
||||
super.match,
|
||||
super.matchingAlgorithm,
|
||||
required super.matchingAlgorithm,
|
||||
super.slug,
|
||||
Color? color,
|
||||
this.textColor,
|
||||
@@ -84,13 +84,14 @@ class Tag extends Label {
|
||||
match,
|
||||
];
|
||||
|
||||
//FIXME: Why is this not generated?!
|
||||
factory Tag.fromJson(Map<String, dynamic> json) {
|
||||
const $MatchingAlgorithmEnumMap = {
|
||||
MatchingAlgorithm.anyWord: 1,
|
||||
MatchingAlgorithm.allWords: 2,
|
||||
MatchingAlgorithm.exactMatch: 3,
|
||||
MatchingAlgorithm.regex: 4,
|
||||
MatchingAlgorithm.similarWord: 5,
|
||||
MatchingAlgorithm.fuzzy: 5,
|
||||
MatchingAlgorithm.auto: 6,
|
||||
};
|
||||
|
||||
@@ -100,8 +101,8 @@ class Tag extends Label {
|
||||
documentCount: json['document_count'] as int?,
|
||||
isInsensitive: json['is_insensitive'] as bool?,
|
||||
match: json['match'] as String?,
|
||||
matchingAlgorithm: $enumDecodeNullable(
|
||||
$MatchingAlgorithmEnumMap, json['matching_algorithm']),
|
||||
matchingAlgorithm:
|
||||
$enumDecode($MatchingAlgorithmEnumMap, json['matching_algorithm']),
|
||||
slug: json['slug'] as String?,
|
||||
textColor: _colorFromJson(json['text_color']),
|
||||
isInboxTag: json['is_inbox_tag'] as bool?,
|
||||
@@ -118,7 +119,7 @@ class Tag extends Label {
|
||||
MatchingAlgorithm.allWords: 2,
|
||||
MatchingAlgorithm.exactMatch: 3,
|
||||
MatchingAlgorithm.regex: 4,
|
||||
MatchingAlgorithm.similarWord: 5,
|
||||
MatchingAlgorithm.fuzzy: 5,
|
||||
MatchingAlgorithm.auto: 6,
|
||||
};
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ class PagedSearchResult<T> extends Equatable {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int get pageSize => results.length;
|
||||
|
||||
const PagedSearchResult({
|
||||
required this.count,
|
||||
required this.next,
|
||||
@@ -80,11 +82,11 @@ class PagedSearchResult<T> extends Equatable {
|
||||
return PagedSearchResult.fromJsonT(serializer.json, serializer.converter);
|
||||
}
|
||||
|
||||
PagedSearchResult copyWith({
|
||||
PagedSearchResult<T> copyWith({
|
||||
int? count,
|
||||
String? next,
|
||||
String? previous,
|
||||
List<DocumentModel>? results,
|
||||
List<T>? results,
|
||||
}) {
|
||||
return PagedSearchResult(
|
||||
count: count ?? this.count,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:json_annotation/json_annotation.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_field.dart';
|
||||
@@ -8,7 +9,10 @@ part 'absolute_date_range_query.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class AbsoluteDateRangeQuery extends DateRangeQuery {
|
||||
@LocalDateTimeJsonConverter()
|
||||
final DateTime? after;
|
||||
|
||||
@LocalDateTimeJsonConverter()
|
||||
final DateTime? before;
|
||||
|
||||
const AbsoluteDateRangeQuery({this.after, this.before});
|
||||
|
||||
@@ -9,17 +9,29 @@ part of 'absolute_date_range_query.dart';
|
||||
AbsoluteDateRangeQuery _$AbsoluteDateRangeQueryFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
AbsoluteDateRangeQuery(
|
||||
after: json['after'] == null
|
||||
? null
|
||||
: DateTime.parse(json['after'] as String),
|
||||
before: json['before'] == null
|
||||
? null
|
||||
: DateTime.parse(json['before'] as String),
|
||||
after: _$JsonConverterFromJson<String, DateTime>(
|
||||
json['after'], const LocalDateTimeJsonConverter().fromJson),
|
||||
before: _$JsonConverterFromJson<String, DateTime>(
|
||||
json['before'], const LocalDateTimeJsonConverter().fromJson),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AbsoluteDateRangeQueryToJson(
|
||||
AbsoluteDateRangeQuery instance) =>
|
||||
<String, dynamic>{
|
||||
'after': instance.after?.toIso8601String(),
|
||||
'before': instance.before?.toIso8601String(),
|
||||
'after': _$JsonConverterToJson<String, DateTime>(
|
||||
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);
|
||||
|
||||
21
packages/paperless_api/lib/src/models/search_hit.dart
Normal file
21
packages/paperless_api/lib/src/models/search_hit.dart
Normal 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);
|
||||
}
|
||||
19
packages/paperless_api/lib/src/models/search_hit.g.dart
Normal file
19
packages/paperless_api/lib/src/models/search_hit.g.dart
Normal 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,
|
||||
};
|
||||
@@ -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/search_hit.dart';
|
||||
|
||||
part 'similar_document_model.g.dart';
|
||||
|
||||
@LocalDateTimeJsonConverter()
|
||||
@JsonSerializable()
|
||||
class SimilarDocumentModel extends DocumentModel {
|
||||
@JsonKey(name: '__search_hit__')
|
||||
final SearchHit searchHit;
|
||||
|
||||
const SimilarDocumentModel({
|
||||
@@ -20,39 +28,9 @@ class SimilarDocumentModel extends DocumentModel {
|
||||
super.tags,
|
||||
});
|
||||
|
||||
factory SimilarDocumentModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$SimilarDocumentModelFromJson(json);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
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'];
|
||||
Map<String, dynamic> toJson() => _$SimilarDocumentModelToJson(this);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:equatable/equatable.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 'task_status.dart';
|
||||
|
||||
part 'task.g.dart';
|
||||
|
||||
@LocalDateTimeJsonConverter()
|
||||
@JsonSerializable(fieldRename: FieldRename.snake)
|
||||
class Task extends Equatable {
|
||||
final int id;
|
||||
|
||||
@@ -10,10 +10,10 @@ Task _$TaskFromJson(Map<String, dynamic> json) => Task(
|
||||
id: json['id'] as int,
|
||||
taskId: json['task_id'] as String?,
|
||||
taskFileName: json['task_file_name'] as String?,
|
||||
dateCreated: DateTime.parse(json['date_created'] as String),
|
||||
dateDone: json['date_done'] == null
|
||||
? null
|
||||
: DateTime.parse(json['date_done'] as String),
|
||||
dateCreated: const LocalDateTimeJsonConverter()
|
||||
.fromJson(json['date_created'] as String),
|
||||
dateDone: _$JsonConverterFromJson<String, DateTime>(
|
||||
json['date_done'], const LocalDateTimeJsonConverter().fromJson),
|
||||
type: json['type'] as String?,
|
||||
status: $enumDecodeNullable(_$TaskStatusEnumMap, json['status']),
|
||||
acknowledged: json['acknowledged'] as bool? ?? false,
|
||||
@@ -25,8 +25,10 @@ Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'task_id': instance.taskId,
|
||||
'task_file_name': instance.taskFileName,
|
||||
'date_created': instance.dateCreated.toIso8601String(),
|
||||
'date_done': instance.dateDone?.toIso8601String(),
|
||||
'date_created':
|
||||
const LocalDateTimeJsonConverter().toJson(instance.dateCreated),
|
||||
'date_done': _$JsonConverterToJson<String, DateTime>(
|
||||
instance.dateDone, const LocalDateTimeJsonConverter().toJson),
|
||||
'type': instance.type,
|
||||
'status': _$TaskStatusEnumMap[instance.status],
|
||||
'result': instance.result,
|
||||
@@ -34,9 +36,21 @@ Map<String, dynamic> _$TaskToJson(Task instance) => <String, dynamic>{
|
||||
'related_document': instance.relatedDocument,
|
||||
};
|
||||
|
||||
Value? _$JsonConverterFromJson<Json, Value>(
|
||||
Object? json,
|
||||
Value? Function(Json json) fromJson,
|
||||
) =>
|
||||
json == null ? null : fromJson(json as Json);
|
||||
|
||||
const _$TaskStatusEnumMap = {
|
||||
TaskStatus.started: 'STARTED',
|
||||
TaskStatus.pending: 'PENDING',
|
||||
TaskStatus.failure: 'FAILURE',
|
||||
TaskStatus.success: 'SUCCESS',
|
||||
};
|
||||
|
||||
Json? _$JsonConverterToJson<Json, Value>(
|
||||
Value? value,
|
||||
Json? Function(Value value) toJson,
|
||||
) =>
|
||||
value == null ? null : toJson(value);
|
||||
|
||||
@@ -2,10 +2,14 @@ import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_api/src/constants.dart';
|
||||
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
|
||||
|
||||
class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||
static const _dateTimeConverter = LocalDateTimeJsonConverter();
|
||||
|
||||
final Dio client;
|
||||
|
||||
PaperlessDocumentsApiImpl(this.client);
|
||||
@@ -21,16 +25,19 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||
int? correspondent,
|
||||
Iterable<int> tags = const [],
|
||||
}) async {
|
||||
final formData = FormData()
|
||||
..files.add(
|
||||
final formData = FormData();
|
||||
formData.files.add(
|
||||
MapEntry(
|
||||
'document',
|
||||
MultipartFile.fromBytes(documentBytes, filename: filename),
|
||||
),
|
||||
)
|
||||
..fields.add(MapEntry('title', title));
|
||||
);
|
||||
formData.fields.add(MapEntry('title', title));
|
||||
|
||||
if (createdAt != null) {
|
||||
formData.fields.add(MapEntry('created', apiDateFormat.format(createdAt)));
|
||||
formData.fields.add(
|
||||
MapEntry('created', apiDateFormat.format(createdAt)),
|
||||
);
|
||||
}
|
||||
if (correspondent != null) {
|
||||
formData.fields.add(MapEntry('correspondent', jsonEncode(correspondent)));
|
||||
@@ -42,8 +49,10 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||
formData.fields.add(MapEntry('tags', tag.toString()));
|
||||
}
|
||||
try {
|
||||
final response =
|
||||
await client.post('/api/documents/post_document/', data: formData);
|
||||
final response = await client.post(
|
||||
'/api/documents/post_document/',
|
||||
data: formData,
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
if (response.data is String && response.data != "OK") {
|
||||
return response.data;
|
||||
|
||||
Reference in New Issue
Block a user