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
void initState() {
super.initState();
_enableMatchFormField =
widget.initialValue?.matchingAlgorithm != MatchingAlgorithm.auto;
_enableMatchFormField = (widget.initialValue?.matchingAlgorithm ??
MatchingAlgorithm.defaultValue) !=
MatchingAlgorithm.auto;
}
@override
@@ -83,8 +84,9 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
),
FormBuilderDropdown<int?>(
name: Label.matchingAlgorithmKey,
initialValue: widget.initialValue?.matchingAlgorithm.value ??
MatchingAlgorithm.auto.value,
initialValue: (widget.initialValue?.matchingAlgorithm ??
MatchingAlgorithm.defaultValue)
.value,
decoration: InputDecoration(
labelText: S.of(context).labelMatchingAlgorithmPropertyLabel,
errorText: _errors[Label.matchingAlgorithmKey],
@@ -99,7 +101,8 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
.map(
(algo) => DropdownMenuItem<int?>(
child: Text(
translateMatchingAlgorithmDescription(context, algo)),
translateMatchingAlgorithmDescription(context, algo),
),
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.
mergedJson[Label.matchKey] = '';
}
final createdLabel = await widget.submitButtonConfig
.onSubmit(widget.fromJsonT(mergedJson));
final parsed = widget.fromJsonT(mergedJson);
final createdLabel = await widget.submitButtonConfig.onSubmit(parsed);
Navigator.pop(context, createdLabel);
} on PaperlessServerException catch (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;
const Correspondent({
required super.id,
super.id,
required super.name,
super.slug,
super.match,
required super.matchingAlgorithm,
super.matchingAlgorithm,
super.isInsensitive,
super.documentCount,
this.lastCorrespondence,
@@ -24,6 +24,7 @@ class Correspondent extends Label {
factory Correspondent.fromJson(Map<String, dynamic> json) =>
_$CorrespondentFromJson(json);
@override
Map<String, dynamic> toJson() => _$CorrespondentToJson(this);
@override

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,17 +6,17 @@ part 'storage_path_model.g.dart';
@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
class StoragePath extends Label {
static const pathKey = 'path';
late String? path;
final String path;
StoragePath({
required super.id,
const StoragePath({
super.id,
required super.name,
required this.path,
super.slug,
super.match,
required super.matchingAlgorithm,
super.matchingAlgorithm,
super.isInsensitive,
super.documentCount,
required this.path,
});
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(
id: json['id'] as int?,
name: json['name'] as String,
path: json['path'] as String,
slug: json['slug'] as String?,
match: json['match'] as String?,
matchingAlgorithm:
$enumDecode(_$MatchingAlgorithmEnumMap, json['matching_algorithm']),
isInsensitive: json['is_insensitive'] as bool?,
matchingAlgorithm: $enumDecodeNullable(
_$MatchingAlgorithmEnumMap, json['matching_algorithm']) ??
MatchingAlgorithm.defaultValue,
isInsensitive: json['is_insensitive'] as bool? ?? true,
documentCount: json['document_count'] as int?,
path: json['path'] as String?,
);
Map<String, dynamic> _$StoragePathToJson(StoragePath instance) {
@@ -35,7 +36,7 @@ Map<String, dynamic> _$StoragePathToJson(StoragePath instance) {
_$MatchingAlgorithmEnumMap[instance.matchingAlgorithm]!;
writeNotNull('is_insensitive', instance.isInsensitive);
writeNotNull('document_count', instance.documentCount);
writeNotNull('path', instance.path);
val['path'] = instance.path;
return val;
}

View File

@@ -1,44 +1,84 @@
import 'dart:developer';
import 'dart:ui';
import 'package:flutter/foundation.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/matching_algorithm.dart';
part 'tag_model.g.dart';
@HexColorJsonConverter()
@JsonSerializable(
fieldRename: FieldRename.snake, explicitToJson: true, constructor: "_")
class Tag extends Label {
static const colorKey = 'color';
static const isInboxTagKey = 'is_inbox_tag';
static const textColorKey = 'text_color';
static const legacyColourKey = 'colour';
final Color? _apiV2color;
final Color? _apiV1color;
final Color? textColor;
final bool? isInboxTag;
Color? get color => _apiV2color ?? _apiV1color;
@protected
@JsonKey(name: colorKey)
Color? colorv2;
const Tag({
required super.id,
@protected
@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,
super.documentCount,
super.isInsensitive,
super.isInsensitive = true,
super.match,
required super.matchingAlgorithm,
super.matchingAlgorithm,
super.slug,
Color? color,
this.colorv1,
this.colorv2,
this.textColor,
this.isInboxTag,
}) : _apiV1color = color,
_apiV2color = color;
this.isInboxTag = false,
}) {
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
String toString() {
return name;
}
String toString() => name;
@override
Tag copyWith({
@@ -84,89 +124,8 @@ 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.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']),
);
}
factory Tag.fromJson(Map<String, dynamic> json) => _$TagFromJson(json);
@override
Map<String, dynamic> toJson() {
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;
}
Map<String, dynamic> toJson() => _$TagToJson(this);
}

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