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

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

View File

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

View File

@@ -1,7 +1,13 @@
// ignore_for_file: non_constant_identifier_names
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,

View File

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

View File

@@ -1,14 +1,16 @@
import 'package:json_annotation/json_annotation.dart';
import 'package: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);

View File

@@ -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(),
};

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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);

View File

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

View File

@@ -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,
};

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,

View File

@@ -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,
};

View File

@@ -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,
};

View File

@@ -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,

View File

@@ -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});

View File

@@ -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);

View File

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

View File

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

View File

@@ -1,6 +1,14 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
import 'package:paperless_api/src/models/document_model.dart';
import 'package:paperless_api/src/models/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);
}

View File

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

View File

@@ -1,10 +1,12 @@
import 'package:equatable/equatable.dart';
import 'package: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;

View File

@@ -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);

View File

@@ -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(
MapEntry(
'document',
MultipartFile.fromBytes(documentBytes, filename: filename),
),
)
..fields.add(MapEntry('title', title));
final formData = FormData();
formData.files.add(
MapEntry(
'document',
MultipartFile.fromBytes(documentBytes, filename: filename),
),
);
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;