From 348eb30e1a89cf7a385c14d52bb8208fae28b01d Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Mon, 6 Feb 2023 17:30:00 +0100 Subject: [PATCH] Fixes labels being serialized incorrectly, automates tag serialization --- lib/features/edit_label/view/label_form.dart | 17 +- .../converters/hex_color_json_converter.dart | 31 ++++ .../models/labels/correspondent_model.dart | 5 +- .../models/labels/correspondent_model.g.dart | 7 +- .../models/labels/document_type_model.dart | 4 +- .../models/labels/document_type_model.g.dart | 7 +- .../lib/src/models/labels/label_model.dart | 15 +- .../src/models/labels/matching_algorithm.dart | 9 +- .../src/models/labels/storage_path_model.dart | 10 +- .../models/labels/storage_path_model.g.dart | 11 +- .../lib/src/models/labels/tag_model.dart | 157 +++++++----------- .../lib/src/models/labels/tag_model.g.dart | 47 ++++++ 12 files changed, 175 insertions(+), 145 deletions(-) create mode 100644 packages/paperless_api/lib/src/converters/hex_color_json_converter.dart create mode 100644 packages/paperless_api/lib/src/models/labels/tag_model.g.dart diff --git a/lib/features/edit_label/view/label_form.dart b/lib/features/edit_label/view/label_form.dart index 8fa7327..1c0d1ea 100644 --- a/lib/features/edit_label/view/label_form.dart +++ b/lib/features/edit_label/view/label_form.dart @@ -54,8 +54,9 @@ class _LabelFormState extends State> { @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 extends State> { ), FormBuilderDropdown( 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 extends State> { .map( (algo) => DropdownMenuItem( child: Text( - translateMatchingAlgorithmDescription(context, algo)), + translateMatchingAlgorithmDescription(context, algo), + ), value: algo.value, ), ) @@ -139,8 +142,8 @@ class _LabelFormState extends State> { // 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); diff --git a/packages/paperless_api/lib/src/converters/hex_color_json_converter.dart b/packages/paperless_api/lib/src/converters/hex_color_json_converter.dart new file mode 100644 index 0000000..3a73baf --- /dev/null +++ b/packages/paperless_api/lib/src/converters/hex_color_json_converter.dart @@ -0,0 +1,31 @@ +import 'dart:ui'; + +import 'package:json_annotation/json_annotation.dart'; + +class HexColorJsonConverter implements JsonConverter { + 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; + } +} diff --git a/packages/paperless_api/lib/src/models/labels/correspondent_model.dart b/packages/paperless_api/lib/src/models/labels/correspondent_model.dart index e0602a1..2a7523b 100644 --- a/packages/paperless_api/lib/src/models/labels/correspondent_model.dart +++ b/packages/paperless_api/lib/src/models/labels/correspondent_model.dart @@ -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 json) => _$CorrespondentFromJson(json); + @override Map toJson() => _$CorrespondentToJson(this); @override diff --git a/packages/paperless_api/lib/src/models/labels/correspondent_model.g.dart b/packages/paperless_api/lib/src/models/labels/correspondent_model.g.dart index 7354ce7..abd32f6 100644 --- a/packages/paperless_api/lib/src/models/labels/correspondent_model.g.dart +++ b/packages/paperless_api/lib/src/models/labels/correspondent_model.g.dart @@ -12,9 +12,10 @@ Correspondent _$CorrespondentFromJson(Map 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( json['last_correspondence'], diff --git a/packages/paperless_api/lib/src/models/labels/document_type_model.dart b/packages/paperless_api/lib/src/models/labels/document_type_model.dart index 76be83e..085f822 100644 --- a/packages/paperless_api/lib/src/models/labels/document_type_model.dart +++ b/packages/paperless_api/lib/src/models/labels/document_type_model.dart @@ -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, }); diff --git a/packages/paperless_api/lib/src/models/labels/document_type_model.g.dart b/packages/paperless_api/lib/src/models/labels/document_type_model.g.dart index 93567b1..be8b0eb 100644 --- a/packages/paperless_api/lib/src/models/labels/document_type_model.g.dart +++ b/packages/paperless_api/lib/src/models/labels/document_type_model.g.dart @@ -11,9 +11,10 @@ DocumentType _$DocumentTypeFromJson(Map 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?, ); diff --git a/packages/paperless_api/lib/src/models/labels/label_model.dart b/packages/paperless_api/lib/src/models/labels/label_model.dart index 99111ed..1b78ccd 100644 --- a/packages/paperless_api/lib/src/models/labels/label_model.dart +++ b/packages/paperless_api/lib/src/models/labels/label_model.dart @@ -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, }); diff --git a/packages/paperless_api/lib/src/models/labels/matching_algorithm.dart b/packages/paperless_api/lib/src/models/labels/matching_algorithm.dart index 1d6fd4b..b4229f9 100644 --- a/packages/paperless_api/lib/src/models/labels/matching_algorithm.dart +++ b/packages/paperless_api/lib/src/models/labels/matching_algorithm.dart @@ -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; } diff --git a/packages/paperless_api/lib/src/models/labels/storage_path_model.dart b/packages/paperless_api/lib/src/models/labels/storage_path_model.dart index 0ac9c33..09566cc 100644 --- a/packages/paperless_api/lib/src/models/labels/storage_path_model.dart +++ b/packages/paperless_api/lib/src/models/labels/storage_path_model.dart @@ -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 json) => diff --git a/packages/paperless_api/lib/src/models/labels/storage_path_model.g.dart b/packages/paperless_api/lib/src/models/labels/storage_path_model.g.dart index 8c1211e..5ae4ad4 100644 --- a/packages/paperless_api/lib/src/models/labels/storage_path_model.g.dart +++ b/packages/paperless_api/lib/src/models/labels/storage_path_model.g.dart @@ -9,13 +9,14 @@ part of 'storage_path_model.dart'; StoragePath _$StoragePathFromJson(Map 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 _$StoragePathToJson(StoragePath instance) { @@ -35,7 +36,7 @@ Map _$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; } diff --git a/packages/paperless_api/lib/src/models/labels/tag_model.dart b/packages/paperless_api/lib/src/models/labels/tag_model.dart index 62c4523..8f22e05 100644 --- a/packages/paperless_api/lib/src/models/labels/tag_model.dart +++ b/packages/paperless_api/lib/src/models/labels/tag_model.dart @@ -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 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 json) => _$TagFromJson(json); @override - Map toJson() { - final val = {}; - - 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 toJson() => _$TagToJson(this); } diff --git a/packages/paperless_api/lib/src/models/labels/tag_model.g.dart b/packages/paperless_api/lib/src/models/labels/tag_model.g.dart new file mode 100644 index 0000000..9be6dc3 --- /dev/null +++ b/packages/paperless_api/lib/src/models/labels/tag_model.g.dart @@ -0,0 +1,47 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tag_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Tag _$TagFromJson(Map 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 _$TagToJson(Tag instance) => { + '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, +};