Add persistent cap catalog with image/name/color management
This commit is contained in:
16
README.md
16
README.md
@@ -8,13 +8,17 @@ Prototype Flutter app for generating bottle-cap mosaics from imported images.
|
|||||||
- Resolution controls:
|
- Resolution controls:
|
||||||
- explicit grid width/height
|
- explicit grid width/height
|
||||||
- or auto grid by approximate cap size in source image pixels
|
- or auto grid by approximate cap size in source image pixels
|
||||||
- Cap palette management:
|
- Persistent **Cap Catalog** (local JSON in app documents directory):
|
||||||
- list caps with name + color
|
- each entry stores `name`, `color` (hex/swatch), and optional preview image path
|
||||||
- add color via picker and/or manual hex
|
- survives app restarts
|
||||||
- **Deckel fotografieren**: capture a cap with camera and auto-detect its color from a robust center-circle sample (reduced background contamination)
|
- Catalog management:
|
||||||
- review detected color preview, edit name + hex, then save as a normal palette entry
|
- add entries manually (name + hex/color picker + optional photo)
|
||||||
- remove caps
|
- **Deckel fotografieren**: capture a cap with camera, auto-detect dominant center color, store cropped thumbnail
|
||||||
|
- 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 preview + bill of materials counts per cap color
|
||||||
|
- Mosaic palette source is always the current catalog entries
|
||||||
|
|
||||||
## Style controls (new)
|
## Style controls (new)
|
||||||
|
|
||||||
|
|||||||
565
lib/main.dart
565
lib/main.dart
@@ -1,4 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
@@ -7,6 +9,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||||
import 'package:image/image.dart' as img;
|
import 'package:image/image.dart' as img;
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const KorkenMosaicApp());
|
runApp(const KorkenMosaicApp());
|
||||||
@@ -27,6 +30,8 @@ class KorkenMosaicApp extends StatelessWidget {
|
|||||||
|
|
||||||
enum StylePreset { realistisch, ausgewogen, kuenstlerisch }
|
enum StylePreset { realistisch, ausgewogen, kuenstlerisch }
|
||||||
|
|
||||||
|
enum CatalogViewMode { list, grid }
|
||||||
|
|
||||||
class MosaicHomePage extends StatefulWidget {
|
class MosaicHomePage extends StatefulWidget {
|
||||||
const MosaicHomePage({super.key});
|
const MosaicHomePage({super.key});
|
||||||
|
|
||||||
@@ -50,6 +55,8 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
bool _isGenerating = false;
|
bool _isGenerating = false;
|
||||||
Timer? _debounceTimer;
|
Timer? _debounceTimer;
|
||||||
int _generationToken = 0;
|
int _generationToken = 0;
|
||||||
|
bool _isCatalogLoaded = false;
|
||||||
|
CatalogViewMode _catalogViewMode = CatalogViewMode.grid;
|
||||||
|
|
||||||
double _fidelityStructure = 0.5;
|
double _fidelityStructure = 0.5;
|
||||||
double _ditheringStrength = 0.35;
|
double _ditheringStrength = 0.35;
|
||||||
@@ -57,13 +64,7 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
double _colorVariation = 0.3;
|
double _colorVariation = 0.3;
|
||||||
StylePreset _selectedPreset = StylePreset.ausgewogen;
|
StylePreset _selectedPreset = StylePreset.ausgewogen;
|
||||||
|
|
||||||
final List<CapColor> _palette = [
|
List<CapCatalogEntry> _catalog = [];
|
||||||
CapColor(name: 'White', color: const Color(0xFFF2F2F2)),
|
|
||||||
CapColor(name: 'Black', color: const Color(0xFF222222)),
|
|
||||||
CapColor(name: 'Red', color: const Color(0xFFD84343)),
|
|
||||||
CapColor(name: 'Blue', color: const Color(0xFF3F6FD8)),
|
|
||||||
CapColor(name: 'Green', color: const Color(0xFF4FAE63)),
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -71,6 +72,7 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
_gridWidthCtrl.addListener(_scheduleRegenerate);
|
_gridWidthCtrl.addListener(_scheduleRegenerate);
|
||||||
_gridHeightCtrl.addListener(_scheduleRegenerate);
|
_gridHeightCtrl.addListener(_scheduleRegenerate);
|
||||||
_capSizeCtrl.addListener(_scheduleRegenerate);
|
_capSizeCtrl.addListener(_scheduleRegenerate);
|
||||||
|
_loadCatalog();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -84,6 +86,55 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<File> _catalogFile() async {
|
||||||
|
final docs = await getApplicationDocumentsDirectory();
|
||||||
|
return File('${docs.path}/cap_catalog.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _saveThumbnail(Uint8List bytes, String id) async {
|
||||||
|
final docs = await getApplicationDocumentsDirectory();
|
||||||
|
final dir = Directory('${docs.path}/cap_thumbs');
|
||||||
|
await dir.create(recursive: true);
|
||||||
|
final file = File('${dir.path}/$id.png');
|
||||||
|
await file.writeAsBytes(bytes, flush: true);
|
||||||
|
return file.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadCatalog() async {
|
||||||
|
final defaults = [
|
||||||
|
CapCatalogEntry.newEntry(name: 'White', color: const Color(0xFFF2F2F2)),
|
||||||
|
CapCatalogEntry.newEntry(name: 'Black', color: const Color(0xFF222222)),
|
||||||
|
CapCatalogEntry.newEntry(name: 'Red', color: const Color(0xFFD84343)),
|
||||||
|
CapCatalogEntry.newEntry(name: 'Blue', color: const Color(0xFF3F6FD8)),
|
||||||
|
CapCatalogEntry.newEntry(name: 'Green', color: const Color(0xFF4FAE63)),
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
final file = await _catalogFile();
|
||||||
|
if (await file.exists()) {
|
||||||
|
final jsonRaw = jsonDecode(await file.readAsString()) as List<dynamic>;
|
||||||
|
_catalog = jsonRaw
|
||||||
|
.map((e) => CapCatalogEntry.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
if (_catalog.isEmpty) {
|
||||||
|
_catalog = defaults;
|
||||||
|
await _persistCatalog();
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
_catalog = defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _isCatalogLoaded = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _persistCatalog() async {
|
||||||
|
final file = await _catalogFile();
|
||||||
|
final jsonData = jsonEncode(_catalog.map((e) => e.toJson()).toList());
|
||||||
|
await file.writeAsString(jsonData, flush: true);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _pickImage() async {
|
Future<void> _pickImage() async {
|
||||||
final XFile? picked = await _picker.pickImage(source: ImageSource.gallery);
|
final XFile? picked = await _picker.pickImage(source: ImageSource.gallery);
|
||||||
if (picked == null) return;
|
if (picked == null) return;
|
||||||
@@ -125,12 +176,8 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Image.memory(
|
child: Image.memory(previewBytes,
|
||||||
previewBytes,
|
width: 220, height: 220, fit: BoxFit.cover),
|
||||||
width: 220,
|
|
||||||
height: 220,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
@@ -180,22 +227,24 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(ctx),
|
onPressed: () => Navigator.pop(ctx),
|
||||||
child: const Text('Abbrechen'),
|
child: const Text('Abbrechen')),
|
||||||
),
|
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
final name = _photoCapNameCtrl.text.trim();
|
final name = _photoCapNameCtrl.text.trim();
|
||||||
if (name.isEmpty) return;
|
if (name.isEmpty) return;
|
||||||
final parsed = _parseHex(_photoCapHexCtrl.text);
|
final parsed = _parseHex(_photoCapHexCtrl.text);
|
||||||
setState(() {
|
final entry = CapCatalogEntry.newEntry(
|
||||||
_palette.add(
|
name: name, color: parsed ?? selected);
|
||||||
CapColor(name: name, color: parsed ?? selected),
|
entry.imagePath =
|
||||||
);
|
await _saveThumbnail(previewBytes, entry.id);
|
||||||
});
|
_catalog.add(entry);
|
||||||
|
await _persistCatalog();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {});
|
||||||
Navigator.pop(ctx);
|
Navigator.pop(ctx);
|
||||||
_scheduleRegenerate();
|
_scheduleRegenerate();
|
||||||
},
|
},
|
||||||
child: const Text('Zur Palette hinzufügen'),
|
child: const Text('Zum Katalog hinzufügen'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -245,25 +294,112 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
_scheduleRegenerate();
|
_scheduleRegenerate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addCapDialog() {
|
Future<void> _addCapDialog() async {
|
||||||
Color selected = Colors.orange;
|
Color selected = Colors.orange;
|
||||||
final nameCtrl = TextEditingController();
|
final nameCtrl = TextEditingController();
|
||||||
final hexCtrl = TextEditingController(text: _colorToHex(selected));
|
final hexCtrl = TextEditingController(text: _colorToHex(selected));
|
||||||
|
String? imagePath;
|
||||||
|
|
||||||
showDialog<void>(
|
await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) {
|
builder: (ctx) {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (ctx, setDialogState) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('Add Bottle Cap Color'),
|
title: const Text('Deckel manuell hinzufügen'),
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: nameCtrl,
|
controller: nameCtrl,
|
||||||
decoration: const InputDecoration(labelText: 'Name'),
|
decoration: const InputDecoration(labelText: 'Name')),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextField(
|
||||||
|
controller: hexCtrl,
|
||||||
|
decoration:
|
||||||
|
const InputDecoration(labelText: 'Hex (#RRGGBB)'),
|
||||||
|
onChanged: (value) {
|
||||||
|
final parsed = _parseHex(value);
|
||||||
|
if (parsed != null)
|
||||||
|
setDialogState(() => selected = parsed);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: () async {
|
||||||
|
final picked = await _picker.pickImage(
|
||||||
|
source: ImageSource.gallery,
|
||||||
|
maxWidth: 1200,
|
||||||
|
imageQuality: 90);
|
||||||
|
if (picked == null) return;
|
||||||
|
imagePath = picked.path;
|
||||||
|
setDialogState(() {});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.image_outlined),
|
||||||
|
label: Text(imagePath == null
|
||||||
|
? 'Optionales Foto wählen'
|
||||||
|
: 'Foto gewählt ✅'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
ColorPicker(
|
||||||
|
pickerColor: selected,
|
||||||
|
onColorChanged: (c) {
|
||||||
|
selected = c;
|
||||||
|
hexCtrl.text = _colorToHex(c);
|
||||||
|
setDialogState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx),
|
||||||
|
child: const Text('Cancel')),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final name = nameCtrl.text.trim();
|
||||||
|
if (name.isEmpty) return;
|
||||||
|
final entry = CapCatalogEntry.newEntry(
|
||||||
|
name: name,
|
||||||
|
color: _parseHex(hexCtrl.text) ?? selected,
|
||||||
|
imagePath: imagePath);
|
||||||
|
_catalog.add(entry);
|
||||||
|
await _persistCatalog();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {});
|
||||||
|
Navigator.pop(ctx);
|
||||||
|
_scheduleRegenerate();
|
||||||
|
},
|
||||||
|
child: const Text('Add'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _editEntry(CapCatalogEntry entry) async {
|
||||||
|
Color selected = entry.color;
|
||||||
|
final nameCtrl = TextEditingController(text: entry.name);
|
||||||
|
final hexCtrl = TextEditingController(text: _colorToHex(entry.color));
|
||||||
|
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Deckel bearbeiten'),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: nameCtrl,
|
||||||
|
decoration: const InputDecoration(labelText: 'Name')),
|
||||||
|
const SizedBox(height: 8),
|
||||||
TextField(
|
TextField(
|
||||||
controller: hexCtrl,
|
controller: hexCtrl,
|
||||||
decoration: const InputDecoration(labelText: 'Hex (#RRGGBB)'),
|
decoration: const InputDecoration(labelText: 'Hex (#RRGGBB)'),
|
||||||
@@ -286,22 +422,21 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(ctx),
|
onPressed: () => Navigator.pop(ctx),
|
||||||
child: const Text('Cancel'),
|
child: const Text('Abbrechen')),
|
||||||
),
|
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
final name = nameCtrl.text.trim();
|
entry.name = nameCtrl.text.trim().isEmpty
|
||||||
if (name.isEmpty) return;
|
? entry.name
|
||||||
setState(() {
|
: nameCtrl.text.trim();
|
||||||
_palette.add(
|
entry.colorValue =
|
||||||
CapColor(
|
(_parseHex(hexCtrl.text) ?? selected).toARGB32();
|
||||||
name: name, color: _parseHex(hexCtrl.text) ?? selected),
|
await _persistCatalog();
|
||||||
);
|
if (!mounted) return;
|
||||||
});
|
setState(() {});
|
||||||
Navigator.pop(ctx);
|
Navigator.pop(ctx);
|
||||||
_scheduleRegenerate();
|
_scheduleRegenerate();
|
||||||
},
|
},
|
||||||
child: const Text('Add'),
|
child: const Text('Speichern'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -309,8 +444,23 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _deleteEntry(CapCatalogEntry entry) async {
|
||||||
|
if (_catalog.length <= 1) return;
|
||||||
|
_catalog.removeWhere((e) => e.id == entry.id);
|
||||||
|
if (entry.imagePath != null) {
|
||||||
|
final f = File(entry.imagePath!);
|
||||||
|
if (await f.exists()) {
|
||||||
|
await f.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await _persistCatalog();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {});
|
||||||
|
_scheduleRegenerate();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _generate() async {
|
Future<void> _generate() async {
|
||||||
if (_sourceImageBytes == null || _palette.isEmpty) return;
|
if (_sourceImageBytes == null || _catalog.isEmpty) return;
|
||||||
|
|
||||||
final int gridW = math.max(1, int.tryParse(_gridWidthCtrl.text) ?? 40);
|
final int gridW = math.max(1, int.tryParse(_gridWidthCtrl.text) ?? 40);
|
||||||
final int gridH = math.max(1, int.tryParse(_gridHeightCtrl.text) ?? 30);
|
final int gridH = math.max(1, int.tryParse(_gridHeightCtrl.text) ?? 30);
|
||||||
@@ -329,9 +479,8 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
'ditheringStrength': _ditheringStrength,
|
'ditheringStrength': _ditheringStrength,
|
||||||
'edgeEmphasis': _edgeEmphasis,
|
'edgeEmphasis': _edgeEmphasis,
|
||||||
'colorVariation': _colorVariation,
|
'colorVariation': _colorVariation,
|
||||||
'palette': _palette
|
'palette': _catalog
|
||||||
.map((p) =>
|
.map((p) => <String, dynamic>{'name': p.name, 'value': p.colorValue})
|
||||||
<String, dynamic>{'name': p.name, 'value': p.color.toARGB32()})
|
|
||||||
.toList(growable: false),
|
.toList(growable: false),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -339,10 +488,8 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
if (!mounted || token != _generationToken) return;
|
if (!mounted || token != _generationToken) return;
|
||||||
|
|
||||||
final palette = (out['palette'] as List)
|
final palette = (out['palette'] as List)
|
||||||
.map(
|
.map((e) => CapColor(
|
||||||
(e) => CapColor(
|
name: e['name'] as String, color: Color(e['value'] as int)))
|
||||||
name: e['name'] as String, color: Color(e['value'] as int)),
|
|
||||||
)
|
|
||||||
.toList(growable: false);
|
.toList(growable: false);
|
||||||
|
|
||||||
final countsList = (out['counts'] as List).cast<int>();
|
final countsList = (out['counts'] as List).cast<int>();
|
||||||
@@ -378,12 +525,13 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
width: 18,
|
width: 18,
|
||||||
height: 18,
|
height: 18,
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(strokeWidth: 2))
|
||||||
)
|
|
||||||
: const Icon(Icons.auto_fix_high),
|
: const Icon(Icons.auto_fix_high),
|
||||||
label: Text(_isGenerating ? 'Generating…' : 'Generate Mosaic'),
|
label: Text(_isGenerating ? 'Generating…' : 'Generate Mosaic'),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: !_isCatalogLoaded
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: Padding(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
@@ -397,7 +545,8 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
icon: const Icon(Icons.image_outlined),
|
icon: const Icon(Icons.image_outlined),
|
||||||
label: const Text('Import target image'),
|
label: const Text('Import target image'),
|
||||||
),
|
),
|
||||||
if (_sourceImageBytes != null) const Text('Image loaded ✅'),
|
if (_sourceImageBytes != null)
|
||||||
|
const Text('Image loaded ✅'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -420,18 +569,16 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _gridWidthCtrl,
|
controller: _gridWidthCtrl,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
decoration:
|
decoration: const InputDecoration(
|
||||||
const InputDecoration(labelText: 'Grid Width'),
|
labelText: 'Grid Width')),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _gridHeightCtrl,
|
controller: _gridHeightCtrl,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
decoration:
|
decoration: const InputDecoration(
|
||||||
const InputDecoration(labelText: 'Grid Height'),
|
labelText: 'Grid Height')),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -440,29 +587,25 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
controller: _capSizeCtrl,
|
controller: _capSizeCtrl,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Approx cap size in source image (pixels)',
|
labelText:
|
||||||
),
|
'Approx cap size in source image (pixels)'),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text(
|
const Text('Style Preset',
|
||||||
'Style Preset',
|
style:
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
SegmentedButton<StylePreset>(
|
SegmentedButton<StylePreset>(
|
||||||
segments: const [
|
segments: const [
|
||||||
ButtonSegment(
|
ButtonSegment(
|
||||||
value: StylePreset.realistisch,
|
value: StylePreset.realistisch,
|
||||||
label: Text('Realistisch'),
|
label: Text('Realistisch')),
|
||||||
),
|
|
||||||
ButtonSegment(
|
ButtonSegment(
|
||||||
value: StylePreset.ausgewogen,
|
value: StylePreset.ausgewogen,
|
||||||
label: Text('Ausgewogen'),
|
label: Text('Ausgewogen')),
|
||||||
),
|
|
||||||
ButtonSegment(
|
ButtonSegment(
|
||||||
value: StylePreset.kuenstlerisch,
|
value: StylePreset.kuenstlerisch,
|
||||||
label: Text('Künstlerisch'),
|
label: Text('Künstlerisch')),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
selected: {_selectedPreset},
|
selected: {_selectedPreset},
|
||||||
onSelectionChanged: (s) => _applyPreset(s.first),
|
onSelectionChanged: (s) => _applyPreset(s.first),
|
||||||
@@ -470,8 +613,8 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Card(
|
Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
horizontal: 12, vertical: 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_SliderRow(
|
_SliderRow(
|
||||||
@@ -482,8 +625,7 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
setState(() => _fidelityStructure = v);
|
setState(() => _fidelityStructure = v);
|
||||||
_onStyleChanged();
|
_onStyleChanged();
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
_SliderRow(
|
_SliderRow(
|
||||||
label: 'Dithering strength',
|
label: 'Dithering strength',
|
||||||
leftLabel: 'Off',
|
leftLabel: 'Off',
|
||||||
@@ -492,8 +634,7 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
setState(() => _ditheringStrength = v);
|
setState(() => _ditheringStrength = v);
|
||||||
_onStyleChanged();
|
_onStyleChanged();
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
_SliderRow(
|
_SliderRow(
|
||||||
label: 'Edge emphasis',
|
label: 'Edge emphasis',
|
||||||
leftLabel: 'Soft',
|
leftLabel: 'Soft',
|
||||||
@@ -502,8 +643,7 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
setState(() => _edgeEmphasis = v);
|
setState(() => _edgeEmphasis = v);
|
||||||
_onStyleChanged();
|
_onStyleChanged();
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
_SliderRow(
|
_SliderRow(
|
||||||
label: 'Color tolerance / variation',
|
label: 'Color tolerance / variation',
|
||||||
leftLabel: 'Strict',
|
leftLabel: 'Strict',
|
||||||
@@ -512,8 +652,7 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
setState(() => _colorVariation = v);
|
setState(() => _colorVariation = v);
|
||||||
_onStyleChanged();
|
_onStyleChanged();
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -522,65 +661,59 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Expanded(
|
const Expanded(
|
||||||
child: Text(
|
child: Text('Cap Catalog',
|
||||||
'Cap Palette',
|
style: TextStyle(
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
fontSize: 18, fontWeight: FontWeight.bold))),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => setState(
|
||||||
|
() => _catalogViewMode = CatalogViewMode.list),
|
||||||
|
icon: Icon(Icons.view_list,
|
||||||
|
color: _catalogViewMode == CatalogViewMode.list
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: null),
|
||||||
|
tooltip: 'Listenansicht',
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => setState(
|
||||||
|
() => _catalogViewMode = CatalogViewMode.grid),
|
||||||
|
icon: Icon(Icons.grid_view,
|
||||||
|
color: _catalogViewMode == CatalogViewMode.grid
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: null),
|
||||||
|
tooltip: 'Rasteransicht',
|
||||||
),
|
),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: _captureCapPhoto,
|
onPressed: _captureCapPhoto,
|
||||||
icon: const Icon(Icons.photo_camera_outlined),
|
icon: const Icon(Icons.photo_camera_outlined),
|
||||||
label: const Text('Deckel fotografieren'),
|
label: const Text('Foto')),
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: _addCapDialog,
|
onPressed: _addCapDialog,
|
||||||
icon: const Icon(Icons.add_circle_outline),
|
icon: const Icon(Icons.add_circle_outline),
|
||||||
tooltip: 'Farbe manuell hinzufügen',
|
tooltip: 'Manuell hinzufügen'),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: [
|
|
||||||
for (int i = 0; i < _palette.length; i++)
|
|
||||||
Chip(
|
|
||||||
avatar: CircleAvatar(backgroundColor: _palette[i].color),
|
|
||||||
label: Text(_palette[i].name),
|
|
||||||
onDeleted: _palette.length <= 1
|
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
setState(() => _palette.removeAt(i));
|
|
||||||
_scheduleRegenerate();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildCatalogView(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (_result != null) ...[
|
if (_result != null) ...[
|
||||||
Text(
|
Text('Preview (${_result!.width} x ${_result!.height})',
|
||||||
'Preview (${_result!.width} x ${_result!.height})',
|
style: const TextStyle(
|
||||||
style:
|
fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
RepaintBoundary(
|
RepaintBoundary(
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: _result!.width / _result!.height,
|
aspectRatio: _result!.width / _result!.height,
|
||||||
child: Image.memory(
|
child: Image.memory(_result!.previewPng,
|
||||||
_result!.previewPng,
|
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
filterQuality: FilterQuality.none,
|
filterQuality: FilterQuality.none,
|
||||||
gaplessPlayback: true,
|
gaplessPlayback: true),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text(
|
const Text('Bill of Materials',
|
||||||
'Bill of Materials',
|
style: TextStyle(
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
),
|
|
||||||
..._result!.sortedCounts.map(
|
..._result!.sortedCounts.map(
|
||||||
(e) => ListTile(
|
(e) => ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
@@ -596,6 +729,92 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildCatalogView() {
|
||||||
|
if (_catalog.isEmpty) return const Text('Noch keine Deckel im Katalog');
|
||||||
|
|
||||||
|
if (_catalogViewMode == CatalogViewMode.list) {
|
||||||
|
return Column(
|
||||||
|
children: _catalog
|
||||||
|
.map(
|
||||||
|
(entry) => Card(
|
||||||
|
child: ListTile(
|
||||||
|
leading: _CapThumb(entry: entry),
|
||||||
|
title: Text(entry.name),
|
||||||
|
subtitle: Text(_colorToHex(entry.color)),
|
||||||
|
trailing: Wrap(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
CircleAvatar(radius: 10, backgroundColor: entry.color),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _editEntry(entry),
|
||||||
|
icon: const Icon(Icons.edit_outlined)),
|
||||||
|
IconButton(
|
||||||
|
onPressed: _catalog.length <= 1
|
||||||
|
? null
|
||||||
|
: () => _deleteEntry(entry),
|
||||||
|
icon: const Icon(Icons.delete_outline),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: _catalog.length,
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
childAspectRatio: 1.2,
|
||||||
|
mainAxisSpacing: 8,
|
||||||
|
crossAxisSpacing: 8,
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final entry = _catalog[index];
|
||||||
|
return Card(
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => _editEntry(entry),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(child: _CapThumb(entry: entry, large: true)),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(entry.name,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(radius: 8, backgroundColor: entry.color),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(_colorToHex(entry.color),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
onPressed: _catalog.length <= 1
|
||||||
|
? null
|
||||||
|
: () => _deleteEntry(entry),
|
||||||
|
icon: const Icon(Icons.delete_outline),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Color? _parseHex(String raw) {
|
Color? _parseHex(String raw) {
|
||||||
final hex = raw.trim().replaceFirst('#', '');
|
final hex = raw.trim().replaceFirst('#', '');
|
||||||
if (hex.length != 6) return null;
|
if (hex.length != 6) return null;
|
||||||
@@ -609,6 +828,36 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CapThumb extends StatelessWidget {
|
||||||
|
final CapCatalogEntry entry;
|
||||||
|
final bool large;
|
||||||
|
|
||||||
|
const _CapThumb({required this.entry, this.large = false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final size = large ? double.infinity : 42.0;
|
||||||
|
final radius = BorderRadius.circular(large ? 12 : 8);
|
||||||
|
|
||||||
|
if (entry.imagePath != null && File(entry.imagePath!).existsSync()) {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: radius,
|
||||||
|
child: Image.file(File(entry.imagePath!),
|
||||||
|
width: size, height: size, fit: BoxFit.cover),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: entry.color,
|
||||||
|
borderRadius: radius,
|
||||||
|
border: Border.all(color: Colors.black12)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _SliderRow extends StatelessWidget {
|
class _SliderRow extends StatelessWidget {
|
||||||
final String label;
|
final String label;
|
||||||
final String leftLabel;
|
final String leftLabel;
|
||||||
@@ -646,6 +895,46 @@ class _SliderRow extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CapCatalogEntry {
|
||||||
|
final String id;
|
||||||
|
String name;
|
||||||
|
int colorValue;
|
||||||
|
String? imagePath;
|
||||||
|
|
||||||
|
CapCatalogEntry(
|
||||||
|
{required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.colorValue,
|
||||||
|
this.imagePath});
|
||||||
|
|
||||||
|
factory CapCatalogEntry.newEntry(
|
||||||
|
{required String name, required Color color, String? imagePath}) {
|
||||||
|
return CapCatalogEntry(
|
||||||
|
id: DateTime.now().microsecondsSinceEpoch.toString(),
|
||||||
|
name: name,
|
||||||
|
colorValue: color.toARGB32(),
|
||||||
|
imagePath: imagePath,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color get color => Color(colorValue);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'colorValue': colorValue,
|
||||||
|
'imagePath': imagePath,
|
||||||
|
};
|
||||||
|
|
||||||
|
factory CapCatalogEntry.fromJson(Map<String, dynamic> json) =>
|
||||||
|
CapCatalogEntry(
|
||||||
|
id: json['id'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
colorValue: json['colorValue'] as int,
|
||||||
|
imagePath: json['imagePath'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class CapColor {
|
class CapColor {
|
||||||
final String name;
|
final String name;
|
||||||
final Color color;
|
final Color color;
|
||||||
@@ -693,9 +982,8 @@ Map<String, dynamic> _generateMosaicIsolate(Map<String, dynamic> request) {
|
|||||||
'assignments': <int>[0],
|
'assignments': <int>[0],
|
||||||
'counts': <int>[1],
|
'counts': <int>[1],
|
||||||
'palette': paletteRaw,
|
'palette': paletteRaw,
|
||||||
'previewPng': Uint8List.fromList(
|
'previewPng':
|
||||||
img.encodePng(img.Image(width: 1, height: 1)),
|
Uint8List.fromList(img.encodePng(img.Image(width: 1, height: 1))),
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,12 +997,8 @@ Map<String, dynamic> _generateMosaicIsolate(Map<String, dynamic> request) {
|
|||||||
final interpolation = fidelityStructure < 0.4
|
final interpolation = fidelityStructure < 0.4
|
||||||
? img.Interpolation.average
|
? img.Interpolation.average
|
||||||
: img.Interpolation.linear;
|
: img.Interpolation.linear;
|
||||||
final scaled = img.copyResize(
|
final scaled = img.copyResize(decoded,
|
||||||
decoded,
|
width: gridW, height: gridH, interpolation: interpolation);
|
||||||
width: gridW,
|
|
||||||
height: gridH,
|
|
||||||
interpolation: interpolation,
|
|
||||||
);
|
|
||||||
|
|
||||||
final pixelCount = gridW * gridH;
|
final pixelCount = gridW * gridH;
|
||||||
final workingR = List<double>.filled(pixelCount, 0);
|
final workingR = List<double>.filled(pixelCount, 0);
|
||||||
@@ -897,13 +1181,7 @@ Map<String, dynamic> _generateMosaicIsolate(Map<String, dynamic> request) {
|
|||||||
final idx = assignments[y * gridW + x];
|
final idx = assignments[y * gridW + x];
|
||||||
final argb = paletteValues[idx];
|
final argb = paletteValues[idx];
|
||||||
preview.setPixelRgba(
|
preview.setPixelRgba(
|
||||||
x,
|
x, y, (argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF, 255);
|
||||||
y,
|
|
||||||
(argb >> 16) & 0xFF,
|
|
||||||
(argb >> 8) & 0xFF,
|
|
||||||
argb & 0xFF,
|
|
||||||
255,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -923,21 +1201,15 @@ Map<String, dynamic> _extractCapFromCenterIsolate(Uint8List sourceBytes) {
|
|||||||
return {
|
return {
|
||||||
'color': Colors.orange.toARGB32(),
|
'color': Colors.orange.toARGB32(),
|
||||||
'previewPng': Uint8List.fromList(
|
'previewPng': Uint8List.fromList(
|
||||||
img.encodePng(img.Image(width: 1, height: 1), level: 1),
|
img.encodePng(img.Image(width: 1, height: 1), level: 1)),
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
final cropSize = math.min(decoded.width, decoded.height);
|
final cropSize = math.min(decoded.width, decoded.height);
|
||||||
final startX = (decoded.width - cropSize) ~/ 2;
|
final startX = (decoded.width - cropSize) ~/ 2;
|
||||||
final startY = (decoded.height - cropSize) ~/ 2;
|
final startY = (decoded.height - cropSize) ~/ 2;
|
||||||
final centered = img.copyCrop(
|
final centered = img.copyCrop(decoded,
|
||||||
decoded,
|
x: startX, y: startY, width: cropSize, height: cropSize);
|
||||||
x: startX,
|
|
||||||
y: startY,
|
|
||||||
width: cropSize,
|
|
||||||
height: cropSize,
|
|
||||||
);
|
|
||||||
|
|
||||||
final analysisSize = centered.width > 420
|
final analysisSize = centered.width > 420
|
||||||
? img.copyResize(centered, width: 420, height: 420)
|
? img.copyResize(centered, width: 420, height: 420)
|
||||||
@@ -985,19 +1257,16 @@ Map<String, dynamic> _extractCapFromCenterIsolate(Uint8List sourceBytes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dominant == null || dominant.count < included * 0.08) {
|
if (dominant == null || dominant.count < included * 0.08) {
|
||||||
resultArgb = Color.fromARGB(
|
resultArgb = Color.fromARGB(255, (sumR / included).round(),
|
||||||
255,
|
(sumG / included).round(), (sumB / included).round())
|
||||||
(sumR / included).round(),
|
.toARGB32();
|
||||||
(sumG / included).round(),
|
|
||||||
(sumB / included).round(),
|
|
||||||
).toARGB32();
|
|
||||||
} else {
|
} else {
|
||||||
resultArgb = Color.fromARGB(
|
resultArgb = Color.fromARGB(
|
||||||
255,
|
255,
|
||||||
(dominant.r / dominant.count).round(),
|
(dominant.r / dominant.count).round(),
|
||||||
(dominant.g / dominant.count).round(),
|
(dominant.g / dominant.count).round(),
|
||||||
(dominant.b / dominant.count).round(),
|
(dominant.b / dominant.count).round())
|
||||||
).toARGB32();
|
.toARGB32();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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_picker: ^1.1.2
|
||||||
image: ^4.2.0
|
image: ^4.2.0
|
||||||
flutter_colorpicker: ^1.1.0
|
flutter_colorpicker: ^1.1.0
|
||||||
|
path_provider: ^2.1.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user