From fd72d53d2a7a240c4330e05c8b40fe45fc7a1a0d Mon Sep 17 00:00:00 2001 From: gary Date: Tue, 24 Feb 2026 21:43:25 +0100 Subject: [PATCH] feat(project): persist catalog snapshot in project data --- lib/project_codec.dart | 37 ++++++++++++++++++++++++++++++++++-- test/project_codec_test.dart | 23 +++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/lib/project_codec.dart b/lib/project_codec.dart index bccdb78..599d20e 100644 --- a/lib/project_codec.dart +++ b/lib/project_codec.dart @@ -1,6 +1,28 @@ import 'dart:convert'; import 'dart:typed_data'; +class MosaicPaletteSnapshotEntry { + final String name; + final int colorValue; + + const MosaicPaletteSnapshotEntry({ + required this.name, + required this.colorValue, + }); + + Map toJson() => { + 'name': name, + 'colorValue': colorValue, + }; + + factory MosaicPaletteSnapshotEntry.fromJson(Map json) { + return MosaicPaletteSnapshotEntry( + name: json['name'] as String? ?? 'Unbenannt', + colorValue: (json['colorValue'] as num?)?.toInt() ?? 0xFF000000, + ); + } +} + class MosaicProjectData { final bool useCapSize; final String gridWidth; @@ -12,6 +34,7 @@ class MosaicProjectData { final double colorVariation; final String selectedPreset; final Uint8List? sourceImageBytes; + final List catalogSnapshot; final DateTime savedAt; const MosaicProjectData({ @@ -25,6 +48,7 @@ class MosaicProjectData { required this.colorVariation, required this.selectedPreset, required this.sourceImageBytes, + required this.catalogSnapshot, required this.savedAt, }); @@ -40,23 +64,32 @@ class MosaicProjectData { 'selectedPreset': selectedPreset, 'sourceImageBase64': sourceImageBytes == null ? null : base64Encode(sourceImageBytes!), + 'catalogSnapshot': catalogSnapshot.map((entry) => entry.toJson()).toList(), 'savedAt': savedAt.toIso8601String(), }; factory MosaicProjectData.fromJson(Map json) { final sourceB64 = json['sourceImageBase64'] as String?; + final snapshotRaw = (json['catalogSnapshot'] as List?) ?? const []; return MosaicProjectData( useCapSize: json['useCapSize'] as bool? ?? false, gridWidth: json['gridWidth'] as String? ?? '40', gridHeight: json['gridHeight'] as String? ?? '30', capSize: json['capSize'] as String? ?? '12', fidelityStructure: (json['fidelityStructure'] as num?)?.toDouble() ?? 0.5, - ditheringStrength: (json['ditheringStrength'] as num?)?.toDouble() ?? 0.35, + ditheringStrength: + (json['ditheringStrength'] as num?)?.toDouble() ?? 0.35, edgeEmphasis: (json['edgeEmphasis'] as num?)?.toDouble() ?? 0.4, colorVariation: (json['colorVariation'] as num?)?.toDouble() ?? 0.3, selectedPreset: json['selectedPreset'] as String? ?? 'ausgewogen', sourceImageBytes: sourceB64 == null ? null : base64Decode(sourceB64), - savedAt: DateTime.tryParse(json['savedAt'] as String? ?? '') ?? DateTime.now(), + catalogSnapshot: snapshotRaw + .whereType() + .map((entry) => MosaicPaletteSnapshotEntry.fromJson( + Map.from(entry))) + .toList(growable: false), + savedAt: + DateTime.tryParse(json['savedAt'] as String? ?? '') ?? DateTime.now(), ); } } diff --git a/test/project_codec_test.dart b/test/project_codec_test.dart index e9ad709..af59a9f 100644 --- a/test/project_codec_test.dart +++ b/test/project_codec_test.dart @@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:korken_mosaic/project_codec.dart'; void main() { - test('MosaicProjectData json roundtrip keeps values', () { + test('MosaicProjectData json roundtrip keeps values including catalog snapshot', () { final original = MosaicProjectData( useCapSize: true, gridWidth: '50', @@ -16,6 +16,10 @@ void main() { colorVariation: 0.5, selectedPreset: 'realistisch', sourceImageBytes: Uint8List.fromList([1, 2, 3]), + catalogSnapshot: const [ + MosaicPaletteSnapshotEntry(name: 'White', colorValue: 0xFFF2F2F2), + MosaicPaletteSnapshotEntry(name: 'Blue', colorValue: 0xFF3F6FD8), + ], savedAt: DateTime.parse('2026-01-01T12:00:00Z'), ); @@ -28,5 +32,22 @@ void main() { expect(decoded.selectedPreset, 'realistisch'); expect(decoded.sourceImageBytes, isNotNull); expect(decoded.sourceImageBytes!, [1, 2, 3]); + expect(decoded.catalogSnapshot.length, 2); + expect(decoded.catalogSnapshot.first.name, 'White'); + expect(decoded.catalogSnapshot.last.colorValue, 0xFF3F6FD8); + }); + + test('MosaicProjectData defaults to empty snapshot when old project has none', () { + final decoded = MosaicProjectData.fromJson({ + 'useCapSize': false, + 'gridWidth': '40', + 'gridHeight': '30', + 'capSize': '12', + 'selectedPreset': 'ausgewogen', + 'savedAt': '2026-01-01T12:00:00Z', + }); + + expect(decoded.catalogSnapshot, isEmpty); + expect(decoded.sourceImageBytes, isNull); }); }