Fix cap photo flow to always open review dialog with fallback
This commit is contained in:
113
lib/main.dart
113
lib/main.dart
@@ -154,10 +154,48 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
||||
imageQuality: 95,
|
||||
maxWidth: 1800,
|
||||
);
|
||||
if (captured == null) return;
|
||||
if (captured == null) {
|
||||
debugPrint('[cap-photo] Capture cancelled by user.');
|
||||
return;
|
||||
}
|
||||
|
||||
Uint8List bytes;
|
||||
try {
|
||||
bytes = await captured.readAsBytes();
|
||||
} catch (e, st) {
|
||||
debugPrint('[cap-photo] Failed to read captured bytes: $e\n$st');
|
||||
final fallbackImage = img.Image(width: 1, height: 1)
|
||||
..setPixelRgb(0, 0, 255, 152, 0);
|
||||
bytes = Uint8List.fromList(
|
||||
img.encodePng(fallbackImage, level: 1),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> detected;
|
||||
String? detectionWarning;
|
||||
try {
|
||||
detected = await compute(_extractCapFromPhotoIsolate, bytes);
|
||||
} catch (e, st) {
|
||||
debugPrint('[cap-photo] Detection isolate failed: $e\n$st');
|
||||
detectionWarning =
|
||||
'⚠️ Farberkennung ist fehlgeschlagen. Fallback-Farbe wird verwendet; bitte prüfen/korrigieren.';
|
||||
final decoded = img.decodeImage(bytes);
|
||||
final imageW = (decoded?.width ?? 1).toDouble();
|
||||
final imageH = (decoded?.height ?? 1).toDouble();
|
||||
final fallbackColor = Colors.orange.toARGB32();
|
||||
detected = {
|
||||
'dominantColor': fallbackColor,
|
||||
'averageColor': fallbackColor,
|
||||
'usedFallback': true,
|
||||
'circleX': imageW / 2,
|
||||
'circleY': imageH / 2,
|
||||
'circleR': math.min(imageW, imageH) * 0.28,
|
||||
'imageW': imageW,
|
||||
'imageH': imageH,
|
||||
'previewPng': bytes,
|
||||
};
|
||||
}
|
||||
|
||||
final bytes = await captured.readAsBytes();
|
||||
final detected = await compute(_extractCapFromPhotoIsolate, bytes);
|
||||
if (!mounted) return;
|
||||
|
||||
Color dominantColor = Color(detected['dominantColor'] as int);
|
||||
@@ -201,23 +239,35 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
||||
if (updateDialog) {
|
||||
setDialogState(() {});
|
||||
}
|
||||
final adjusted = await compute(
|
||||
_extractCapFromAdjustedCircleIsolate,
|
||||
<String, dynamic>{
|
||||
'sourceBytes': bytes,
|
||||
'circleX': circleX,
|
||||
'circleY': circleY,
|
||||
'circleR': circleR,
|
||||
},
|
||||
);
|
||||
if (localToken != recalcToken) return;
|
||||
dominantColor = Color(adjusted['dominantColor'] as int);
|
||||
averageColor = Color(adjusted['averageColor'] as int);
|
||||
previewBytes = adjusted['previewPng'] as Uint8List;
|
||||
selected = mode == ColorExtractionMode.dominant
|
||||
? dominantColor
|
||||
: averageColor;
|
||||
_photoCapHexCtrl.text = _colorToHex(selected);
|
||||
try {
|
||||
final adjusted = await compute(
|
||||
_extractCapFromAdjustedCircleIsolate,
|
||||
<String, dynamic>{
|
||||
'sourceBytes': bytes,
|
||||
'circleX': circleX,
|
||||
'circleY': circleY,
|
||||
'circleR': circleR,
|
||||
},
|
||||
);
|
||||
if (localToken != recalcToken) return;
|
||||
dominantColor = Color(adjusted['dominantColor'] as int);
|
||||
averageColor = Color(adjusted['averageColor'] as int);
|
||||
previewBytes = adjusted['previewPng'] as Uint8List;
|
||||
selected = mode == ColorExtractionMode.dominant
|
||||
? dominantColor
|
||||
: averageColor;
|
||||
_photoCapHexCtrl.text = _colorToHex(selected);
|
||||
} catch (e, st) {
|
||||
debugPrint('[cap-photo] Recalculate failed: $e\n$st');
|
||||
if (localToken != recalcToken) return;
|
||||
detectionWarning =
|
||||
'⚠️ Aktualisierung fehlgeschlagen. Bitte Farbe manuell prüfen.';
|
||||
const fallback = Colors.orange;
|
||||
dominantColor = fallback;
|
||||
averageColor = fallback;
|
||||
selected = fallback;
|
||||
_photoCapHexCtrl.text = _colorToHex(selected);
|
||||
}
|
||||
if (ctx.mounted) {
|
||||
setDialogState(() {});
|
||||
}
|
||||
@@ -274,6 +324,16 @@ class _MosaicHomePageState extends State<MosaicHomePage> {
|
||||
: 'Kreis erkannt. Direkt im Foto ziehen oder mit Pinch/Slider anpassen.',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
if (detectionWarning != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
detectionWarning!,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
SegmentedButton<ColorExtractionMode>(
|
||||
segments: const [
|
||||
@@ -1094,8 +1154,8 @@ class _CircleAdjustOverlayState extends State<_CircleAdjustOverlay> {
|
||||
final x = (local.dx / maxW).clamp(0.0, 1.0);
|
||||
final y = (local.dy / shownH).clamp(0.0, 1.0);
|
||||
final scale = details.scale;
|
||||
final r = ((_baseRadius ?? widget.circleR) * scale)
|
||||
.clamp(0.06, 0.49);
|
||||
final r =
|
||||
((_baseRadius ?? widget.circleR) * scale).clamp(0.06, 0.49);
|
||||
widget.onCircleChanged(x, y, r);
|
||||
},
|
||||
onScaleEnd: (_) => _baseRadius = null,
|
||||
@@ -1125,7 +1185,8 @@ class _CircleOverlayPainter extends CustomPainter {
|
||||
final double y;
|
||||
final double r;
|
||||
|
||||
const _CircleOverlayPainter({required this.x, required this.y, required this.r});
|
||||
const _CircleOverlayPainter(
|
||||
{required this.x, required this.y, required this.r});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
@@ -1154,8 +1215,10 @@ class _CircleOverlayPainter extends CustomPainter {
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1
|
||||
..color = Colors.white70;
|
||||
canvas.drawLine(Offset(center.dx - 10, center.dy), Offset(center.dx + 10, center.dy), cross);
|
||||
canvas.drawLine(Offset(center.dx, center.dy - 10), Offset(center.dx, center.dy + 10), cross);
|
||||
canvas.drawLine(Offset(center.dx - 10, center.dy),
|
||||
Offset(center.dx + 10, center.dy), cross);
|
||||
canvas.drawLine(Offset(center.dx, center.dy - 10),
|
||||
Offset(center.dx, center.dy + 10), cross);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user