Kaynağa Gözat

flutter enhancements

Dr-Swopt 4 gün önce
ebeveyn
işleme
ca2e7cf817

+ 1 - 1
palm_oil_mobile/README.md

@@ -1,4 +1,4 @@
-# 🌴 Palm Oil Ripeness AI - Mobile App (YOLO26 Edition)
+# 🌴 Palm Oil Ripeness AI (YOLO26 Mobile)
 
 A professional, high-performance Flutter application powered by the **YOLO26 (January 2026)** architecture. Designed for palm oil plantation managers, this app utilizes **NMS-Free End-to-End** detection for maximum efficiency in the field.
 

+ 8 - 29
palm_oil_mobile/lib/screens/analysis_screen.dart

@@ -6,6 +6,7 @@ import 'package:path/path.dart' as p;
 import '../services/tflite_service.dart';
 import '../services/database_helper.dart';
 import '../models/palm_record.dart';
+import '../widgets/palm_bounding_box.dart';
 
 class AnalysisScreen extends StatefulWidget {
   const AnalysisScreen({super.key});
@@ -167,7 +168,12 @@ class _AnalysisScreenState extends State<AnalysisScreen> with SingleTickerProvid
                                       builder: (context, constraints) {
                                         return Stack(
                                           children: _detections!
-                                              .map((d) => _buildBoundingBox(d, constraints))
+                                              .map((d) => PalmBoundingBox(
+                                                    normalizedRect: d.normalizedBox,
+                                                    label: d.className,
+                                                    confidence: d.confidence,
+                                                    constraints: constraints,
+                                                  ))
                                               .toList(),
                                         );
                                       },
@@ -204,34 +210,7 @@ class _AnalysisScreenState extends State<AnalysisScreen> with SingleTickerProvid
     );
   }
 
-  Widget _buildBoundingBox(DetectionResult detection, BoxConstraints constraints) {
-    final rect = detection.normalizedBox;
-    final color = detection.getStatusColor();
-
-    return Positioned(
-      left: rect.left * constraints.maxWidth,
-      top: rect.top * constraints.maxHeight,
-      width: rect.width * constraints.maxWidth,
-      height: rect.height * constraints.maxHeight,
-      child: Container(
-        decoration: BoxDecoration(
-          border: Border.all(color: color, width: 3),
-          borderRadius: BorderRadius.circular(4),
-        ),
-        child: Align(
-          alignment: Alignment.topLeft,
-          child: Container(
-            padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
-            color: color,
-            child: Text(
-              "${detection.className} ${(detection.confidence * 100).toStringAsFixed(0)}%",
-              style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold),
-            ),
-          ),
-        ),
-      ),
-    );
-  }
+  // Bounding box logic replaced by shared PalmBoundingBox widget
 
   // Removed _getStatusColor local method in favor of DetectionResult.getStatusColor()
 

+ 6 - 13
palm_oil_mobile/lib/screens/history_screen.dart

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
 import 'package:path/path.dart' as p;
 import '../services/database_helper.dart';
 import '../models/palm_record.dart';
+import '../widgets/palm_bounding_box.dart';
 
 /// Simple detection data parsed from the stored Map in the database.
 class _DetectionData {
@@ -201,19 +202,11 @@ class _HistoryScreenState extends State<HistoryScreen> {
                                       builder: (context, constraints) {
                                         return Stack(
                                           children: detections.map<Widget>((d) {
-                                            final rect = d.normalizedBox;
-                                            final color = _getStatusColor(d.className);
-                                            return Positioned(
-                                              left: rect.left * constraints.maxWidth,
-                                              top: rect.top * constraints.maxHeight,
-                                              width: rect.width * constraints.maxWidth,
-                                              height: rect.height * constraints.maxHeight,
-                                              child: Container(
-                                                decoration: BoxDecoration(
-                                                  border: Border.all(color: color, width: 2),
-                                                  borderRadius: BorderRadius.circular(4),
-                                                ),
-                                              ),
+                                            return PalmBoundingBox(
+                                              normalizedRect: d.normalizedBox,
+                                              label: d.className,
+                                              confidence: d.confidence,
+                                              constraints: constraints,
                                             );
                                           }).toList(),
                                         );

+ 11 - 34
palm_oil_mobile/lib/screens/live_analysis_screen.dart

@@ -9,6 +9,7 @@ import 'package:path/path.dart' as p;
 import '../services/tflite_service.dart';
 import '../services/database_helper.dart';
 import '../models/palm_record.dart';
+import '../widgets/palm_bounding_box.dart';
 
 enum DetectionState { searching, locking, capturing, cooldown }
 
@@ -57,7 +58,7 @@ class _LiveAnalysisScreenState extends State<LiveAnalysisScreen> {
 
     _controller = CameraController(
       cameras[0],
-      ResolutionPreset.low, // Downgraded resolution for performance
+      ResolutionPreset.medium, // Upgraded resolution for YOLO26 performance
       enableAudio: false,
       imageFormatGroup: Platform.isAndroid ? ImageFormatGroup.yuv420 : ImageFormatGroup.bgra8888,
     );
@@ -149,10 +150,10 @@ class _LiveAnalysisScreenState extends State<LiveAnalysisScreen> {
       if (momentumTicks < 0) momentumTicks = 0;
       
       setState(() {
-        _lockProgress = (momentumTicks / 3.0).clamp(0.0, 1.0); // 3 ticks target
+        _lockProgress = (momentumTicks / 2.0).clamp(0.0, 1.0); // 2 ticks target
       });
       
-      if (momentumTicks >= 3) {
+      if (momentumTicks >= 2) {
         timer.cancel();
         if (_state == DetectionState.locking) {
           _triggerCapture();
@@ -340,7 +341,13 @@ class _LiveAnalysisScreenState extends State<LiveAnalysisScreen> {
                 builder: (context, constraints) {
                   return Stack(
                     children: _detections!
-                        .map((d) => _buildOverlayBox(d, constraints))
+                        .map((d) => PalmBoundingBox(
+                              normalizedRect: d.normalizedBox,
+                              label: d.className,
+                              confidence: d.confidence,
+                              constraints: constraints,
+                              isLocked: (_state == DetectionState.locking || _state == DetectionState.capturing) && d.confidence > _lockThreshold,
+                            ))
                         .toList(),
                   );
                 },
@@ -459,36 +466,6 @@ class _LiveAnalysisScreenState extends State<LiveAnalysisScreen> {
     );
   }
 
-  Widget _buildOverlayBox(DetectionResult detection, BoxConstraints constraints) {
-    final rect = detection.normalizedBox;
-    // Show green only if the system is overall "Locked" and this detection is high confidence
-    final color = ((_state == DetectionState.locking || _state == DetectionState.capturing) && detection.confidence > _lockThreshold) ? Colors.green : Colors.yellow;
-
-    return Positioned(
-      left: rect.left * constraints.maxWidth,
-      top: rect.top * constraints.maxHeight,
-      width: rect.width * constraints.maxWidth,
-      height: rect.height * constraints.maxHeight,
-      child: Container(
-        decoration: BoxDecoration(
-          border: Border.all(color: color, width: 2),
-          borderRadius: BorderRadius.circular(4),
-        ),
-        child: Align(
-          alignment: Alignment.topLeft,
-          child: Container(
-            padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
-            color: color,
-            child: Text(
-              "${(detection.confidence * 100).toStringAsFixed(0)}%",
-              style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold),
-            ),
-          ),
-        ),
-      ),
-    );
-  }
-
   @override
   void dispose() {
     _lockTimer?.cancel();

+ 72 - 0
palm_oil_mobile/lib/widgets/palm_bounding_box.dart

@@ -0,0 +1,72 @@
+import 'package:flutter/material.dart';
+
+/// A shared widget for displaying MPOB-standard bounding boxes.
+class PalmBoundingBox extends StatelessWidget {
+  final Rect normalizedRect;
+  final String label;
+  final double confidence;
+  final BoxConstraints constraints;
+  final bool isLocked;
+
+  const PalmBoundingBox({
+    super.key,
+    required this.normalizedRect,
+    required this.label,
+    required this.confidence,
+    required this.constraints,
+    this.isLocked = false,
+  });
+
+  /// MPOB-standard color palette
+  Color _getMPOBColor(String label) {
+    if (isLocked) return const Color(0xFF22C55E); // Force Green when locked
+    
+    switch (label) {
+      case 'Ripe':
+        return const Color(0xFF22C55E); // Industrial Green
+      case 'Underripe':
+        return const Color(0xFFFBBF24); // Industrial Orange/Yellow
+      case 'Unripe':
+        return const Color(0xFF3B82F6); // Industrial Blue
+      case 'Abnormal':
+        return const Color(0xFFDC2626); // Critical Red
+      case 'Empty_Bunch':
+        return const Color(0xFF64748B); // Waste Gray
+      default:
+        return Colors.yellow;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final color = _getMPOBColor(label);
+
+    return Positioned(
+      left: normalizedRect.left * constraints.maxWidth,
+      top: normalizedRect.top * constraints.maxHeight,
+      width: normalizedRect.width * constraints.maxWidth,
+      height: normalizedRect.height * constraints.maxHeight,
+      child: Container(
+        decoration: BoxDecoration(
+          border: Border.all(color: color, width: 3),
+          borderRadius: BorderRadius.circular(4),
+        ),
+        child: Align(
+          alignment: Alignment.topLeft,
+          child: Container(
+            padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
+            color: color,
+            child: Text(
+              "$label ${(confidence * 100).toStringAsFixed(0)}%",
+              style: const TextStyle(
+                color: Colors.white,
+                fontSize: 10,
+                fontWeight: FontWeight.bold,
+              ),
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}