import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../../core/app_theme.dart'; import '../providers/monitoring_provider.dart'; import '../widgets/isometric_transform.dart'; import '../widgets/isometric_kumbung_painter.dart'; class KumbungMapPage extends ConsumerStatefulWidget { const KumbungMapPage({super.key}); @override ConsumerState createState() => _KumbungMapPageState(); } class _KumbungMapPageState extends ConsumerState with SingleTickerProviderStateMixin { late AnimationController _animationController; // Koordinat Sensor (X, Y, Z) dalam meter, dan Radius Jangkauan (meter) double _sensorX = 4.0; double _sensorY = 3.0; final double _sensorZ = 1.2; // Tinggi melayang sensor di rak double _sensorRadius = 3.0; // Mode Heatmap: true = Suhu, false = Kelembapan bool _isSuhuMode = true; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(seconds: 2), )..repeat(); _loadSensorSettings(); } @override void dispose() { _animationController.dispose(); super.dispose(); } // Memuat setelan koordinat lokal dari SharedPreferences Future _loadSensorSettings() async { final prefs = await SharedPreferences.getInstance(); setState(() { _sensorX = prefs.getDouble('sensor_x') ?? 4.0; _sensorY = prefs.getDouble('sensor_y') ?? 3.0; _sensorRadius = prefs.getDouble('sensor_radius') ?? 3.0; }); } // Menyimpan setelan koordinat lokal ke SharedPreferences Future _saveSensorSettings() async { final prefs = await SharedPreferences.getInstance(); await prefs.setDouble('sensor_x', _sensorX); await prefs.setDouble('sensor_y', _sensorY); await prefs.setDouble('sensor_radius', _sensorRadius); } // Mengubah posisi sensor saat canvas diketuk oleh pengguna void _handleCanvasTap(Offset localPosition, Size canvasSize, IsometricTransform transformer) { final worldPt = transformer.toWorldGround(localPosition); // Batasi agar koordinat berada di dalam rentang ruangan (8m x 6m) final double clampedX = worldPt.dx.clamp(0.0, 8.0); final double clampedY = worldPt.dy.clamp(0.0, 6.0); setState(() { _sensorX = clampedX; _sensorY = clampedY; }); _saveSensorSettings(); } @override Widget build(BuildContext context) { final monitoring = ref.watch(monitoringProvider); final hasDevice = monitoring.latest != null; // Nilai telemetri sensor real-time final double tempVal = monitoring.temperature; final double humidVal = monitoring.humidity; // Tentukan warna status untuk sensor berdasarkan nilainya final Color pinColor = _isSuhuMode ? (tempVal > 30 ? Colors.redAccent : Colors.teal) : (humidVal < 75 ? Colors.orangeAccent : Colors.cyan); return Scaffold( backgroundColor: AppTheme.bgPage, body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── Header Section ───────────────────────────────── Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Peta Kumbung', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), Text( 'Denah & Cakupan Jangkauan Sensor', style: TextStyle( fontSize: 13, color: AppTheme.textSecondary, ), ), ], ), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: hasDevice ? Colors.green.shade50 : Colors.grey.shade100, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ CircleAvatar( radius: 4, backgroundColor: hasDevice ? Colors.green : Colors.grey, ), const SizedBox(width: 6), Text( hasDevice ? 'Sensor Aktif' : 'Sensor Offline', style: TextStyle( fontSize: 11, fontWeight: FontWeight.bold, color: hasDevice ? Colors.green.shade700 : Colors.grey.shade700, ), ), ], ), ), ], ), const SizedBox(height: 16), // ── Visualizer Canvas 3D ─────────────────────────── Container( width: double.infinity, height: 280, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 16, offset: const Offset(0, 4), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(24), child: Stack( children: [ // Canvas Utama Rendering Isometrik LayoutBuilder( builder: (context, constraints) { // Menghitung origin tengah bawah untuk visualisasi isometrik final centerOrigin = Offset( constraints.maxWidth / 2, constraints.maxHeight / 2 + 30, ); // Skala dinamis: 1 meter = 24 piksel final transformer = IsometricTransform( scale: 24.0, origin: centerOrigin, ); return GestureDetector( onTapUp: (details) => _handleCanvasTap( details.localPosition, Size(constraints.maxWidth, constraints.maxHeight), transformer, ), child: AnimatedBuilder( animation: _animationController, builder: (context, child) { return CustomPaint( size: Size(constraints.maxWidth, constraints.maxHeight), painter: StaticKumbungPainter( transformer: transformer, roomWidth: 6.0, roomLength: 8.0, roomHeight: 2.5, ), foregroundPainter: PinKumbungPainter( transformer: transformer, sensorPos: Offset(_sensorX, _sensorY), sensorHeight: _sensorZ, value: _isSuhuMode ? tempVal : humidVal, unit: _isSuhuMode ? '°C' : '%', animationValue: _animationController.value, pinColor: pinColor, ), child: CustomPaint( size: Size(constraints.maxWidth, constraints.maxHeight), painter: HeatmapKumbungPainter( transformer: transformer, sensorPos: Offset(_sensorX, _sensorY), radius: _sensorRadius, value: _isSuhuMode ? tempVal : humidVal, isSuhuMode: _isSuhuMode, ), ), ); }, ), ); }, ), // Toggle Switch Peta Suhu / Kelembapan Positioned( top: 12, right: 12, child: Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(16), ), child: Row( children: [ _buildToggleButton( label: 'Suhu', isActive: _isSuhuMode, color: Colors.teal, onTap: () => setState(() => _isSuhuMode = true), ), _buildToggleButton( label: 'Kelembapan', isActive: !_isSuhuMode, color: Colors.cyan, onTap: () => setState(() => _isSuhuMode = false), ), ], ), ), ), // Instruksi Interaksi const Positioned( bottom: 12, left: 12, child: Row( children: [ Icon(Icons.touch_app, size: 14, color: AppTheme.textSecondary), SizedBox(width: 4), Text( 'Ketuk lantai grid untuk memindah sensor', style: TextStyle( fontSize: 10, color: AppTheme.textSecondary, ), ), ], ), ), ], ), ), ), const SizedBox(height: 16), // ── Control Settings Panel ───────────────────────── Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.02), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.settings_input_hdmi, color: AppTheme.primary, size: 18), const SizedBox(width: 8), Text( 'Konfigurasi Sensor Terpasang', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.grey.shade800, ), ), ], ), const SizedBox(height: 12), _buildSlider( label: 'Posisi X (Panjang)', value: _sensorX, min: 0.5, max: 7.5, displayValue: '${_sensorX.toStringAsFixed(1)}m', onChanged: (v) { setState(() => _sensorX = v); _saveSensorSettings(); }, ), _buildSlider( label: 'Posisi Y (Lebar)', value: _sensorY, min: 0.5, max: 5.5, displayValue: '${_sensorY.toStringAsFixed(1)}m', onChanged: (v) { setState(() => _sensorY = v); _saveSensorSettings(); }, ), _buildSlider( label: 'Radius Jangkauan', value: _sensorRadius, min: 1.0, max: 5.0, displayValue: '${_sensorRadius.toStringAsFixed(1)}m', onChanged: (v) { setState(() => _sensorRadius = v); _saveSensorSettings(); }, ), ], ), ), const SizedBox(height: 16), // ── Dashboard Referensi Budidaya ─────────────────── _buildReferenceCard(), ], ), ), ), ); } // Widget Button Toggle Widget _buildToggleButton({ required String label, required bool isActive, required Color color, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: isActive ? color : Colors.transparent, borderRadius: BorderRadius.circular(12), ), child: Text( label, style: TextStyle( fontSize: 11, fontWeight: FontWeight.bold, color: isActive ? Colors.white : AppTheme.textSecondary, ), ), ), ); } // Widget Row Slider Pengaturan Widget _buildSlider({ required String label, required double value, required double min, required double max, required String displayValue, required ValueChanged onChanged, }) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( children: [ Expanded( flex: 3, child: Text( label, style: const TextStyle(fontSize: 12, color: AppTheme.textSecondary), ), ), Expanded( flex: 6, child: SliderTheme( data: SliderTheme.of(context).copyWith( activeTrackColor: AppTheme.primary, inactiveTrackColor: Colors.grey.shade200, thumbColor: AppTheme.primary, trackHeight: 3, thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 6), ), child: Slider( value: value, min: min, max: max, onChanged: onChanged, ), ), ), Expanded( flex: 2, child: Text( displayValue, textAlign: TextAlign.right, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), ), ], ), ); } // Widget Kartu Referensi Budidaya Jamur Widget _buildReferenceCard() { return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.02), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: AppTheme.primary.withOpacity(0.1), shape: BoxShape.circle, ), child: const Icon(Icons.menu_book, color: AppTheme.primary, size: 16), ), const SizedBox(width: 10), const Text( 'Panduan Referensi Budidaya', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), ], ), const SizedBox(height: 12), const Text( 'Untuk budidaya Jamur Tiram yang optimal, berikut adalah parameter referensi kondisi kumbung:', style: TextStyle(fontSize: 12, color: AppTheme.textSecondary, height: 1.4), ), const SizedBox(height: 12), Row( children: [ Expanded( child: _buildRefIndicator( icon: Icons.thermostat, iconColor: Colors.teal, label: 'Suhu Ideal', value: '22°C - 28°C', ), ), const SizedBox(width: 12), Expanded( child: _buildRefIndicator( icon: Icons.water_drop, iconColor: Colors.cyan, label: 'Kelembapan Ideal', value: '80% - 90%', ), ), ], ), const SizedBox(height: 12), Divider(color: Colors.grey.shade100), const SizedBox(height: 8), const Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.wb_sunny_outlined, size: 16, color: Colors.orange), SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Waktu Penyiraman Terbaik:', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: AppTheme.textPrimary, ), ), SizedBox(height: 4), Text( '• Pagi Hari (06.00 - 08.00 WIB): Mempersiapkan kelembapan sebelum suhu udara mulai meningkat.\n• Sore Hari (16.00 - 18.00 WIB): Mengembalikan kelembapan setelah penguapan tinggi di siang hari.', style: TextStyle(fontSize: 11, color: AppTheme.textSecondary, height: 1.5), ), ], ), ), ], ), ], ), ); } Widget _buildRefIndicator({ required IconData icon, required Color iconColor, required String label, required String value, }) { return Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: iconColor.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: iconColor.withOpacity(0.1)), ), child: Row( children: [ Icon(icon, color: iconColor, size: 20), const SizedBox(width: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle(fontSize: 9, color: AppTheme.textSecondary), ), const SizedBox(height: 2), Text( value, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: iconColor.withOpacity(0.9), ), ), ], ), ], ), ); } }