Compare commits
19 Commits
4cbd4eb478
...
feat/mosai
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bd109b728 | ||
|
|
1a21bc18bb | ||
|
|
fd72d53d2a | ||
|
|
25d570c779 | ||
|
|
df7406b494 | ||
|
|
ab7aa625e6 | ||
|
|
814705cac6 | ||
|
|
3651b073bf | ||
|
|
b553c29d39 | ||
|
|
84d649ac6d | ||
|
|
75a3a8dd60 | ||
|
|
9edaef23ff | ||
|
|
4cc7de158e | ||
|
|
94eec93e13 | ||
|
|
e392b99bef | ||
|
|
b82d9a03e8 | ||
|
|
448d9dc649 | ||
|
|
eb37322809 | ||
|
|
2e0da448ba |
15
README.md
15
README.md
@@ -8,11 +8,18 @@ Prototype Flutter app for generating bottle-cap mosaics from imported images.
|
||||
- Resolution controls:
|
||||
- explicit grid width/height
|
||||
- or auto grid by approximate cap size in source image pixels
|
||||
- Cap palette management:
|
||||
- list caps with name + color
|
||||
- add color via picker and/or manual hex
|
||||
- remove caps
|
||||
- Persistent **Cap Catalog** (local JSON in app documents directory):
|
||||
- each entry stores `name`, `color` (hex/swatch), and optional preview image path
|
||||
- survives app restarts
|
||||
- Catalog management:
|
||||
- add entries manually (name + hex/color picker + optional photo)
|
||||
- **Deckel fotografieren**: robust circular cap detection (edge-based circle search with fallback), color is computed only from masked cap interior pixels
|
||||
- selectable extraction mode in photo review dialog: **Dominante Farbe** or **Durchschnitt**
|
||||
- dedicated catalog browser with **list/grid** modes
|
||||
- edit existing entry name/color
|
||||
- delete entries (with thumbnail cleanup)
|
||||
- Mosaic preview + bill of materials counts per cap color
|
||||
- Mosaic palette source is always the current catalog entries
|
||||
|
||||
## Style controls (new)
|
||||
|
||||
|
||||
47
android/app/src/main/AndroidManifest.xml
Normal file
47
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<application
|
||||
android:label="korken_mosaic"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
136
docs/qa/issues-20260224.md
Normal file
136
docs/qa/issues-20260224.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# QA Issues – 2026-02-24
|
||||
|
||||
Scope: Agentische, statische QA-Review (Flutter/Dart CLI lokal nicht verfügbar: `flutter` fehlt). Fokus auf 4-Step-Flow, Save/Load/Delete, JSON-Export und UX-Kantenfälle.
|
||||
|
||||
## Issue 1 (P1) – 4-Step-Flow lässt Fortschritt ohne Pflichtdaten zu ✅ ERLEDIGT (feat/mosaic-stepper-export)
|
||||
**Bereich:** 4-Step-Flow
|
||||
|
||||
**Beobachtung**
|
||||
- Im `Stepper` kann man per `Weiter`/Tap bis Schritt 4 springen, auch ohne Bildauswahl.
|
||||
- Auf Schritt 4 ist „Generate Mosaic“ aktiv, aber `_generate()` bricht stillschweigend ab, wenn kein Bild vorhanden ist.
|
||||
|
||||
**Code-Hinweis**
|
||||
- `lib/main.dart:1068-1127` (`Stepper` ohne Guard/Validation)
|
||||
- `lib/main.dart:1250-1255` (Generate-Button nur an `_isGenerating` gebunden)
|
||||
- `lib/main.dart:884`ff (`_generate()` returnt bei `_sourceImageBytes == null` ohne User-Feedback)
|
||||
|
||||
**Repro-Schritte**
|
||||
1. App öffnen (kein Bild geladen).
|
||||
2. Im Mosaic-Stepper mehrfach „Weiter“ klicken oder direkt auf Schritt 4 tippen.
|
||||
3. „Generate Mosaic“ klicken.
|
||||
4. Es passiert visuell nichts (kein Ergebnis, kein Hinweis).
|
||||
|
||||
**Akzeptanzkriterium**
|
||||
- Schritt-Navigation ist zustandsbasiert: Schritt 2-4 erst zugänglich, wenn notwendige Voraussetzungen erfüllt sind.
|
||||
- „Generate Mosaic“ ist deaktiviert, solange kein Bild geladen ist.
|
||||
- Alternativ/zusätzlich: verständliche Fehlermeldung (Snackbar), falls Generierung ohne Bild ausgelöst wird.
|
||||
|
||||
---
|
||||
|
||||
## Issue 2 (P1) – Projekt-Load mit leerem Bild leert bestehenden Zustand nicht ✅ ERLEDIGT (feat/mosaic-stepper-export)
|
||||
**Bereich:** Save/Load-Flow
|
||||
|
||||
**Beobachtung**
|
||||
- Beim Laden werden `_sourceImageBytes` und `_result` nur gesetzt, wenn `data.sourceImageBytes != null`.
|
||||
- Lädt man ein Projekt ohne Bild, bleibt ggf. ein altes Bild/Ergebnis aus vorherigem Zustand erhalten.
|
||||
|
||||
**Code-Hinweis**
|
||||
- `lib/main.dart:308-313` (fehlender `else`-Zweig zum expliziten Zurücksetzen)
|
||||
|
||||
**Repro-Schritte**
|
||||
1. Projekt A mit Bild laden/generieren.
|
||||
2. Projekt B laden, das ohne Bild gespeichert wurde (oder manuell erstellt wurde).
|
||||
3. Erwartung: leerer/initialer Zustand.
|
||||
4. Ist-Zustand: vorheriges Bild kann bestehen bleiben.
|
||||
|
||||
**Akzeptanzkriterium**
|
||||
- Beim Laden eines Projekts ohne `sourceImageBytes` wird Zustand explizit zurückgesetzt (`_sourceImageBytes = null`, `_result = null`, ggf. Step auf 1).
|
||||
- UI zeigt konsistent den tatsächlich geladenen Projektzustand.
|
||||
|
||||
---
|
||||
|
||||
## Issue 3 (P1) – Projekt-Snapshots sind nicht reproduzierbar, da Katalog nicht versioniert wird ✅ ERLEDIGT (feat/mosaic-stepper-export)
|
||||
**Bereich:** Save/Load-Flow + JSON-Export
|
||||
|
||||
**Beobachtung**
|
||||
- Gespeicherte Projekte enthalten Parameter + Bild, aber **keinen Snapshot des verwendeten Farb-Katalogs**.
|
||||
- Beim Laden wird mit aktuellem globalen `_catalog` neu generiert → Ergebnis kann von ursprünglichem Snapshot abweichen.
|
||||
|
||||
**Code-Hinweis**
|
||||
- `lib/project_codec.dart` (`MosaicProjectData` ohne Katalogdaten)
|
||||
- `lib/main.dart:316` (`_generate()` nach Load nutzt aktuellen `_catalog`)
|
||||
|
||||
**Repro-Schritte**
|
||||
1. Mit Katalog-Set A ein Projekt erzeugen/speichern.
|
||||
2. Katalogfarben ändern (hinzufügen/löschen/umfärben).
|
||||
3. Projekt laden.
|
||||
4. Ergebnisfarben/Zuordnung unterscheiden sich vom ursprünglichen Stand.
|
||||
|
||||
**Akzeptanzkriterium**
|
||||
- Projektspeicherung enthält eine Palette/Katalog-Snapshot-Version (mind. Name + Farbe pro Eintrag).
|
||||
- Load nutzt standardmäßig den gespeicherten Snapshot (mit klarer UX bei Konflikten/Optionen).
|
||||
- Reproduzierbarkeit des Mosaiks ist gewährleistet.
|
||||
|
||||
---
|
||||
|
||||
## Issue 4 (P2) – Export JSON nicht klar reproduzierbar ohne Ergebnis
|
||||
**Bereich:** JSON-Export
|
||||
|
||||
**Beobachtung**
|
||||
- Export enthält `project` immer, `result` aber nur falls bereits generiert.
|
||||
- Wenn vor Export keine Generierung lief, fehlt Kerninformation (`assignments`/`palette`) ohne klaren Nutzerhinweis.
|
||||
|
||||
**Code-Hinweis**
|
||||
- `lib/main.dart:378-392`
|
||||
|
||||
**Repro-Schritte**
|
||||
1. Bild laden, aber nicht generieren.
|
||||
2. „Export JSON“ klicken.
|
||||
3. Exportdatei enthält kein `result`-Objekt.
|
||||
|
||||
**Akzeptanzkriterium**
|
||||
- UX-Entscheidung explizit umsetzen:
|
||||
- entweder Export-Button nur mit vorhandenem Ergebnis aktivieren, oder
|
||||
- vor Export automatisch generieren, oder
|
||||
- deutlichen Hinweisdialog anzeigen („Export ohne Ergebnisdaten“).
|
||||
|
||||
---
|
||||
|
||||
## Issue 5 (P3) – „Fertig“-CTA im letzten Step ohne klaren Effekt
|
||||
**Bereich:** 4-Step-Flow UX
|
||||
|
||||
**Beobachtung**
|
||||
- Im letzten Step zeigt der Continue-Button „Fertig“, führt aber funktional zu keiner sichtbaren Aktion.
|
||||
|
||||
**Code-Hinweis**
|
||||
- `lib/main.dart:1071-1093` (`onStepContinue` erhöht nur bis max. letztem Step)
|
||||
|
||||
**Repro-Schritte**
|
||||
1. Bis Schritt 4 navigieren.
|
||||
2. „Fertig“ klicken.
|
||||
3. Kein Abschluss-Feedback/State-Change.
|
||||
|
||||
**Akzeptanzkriterium**
|
||||
- Letzter CTA hat klare Bedeutung (z. B. „Generieren“, „Abschließen“, „Zum Export“) oder wird im letzten Step ausgeblendet.
|
||||
- Nutzer erhält eindeutiges Abschlussfeedback.
|
||||
|
||||
---
|
||||
|
||||
## Issue 6 (P3) – Lösch-Flow ohne Hinweis auf betroffenen Arbeitsstand
|
||||
**Bereich:** Delete-Flow UX
|
||||
|
||||
**Beobachtung**
|
||||
- Beim Löschen eines Snapshots gibt es nur Dateiname + Bestätigung.
|
||||
- Kein Hinweis, ob gerade geladener Stand betroffen ist bzw. wie sich das auf „Letzten Stand laden“ auswirkt.
|
||||
|
||||
**Code-Hinweis**
|
||||
- `lib/main.dart:333-367`
|
||||
|
||||
**Repro-Schritte**
|
||||
1. Snapshot laden.
|
||||
2. In Projekte denselben Snapshot löschen.
|
||||
3. Nutzer bleibt ohne Kontext, ob aktiver Stand/Latest-Verhalten beeinflusst ist.
|
||||
|
||||
**Akzeptanzkriterium**
|
||||
- Dialog/Feedback benennt Auswirkungen klar (z. B. „Aktuell geladener Zustand bleibt im Speicher bis Wechsel/Neustart“).
|
||||
- Optional: Markierung des aktuell geladenen Snapshots in der Liste.
|
||||
52
docs/review/review-20260224.md
Normal file
52
docs/review/review-20260224.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Code Review – Änderungen von `814705c` bis `HEAD` (`ab7aa62`)
|
||||
|
||||
## 1) High-risk Findings
|
||||
|
||||
### Keine blocker-kritischen Defekte gefunden
|
||||
Ich habe im betrachteten Commit keine eindeutigen Crash-/Datenverlust-Blocker gefunden, die einen sofortigen Release-Stopp erzwingen.
|
||||
|
||||
> Hinweis: Es gibt aber eine **relevante funktionale Regression** (siehe Nitpicks #1), die je nach Produktanforderung als Go/No-Go-Kriterium gewertet werden kann.
|
||||
|
||||
---
|
||||
|
||||
## 2) Nitpicks / Verbesserungen
|
||||
|
||||
1. **Funktionale Regression in der UI (ehemals 4 Style-Regler, jetzt nur noch 1 sichtbar)**
|
||||
In `lib/main.dart` zeigt `_buildColorStep()` nur noch den Slider `fidelityStructure`.
|
||||
Die bisherigen Feineinstellungen `ditheringStrength`, `edgeEmphasis`, `colorVariation` sind weiter im State/Codec vorhanden, aber nicht mehr direkt im UI editierbar.
|
||||
**Impact:** Nutzer verlieren granulare Qualitätskontrolle; gespeicherte Werte bleiben zwar erhalten, sind aber kaum noch aktiv manipulierbar.
|
||||
|
||||
2. **Stepper-Logik ohne Validierung / Guidance**
|
||||
`onStepContinue` lässt das Voranschreiten ohne notwendige Preconditions zu (z. B. ohne Bildauswahl bis Schritt 4). Das ist technisch robust (Generate guarded), aber UX-seitig irreführend.
|
||||
**Vorschlag:** Continue-Button je Schritt konditional deaktivieren oder klare Inline-Hinweise anzeigen.
|
||||
|
||||
3. **`Fertig`-Button im letzten Step ohne echte Aktion**
|
||||
In `controlsBuilder` wird im letzten Schritt der Label-Text auf `Fertig` gesetzt, aber `onStepContinue` führt dort effektiv nichts mehr aus.
|
||||
**Vorschlag:** Auf letzter Stufe Button ausblenden oder mit sinnvoller Aktion belegen (z. B. Export, Speichern, Zur Projektliste).
|
||||
|
||||
4. **Projekt-Ladesemantik setzt immer auf Step `result`**
|
||||
Beim Laden mit Bild springt der Flow direkt auf `MosaicFlowStep.result`. Das ist für „Quick resume“ okay, nimmt aber ggf. den Guided-Flow-Charakter.
|
||||
**Vorschlag:** Optionales Verhalten (z. B. Restore des letzten Steps oder Konfiguration `resumeAtResult`).
|
||||
|
||||
5. **Testabdeckung für neue Kernpfade noch dünn**
|
||||
Es gibt gute Basis-Tests (`project_codec_test`, einfacher Stepper-Smoke-Test), aber es fehlen Tests für:
|
||||
- latest/autosave-Verhalten (`latest_project.json`),
|
||||
- manuell vs. automatisch speichern,
|
||||
- Export-JSON-Struktur inkl. Result-Payload,
|
||||
- Laden aus Snapshot vs. latest.
|
||||
|
||||
---
|
||||
|
||||
## 3) Go/No-Go Empfehlung
|
||||
|
||||
**Empfehlung: GO mit Auflagen (kein Hard No-Go).**
|
||||
|
||||
Begründung:
|
||||
- Die neuen Features (4-Step-Flow, autosave/latest, JSON-Export, Bestätigungsdialog beim Löschen) sind grundsätzlich sinnvoll umgesetzt.
|
||||
- Kein klarer, reproduzierbarer Blocker im Diff erkennbar.
|
||||
- Vor Release sollten jedoch mindestens die UX-/Funktionsregressionspunkte (insb. fehlende 3 Style-Regler) bewusst entschieden/fixiert werden.
|
||||
|
||||
**Release-Auflagen (kurz):**
|
||||
1. Entscheiden/fixen, ob die 3 fehlenden Style-Regler absichtliche Scope-Reduktion oder Regression sind.
|
||||
2. Stepper-UX (Continue/Finish-Verhalten) konsistenter machen.
|
||||
3. 2–3 gezielte Tests für Save/Load/Export-Pfade ergänzen.
|
||||
72
ios/Runner/Info.plist
Normal file
72
ios/Runner/Info.plist
Normal file
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Korken Mosaic</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>korken_mosaic</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Die Kamera wird verwendet, um einen Flaschendeckel für die Farbpalette zu fotografieren.</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>flutter</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
2553
lib/main.dart
2553
lib/main.dart
File diff suppressed because it is too large
Load Diff
95
lib/project_codec.dart
Normal file
95
lib/project_codec.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
class MosaicPaletteSnapshotEntry {
|
||||
final String name;
|
||||
final int colorValue;
|
||||
|
||||
const MosaicPaletteSnapshotEntry({
|
||||
required this.name,
|
||||
required this.colorValue,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'colorValue': colorValue,
|
||||
};
|
||||
|
||||
factory MosaicPaletteSnapshotEntry.fromJson(Map<String, dynamic> json) {
|
||||
return MosaicPaletteSnapshotEntry(
|
||||
name: json['name'] as String? ?? 'Unbenannt',
|
||||
colorValue: (json['colorValue'] as num?)?.toInt() ?? 0xFF000000,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MosaicProjectData {
|
||||
final bool useCapSize;
|
||||
final String gridWidth;
|
||||
final String gridHeight;
|
||||
final String capSize;
|
||||
final double fidelityStructure;
|
||||
final double ditheringStrength;
|
||||
final double edgeEmphasis;
|
||||
final double colorVariation;
|
||||
final String selectedPreset;
|
||||
final Uint8List? sourceImageBytes;
|
||||
final List<MosaicPaletteSnapshotEntry> catalogSnapshot;
|
||||
final DateTime savedAt;
|
||||
|
||||
const MosaicProjectData({
|
||||
required this.useCapSize,
|
||||
required this.gridWidth,
|
||||
required this.gridHeight,
|
||||
required this.capSize,
|
||||
required this.fidelityStructure,
|
||||
required this.ditheringStrength,
|
||||
required this.edgeEmphasis,
|
||||
required this.colorVariation,
|
||||
required this.selectedPreset,
|
||||
required this.sourceImageBytes,
|
||||
required this.catalogSnapshot,
|
||||
required this.savedAt,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'useCapSize': useCapSize,
|
||||
'gridWidth': gridWidth,
|
||||
'gridHeight': gridHeight,
|
||||
'capSize': capSize,
|
||||
'fidelityStructure': fidelityStructure,
|
||||
'ditheringStrength': ditheringStrength,
|
||||
'edgeEmphasis': edgeEmphasis,
|
||||
'colorVariation': colorVariation,
|
||||
'selectedPreset': selectedPreset,
|
||||
'sourceImageBase64':
|
||||
sourceImageBytes == null ? null : base64Encode(sourceImageBytes!),
|
||||
'catalogSnapshot': catalogSnapshot.map((entry) => entry.toJson()).toList(),
|
||||
'savedAt': savedAt.toIso8601String(),
|
||||
};
|
||||
|
||||
factory MosaicProjectData.fromJson(Map<String, dynamic> 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,
|
||||
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),
|
||||
catalogSnapshot: snapshotRaw
|
||||
.whereType<Map>()
|
||||
.map((entry) => MosaicPaletteSnapshotEntry.fromJson(
|
||||
Map<String, dynamic>.from(entry)))
|
||||
.toList(growable: false),
|
||||
savedAt:
|
||||
DateTime.tryParse(json['savedAt'] as String? ?? '') ?? DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
578
pubspec.lock
Normal file
578
pubspec.lock
Normal file
@@ -0,0 +1,578 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.9"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
code_assets:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_assets
|
||||
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.5+2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_linux
|
||||
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.4"
|
||||
file_selector_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_macos
|
||||
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.5"
|
||||
file_selector_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_platform_interface
|
||||
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
file_selector_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file_selector_windows
|
||||
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+5"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_colorpicker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_colorpicker
|
||||
sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.33"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
hooks:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hooks
|
||||
sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image
|
||||
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: eda9b91b7e266d9041084a42d605a74937d996b87083395c5e47835916a86156
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+14"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_for_web
|
||||
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
image_picker_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_ios
|
||||
sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+6"
|
||||
image_picker_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_linux
|
||||
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
image_picker_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_macos
|
||||
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2+1"
|
||||
image_picker_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.1"
|
||||
image_picker_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_windows
|
||||
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.18"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
native_toolchain_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: native_toolchain_c
|
||||
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.4"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.3.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.22"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.9"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.2"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.6.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.10.3 <4.0.0"
|
||||
flutter: ">=3.38.4"
|
||||
@@ -13,6 +13,7 @@ dependencies:
|
||||
image_picker: ^1.1.2
|
||||
image: ^4.2.0
|
||||
flutter_colorpicker: ^1.1.0
|
||||
path_provider: ^2.1.5
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
53
test/project_codec_test.dart
Normal file
53
test/project_codec_test.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:korken_mosaic/project_codec.dart';
|
||||
|
||||
void main() {
|
||||
test('MosaicProjectData json roundtrip keeps values including catalog snapshot', () {
|
||||
final original = MosaicProjectData(
|
||||
useCapSize: true,
|
||||
gridWidth: '50',
|
||||
gridHeight: '40',
|
||||
capSize: '10',
|
||||
fidelityStructure: 0.3,
|
||||
ditheringStrength: 0.2,
|
||||
edgeEmphasis: 0.4,
|
||||
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'),
|
||||
);
|
||||
|
||||
final decoded = MosaicProjectData.fromJson(original.toJson());
|
||||
|
||||
expect(decoded.useCapSize, isTrue);
|
||||
expect(decoded.gridWidth, '50');
|
||||
expect(decoded.gridHeight, '40');
|
||||
expect(decoded.capSize, '10');
|
||||
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);
|
||||
});
|
||||
}
|
||||
35
test/widget_test.dart
Normal file
35
test/widget_test.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:korken_mosaic/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('stepper blocks forward navigation without image and shows hint',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(const KorkenMosaicApp());
|
||||
|
||||
expect(find.text('1) Bild'), findsOneWidget);
|
||||
expect(find.text('2) Größe'), findsOneWidget);
|
||||
expect(find.text('3) Farben'), findsOneWidget);
|
||||
expect(find.text('4) Ergebnis'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Weiter'));
|
||||
await tester.pump();
|
||||
|
||||
// Stays on step 1 because no source image is available yet.
|
||||
expect(find.text('Import target image'), findsOneWidget);
|
||||
expect(find.text('Bitte zuerst ein Bild auswählen.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('generate actions are disabled without image',
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(const KorkenMosaicApp());
|
||||
|
||||
final fab = tester.widget<FloatingActionButton>(
|
||||
find.byType(FloatingActionButton),
|
||||
);
|
||||
expect(fab.onPressed, isNull);
|
||||
|
||||
// Result-step action is not reachable before image selection.
|
||||
expect(find.byKey(const Key('generate-btn')), findsNothing);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user