Fixes labels being serialized incorrectly, automates tag serialization

This commit is contained in:
Anton Stubenbord
2023-02-06 17:30:00 +01:00
parent 4aa98c6fa9
commit 348eb30e1a
12 changed files with 175 additions and 145 deletions

View File

@@ -54,8 +54,9 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_enableMatchFormField = _enableMatchFormField = (widget.initialValue?.matchingAlgorithm ??
widget.initialValue?.matchingAlgorithm != MatchingAlgorithm.auto; MatchingAlgorithm.defaultValue) !=
MatchingAlgorithm.auto;
} }
@override @override
@@ -83,8 +84,9 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
), ),
FormBuilderDropdown<int?>( FormBuilderDropdown<int?>(
name: Label.matchingAlgorithmKey, name: Label.matchingAlgorithmKey,
initialValue: widget.initialValue?.matchingAlgorithm.value ?? initialValue: (widget.initialValue?.matchingAlgorithm ??
MatchingAlgorithm.auto.value, MatchingAlgorithm.defaultValue)
.value,
decoration: InputDecoration( decoration: InputDecoration(
labelText: S.of(context).labelMatchingAlgorithmPropertyLabel, labelText: S.of(context).labelMatchingAlgorithmPropertyLabel,
errorText: _errors[Label.matchingAlgorithmKey], errorText: _errors[Label.matchingAlgorithmKey],
@@ -99,7 +101,8 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
.map( .map(
(algo) => DropdownMenuItem<int?>( (algo) => DropdownMenuItem<int?>(
child: Text( child: Text(
translateMatchingAlgorithmDescription(context, algo)), translateMatchingAlgorithmDescription(context, algo),
),
value: algo.value, value: algo.value,
), ),
) )
@@ -139,8 +142,8 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
// If auto is selected, the match will be removed. // If auto is selected, the match will be removed.
mergedJson[Label.matchKey] = ''; mergedJson[Label.matchKey] = '';
} }
final createdLabel = await widget.submitButtonConfig final parsed = widget.fromJsonT(mergedJson);
.onSubmit(widget.fromJsonT(mergedJson)); final createdLabel = await widget.submitButtonConfig.onSubmit(parsed);
Navigator.pop(context, createdLabel); Navigator.pop(context, createdLabel);
} on PaperlessServerException catch (error, stackTrace) { } on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);

View File

@@ -0,0 +1,31 @@
import 'dart:ui';
import 'package:json_annotation/json_annotation.dart';
class HexColorJsonConverter implements JsonConverter<Color?, dynamic> {
const HexColorJsonConverter();
@override
Color? fromJson(dynamic json) {
if (json is Color) {
return json;
}
if (json is String) {
final decoded = int.tryParse(json.replaceAll("#", "ff"), radix: 16);
if (decoded == null) {
return null;
}
return Color(decoded);
}
return null;
}
@override
String? toJson(Color? color) {
if (color == null) {
return null;
}
String val =
'#${(color.value & 0xFFFFFF).toRadixString(16).padLeft(6, '0').toLowerCase()}';
return val;
}
}

View File

@@ -11,11 +11,11 @@ class Correspondent extends Label {
final DateTime? lastCorrespondence; final DateTime? lastCorrespondence;
const Correspondent({ const Correspondent({
required super.id, super.id,
required super.name, required super.name,
super.slug, super.slug,
super.match, super.match,
required super.matchingAlgorithm, super.matchingAlgorithm,
super.isInsensitive, super.isInsensitive,
super.documentCount, super.documentCount,
this.lastCorrespondence, this.lastCorrespondence,
@@ -24,6 +24,7 @@ class Correspondent extends Label {
factory Correspondent.fromJson(Map<String, dynamic> json) => factory Correspondent.fromJson(Map<String, dynamic> json) =>
_$CorrespondentFromJson(json); _$CorrespondentFromJson(json);
@override
Map<String, dynamic> toJson() => _$CorrespondentToJson(this); Map<String, dynamic> toJson() => _$CorrespondentToJson(this);
@override @override

View File

@@ -12,9 +12,10 @@ Correspondent _$CorrespondentFromJson(Map<String, dynamic> json) =>
name: json['name'] as String, name: json['name'] as String,
slug: json['slug'] as String?, slug: json['slug'] as String?,
match: json['match'] as String?, match: json['match'] as String?,
matchingAlgorithm: matchingAlgorithm: $enumDecodeNullable(
$enumDecode(_$MatchingAlgorithmEnumMap, json['matching_algorithm']), _$MatchingAlgorithmEnumMap, json['matching_algorithm']) ??
isInsensitive: json['is_insensitive'] as bool?, MatchingAlgorithm.defaultValue,
isInsensitive: json['is_insensitive'] as bool? ?? true,
documentCount: json['document_count'] as int?, documentCount: json['document_count'] as int?,
lastCorrespondence: _$JsonConverterFromJson<String, DateTime>( lastCorrespondence: _$JsonConverterFromJson<String, DateTime>(
json['last_correspondence'], json['last_correspondence'],

View File

@@ -6,11 +6,11 @@ part 'document_type_model.g.dart';
@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake) @JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
class DocumentType extends Label { class DocumentType extends Label {
const DocumentType({ const DocumentType({
required super.id, super.id,
required super.name, required super.name,
super.slug, super.slug,
super.match, super.match,
required super.matchingAlgorithm, super.matchingAlgorithm,
super.isInsensitive, super.isInsensitive,
super.documentCount, super.documentCount,
}); });

View File

@@ -11,9 +11,10 @@ DocumentType _$DocumentTypeFromJson(Map<String, dynamic> json) => DocumentType(
name: json['name'] as String, name: json['name'] as String,
slug: json['slug'] as String?, slug: json['slug'] as String?,
match: json['match'] as String?, match: json['match'] as String?,
matchingAlgorithm: matchingAlgorithm: $enumDecodeNullable(
$enumDecode(_$MatchingAlgorithmEnumMap, json['matching_algorithm']), _$MatchingAlgorithmEnumMap, json['matching_algorithm']) ??
isInsensitive: json['is_insensitive'] as bool?, MatchingAlgorithm.defaultValue,
isInsensitive: json['is_insensitive'] as bool? ?? true,
documentCount: json['document_count'] as int?, documentCount: json['document_count'] as int?,
); );

View File

@@ -1,5 +1,4 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/src/models/labels/matching_algorithm.dart'; import 'package:paperless_api/src/models/labels/matching_algorithm.dart';
abstract class Label extends Equatable implements Comparable { abstract class Label extends Equatable implements Comparable {
@@ -12,27 +11,21 @@ abstract class Label extends Equatable implements Comparable {
static const documentCountKey = "document_count"; static const documentCountKey = "document_count";
String get queryEndpoint; String get queryEndpoint;
@JsonKey()
final int? id; final int? id;
@JsonKey()
final String name; final String name;
@JsonKey()
final String? slug; final String? slug;
@JsonKey()
final String? match; final String? match;
@JsonKey()
final MatchingAlgorithm matchingAlgorithm; final MatchingAlgorithm matchingAlgorithm;
@JsonKey()
final bool? isInsensitive; final bool? isInsensitive;
@JsonKey()
final int? documentCount; final int? documentCount;
const Label({ const Label({
required this.id, this.id,
required this.name, required this.name,
required this.matchingAlgorithm, this.matchingAlgorithm = MatchingAlgorithm.defaultValue,
this.match, this.match,
this.isInsensitive, this.isInsensitive = true,
this.documentCount, this.documentCount,
this.slug, this.slug,
}); });

View File

@@ -14,12 +14,5 @@ enum MatchingAlgorithm {
const MatchingAlgorithm(this.value, this.name); const MatchingAlgorithm(this.value, this.name);
static MatchingAlgorithm fromInt(int? value) { static const MatchingAlgorithm defaultValue = auto;
return MatchingAlgorithm.values
.where((element) => element.value == value)
.firstWhere(
(element) => true,
orElse: () => MatchingAlgorithm.anyWord,
);
}
} }

View File

@@ -6,17 +6,17 @@ part 'storage_path_model.g.dart';
@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake) @JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
class StoragePath extends Label { class StoragePath extends Label {
static const pathKey = 'path'; static const pathKey = 'path';
late String? path; final String path;
StoragePath({ const StoragePath({
required super.id, super.id,
required super.name, required super.name,
required this.path,
super.slug, super.slug,
super.match, super.match,
required super.matchingAlgorithm, super.matchingAlgorithm,
super.isInsensitive, super.isInsensitive,
super.documentCount, super.documentCount,
required this.path,
}); });
factory StoragePath.fromJson(Map<String, dynamic> json) => factory StoragePath.fromJson(Map<String, dynamic> json) =>

View File

@@ -9,13 +9,14 @@ part of 'storage_path_model.dart';
StoragePath _$StoragePathFromJson(Map<String, dynamic> json) => StoragePath( StoragePath _$StoragePathFromJson(Map<String, dynamic> json) => StoragePath(
id: json['id'] as int?, id: json['id'] as int?,
name: json['name'] as String, name: json['name'] as String,
path: json['path'] as String,
slug: json['slug'] as String?, slug: json['slug'] as String?,
match: json['match'] as String?, match: json['match'] as String?,
matchingAlgorithm: matchingAlgorithm: $enumDecodeNullable(
$enumDecode(_$MatchingAlgorithmEnumMap, json['matching_algorithm']), _$MatchingAlgorithmEnumMap, json['matching_algorithm']) ??
isInsensitive: json['is_insensitive'] as bool?, MatchingAlgorithm.defaultValue,
isInsensitive: json['is_insensitive'] as bool? ?? true,
documentCount: json['document_count'] as int?, documentCount: json['document_count'] as int?,
path: json['path'] as String?,
); );
Map<String, dynamic> _$StoragePathToJson(StoragePath instance) { Map<String, dynamic> _$StoragePathToJson(StoragePath instance) {
@@ -35,7 +36,7 @@ Map<String, dynamic> _$StoragePathToJson(StoragePath instance) {
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]!; _$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]!;
writeNotNull('is_insensitive', instance.isInsensitive); writeNotNull('is_insensitive', instance.isInsensitive);
writeNotNull('document_count', instance.documentCount); writeNotNull('document_count', instance.documentCount);
writeNotNull('path', instance.path); val['path'] = instance.path;
return val; return val;
} }

View File

@@ -1,44 +1,84 @@
import 'dart:developer'; import 'dart:developer';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/src/converters/hex_color_json_converter.dart';
import 'package:paperless_api/src/models/labels/label_model.dart'; import 'package:paperless_api/src/models/labels/label_model.dart';
import 'package:paperless_api/src/models/labels/matching_algorithm.dart'; import 'package:paperless_api/src/models/labels/matching_algorithm.dart';
part 'tag_model.g.dart';
@HexColorJsonConverter()
@JsonSerializable(
fieldRename: FieldRename.snake, explicitToJson: true, constructor: "_")
class Tag extends Label { class Tag extends Label {
static const colorKey = 'color'; static const colorKey = 'color';
static const isInboxTagKey = 'is_inbox_tag'; static const isInboxTagKey = 'is_inbox_tag';
static const textColorKey = 'text_color'; static const textColorKey = 'text_color';
static const legacyColourKey = 'colour'; static const legacyColourKey = 'colour';
final Color? _apiV2color;
final Color? _apiV1color;
final Color? textColor; final Color? textColor;
final bool? isInboxTag; final bool? isInboxTag;
Color? get color => _apiV2color ?? _apiV1color; @protected
@JsonKey(name: colorKey)
Color? colorv2;
const Tag({ @protected
required super.id, @Deprecated(
"Alias for the field color. Deprecated since Paperless API v2. Please use the color getter to access the background color of this tag.",
)
@JsonKey(name: legacyColourKey)
Color? colorv1;
Color? get color => colorv2 ?? colorv1;
/// Constructor to use for serialization.
Tag._({
super.id,
required super.name, required super.name,
super.documentCount, super.documentCount,
super.isInsensitive, super.isInsensitive = true,
super.match, super.match,
required super.matchingAlgorithm, super.matchingAlgorithm,
super.slug, super.slug,
Color? color, this.colorv1,
this.colorv2,
this.textColor, this.textColor,
this.isInboxTag, this.isInboxTag = false,
}) : _apiV1color = color, }) {
_apiV2color = color; colorv1 ??= colorv2;
colorv2 ??= colorv1;
}
Tag({
int? id,
required String name,
int? documentCount,
bool? isInsensitive,
String? match,
MatchingAlgorithm matchingAlgorithm = MatchingAlgorithm.defaultValue,
String? slug,
Color? color,
Color? textColor,
bool? isInboxTag,
}) : this._(
id: id,
name: name,
documentCount: documentCount,
isInsensitive: isInsensitive,
match: match,
matchingAlgorithm: matchingAlgorithm,
slug: slug,
textColor: textColor,
colorv1: color,
colorv2: color,
);
@override @override
String toString() { String toString() => name;
return name;
}
@override @override
Tag copyWith({ Tag copyWith({
@@ -84,89 +124,8 @@ class Tag extends Label {
match, match,
]; ];
//FIXME: Why is this not generated?! factory Tag.fromJson(Map<String, dynamic> json) => _$TagFromJson(json);
factory Tag.fromJson(Map<String, dynamic> json) {
const $MatchingAlgorithmEnumMap = {
MatchingAlgorithm.anyWord: 1,
MatchingAlgorithm.allWords: 2,
MatchingAlgorithm.exactMatch: 3,
MatchingAlgorithm.regex: 4,
MatchingAlgorithm.fuzzy: 5,
MatchingAlgorithm.auto: 6,
};
return Tag(
id: json['id'] as int?,
name: json['name'] as String,
documentCount: json['document_count'] as int?,
isInsensitive: json['is_insensitive'] as bool?,
match: json['match'] as String?,
matchingAlgorithm:
$enumDecode($MatchingAlgorithmEnumMap, json['matching_algorithm']),
slug: json['slug'] as String?,
textColor: _colorFromJson(json['text_color']),
isInboxTag: json['is_inbox_tag'] as bool?,
color: _colorFromJson(json['color']) ?? _colorFromJson(json['colour']),
);
}
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() => _$TagToJson(this);
final val = <String, dynamic>{};
const $MatchingAlgorithmEnumMap = {
MatchingAlgorithm.anyWord: 1,
MatchingAlgorithm.allWords: 2,
MatchingAlgorithm.exactMatch: 3,
MatchingAlgorithm.regex: 4,
MatchingAlgorithm.fuzzy: 5,
MatchingAlgorithm.auto: 6,
};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('id', id);
val['name'] = name;
writeNotNull('slug', slug);
writeNotNull('match', match);
writeNotNull(
'matching_algorithm', $MatchingAlgorithmEnumMap[matchingAlgorithm]);
writeNotNull('is_insensitive', isInsensitive);
writeNotNull('document_count', documentCount);
writeNotNull('color', _toHex(_apiV2color));
writeNotNull('colour', _toHex(_apiV1color));
writeNotNull('text_color', _toHex(textColor));
writeNotNull('is_inbox_tag', isInboxTag);
return val;
}
static Color? _colorFromJson(dynamic color) {
if (color is Color) {
return color;
}
if (color is String) {
final decoded = int.tryParse(color.replaceAll("#", "ff"), radix: 16);
if (decoded == null) {
return null;
}
return Color(decoded);
}
return null;
}
///
/// Taken from [FormBuilderColorPicker].
///
static String? _toHex(Color? color) {
if (color == null) {
return null;
}
String val =
'#${(color.value & 0xFFFFFF).toRadixString(16).padLeft(6, '0').toLowerCase()}';
return val;
}
} }

View File

@@ -0,0 +1,47 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tag_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Tag _$TagFromJson(Map<String, dynamic> json) => Tag._(
id: json['id'] as int?,
name: json['name'] as String,
documentCount: json['document_count'] as int?,
isInsensitive: json['is_insensitive'] as bool? ?? true,
match: json['match'] as String?,
matchingAlgorithm: $enumDecodeNullable(
_$MatchingAlgorithmEnumMap, json['matching_algorithm']) ??
MatchingAlgorithm.defaultValue,
slug: json['slug'] as String?,
colorv1: const HexColorJsonConverter().fromJson(json['colour']),
colorv2: const HexColorJsonConverter().fromJson(json['color']),
textColor: const HexColorJsonConverter().fromJson(json['text_color']),
isInboxTag: json['is_inbox_tag'] as bool? ?? false,
);
Map<String, dynamic> _$TagToJson(Tag instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'slug': instance.slug,
'match': instance.match,
'matching_algorithm':
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]!,
'is_insensitive': instance.isInsensitive,
'document_count': instance.documentCount,
'text_color': const HexColorJsonConverter().toJson(instance.textColor),
'is_inbox_tag': instance.isInboxTag,
'color': const HexColorJsonConverter().toJson(instance.colorv2),
'colour': const HexColorJsonConverter().toJson(instance.colorv1),
};
const _$MatchingAlgorithmEnumMap = {
MatchingAlgorithm.anyWord: 1,
MatchingAlgorithm.allWords: 2,
MatchingAlgorithm.exactMatch: 3,
MatchingAlgorithm.regex: 4,
MatchingAlgorithm.fuzzy: 5,
MatchingAlgorithm.auto: 6,
};