import 'package:flutter/material.dart'; import '../../../core/app_theme.dart'; class SensorLimitCard extends StatefulWidget { final double suhuMinLimit; final double suhuMaxLimit; final double kelembapanLimit; final Future Function(double suhuMin, double suhuMax, double kelembapan) onSave; const SensorLimitCard({ super.key, required this.suhuMinLimit, required this.suhuMaxLimit, required this.kelembapanLimit, required this.onSave, }); // Nilai default untuk tanaman jamur static const double defaultSuhuMin = 22.0; static const double defaultSuhuMax = 28.0; static const double defaultKelembapan = 90.0; @override State createState() => _SensorLimitCardState(); } class _SensorLimitCardState extends State { late double _draftSuhuMin; late double _draftSuhuMax; late double _draftKelembapan; bool _isSaving = false; @override void initState() { super.initState(); _draftSuhuMin = widget.suhuMinLimit; _draftSuhuMax = widget.suhuMaxLimit; _draftKelembapan = widget.kelembapanLimit; } // Sinkronisasi draft jika nilai dari provider berubah dari luar @override void didUpdateWidget(SensorLimitCard oldWidget) { super.didUpdateWidget(oldWidget); final serverChanged = oldWidget.suhuMinLimit != widget.suhuMinLimit || oldWidget.suhuMaxLimit != widget.suhuMaxLimit || oldWidget.kelembapanLimit != widget.kelembapanLimit; final draftUnchanged = _draftSuhuMin == oldWidget.suhuMinLimit && _draftSuhuMax == oldWidget.suhuMaxLimit && _draftKelembapan == oldWidget.kelembapanLimit; // Update draft hanya jika user belum mengedit if (serverChanged && draftUnchanged) { _draftSuhuMin = widget.suhuMinLimit; _draftSuhuMax = widget.suhuMaxLimit; _draftKelembapan = widget.kelembapanLimit; } } bool get _hasChanges => _draftSuhuMin != widget.suhuMinLimit || _draftSuhuMax != widget.suhuMaxLimit || _draftKelembapan != widget.kelembapanLimit; bool get _isDefault => _draftSuhuMin == SensorLimitCard.defaultSuhuMin && _draftSuhuMax == SensorLimitCard.defaultSuhuMax && _draftKelembapan == SensorLimitCard.defaultKelembapan; void _applyDefault() { setState(() { _draftSuhuMin = SensorLimitCard.defaultSuhuMin; _draftSuhuMax = SensorLimitCard.defaultSuhuMax; _draftKelembapan = SensorLimitCard.defaultKelembapan; }); } void _cancel() { setState(() { _draftSuhuMin = widget.suhuMinLimit; _draftSuhuMax = widget.suhuMaxLimit; _draftKelembapan = widget.kelembapanLimit; }); } Future _save() async { setState(() => _isSaving = true); await widget.onSave(_draftSuhuMin, _draftSuhuMax, _draftKelembapan); if (mounted) setState(() => _isSaving = false); } @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppTheme.accent, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppTheme.accent.withOpacity(0.3), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── Header ───────────────────────────────────────────── Row( children: [ const Icon(Icons.tune, color: Colors.white, size: 18), const SizedBox(width: 6), const Expanded( child: Text( 'Batasan Sensor Otomatis', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w700, fontSize: 14, ), ), ), // Tombol Default — tampil jika nilai draft bukan default if (!_isDefault) GestureDetector( onTap: _applyDefault, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(20), ), child: const Text( 'Default', style: TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.w600, ), ), ), ), ], ), const SizedBox(height: 16), // ── Slider Rentang Suhu (Min - Maks) ─────────────────── _RangeSliderRow( icon: Icons.thermostat, label: 'Batas Rentang Suhu', startValue: _draftSuhuMin, endValue: _draftSuhuMax, displayValue: '${_draftSuhuMin.round()}°C - ${_draftSuhuMax.round()}°C', min: 15, max: 50, divisions: 35, onChanged: (values) { setState(() { _draftSuhuMin = values.start; _draftSuhuMax = values.end; }); }, ), const SizedBox(height: 8), // ── Slider Kelembapan ────────────────────────────────── _SliderRow( icon: Icons.water_drop_outlined, label: 'Kelembapan Min', value: _draftKelembapan, displayValue: '${_draftKelembapan.round()}%', min: 50, max: 100, divisions: 50, onChanged: (v) => setState(() => _draftKelembapan = v), ), // ── Tombol Aksi (hanya tampil jika ada perubahan) ────── AnimatedSize( duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, child: _hasChanges ? Padding( padding: const EdgeInsets.only(top: 14), child: Row( children: [ // Tombol Batal Expanded( child: OutlinedButton( onPressed: _isSaving ? null : _cancel, style: OutlinedButton.styleFrom( foregroundColor: Colors.white, side: const BorderSide(color: Colors.white54), padding: const EdgeInsets.symmetric(vertical: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: const Text( 'Batal', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600), ), ), ), const SizedBox(width: 10), // Tombol Simpan Expanded( flex: 2, child: ElevatedButton( onPressed: _isSaving ? null : _save, style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: AppTheme.accent, padding: const EdgeInsets.symmetric(vertical: 10), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: _isSaving ? const SizedBox( height: 16, width: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.check, size: 16), SizedBox(width: 6), Text( 'Simpan', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold), ), ], ), ), ), ], ), ) : const SizedBox.shrink(), ), ], ), ); } } // ── Range Slider Row ────────────────────────────────────────────── class _RangeSliderRow extends StatelessWidget { final IconData icon; final String label; final double startValue; final double endValue; final String displayValue; final double min; final double max; final int divisions; final ValueChanged onChanged; const _RangeSliderRow({ required this.icon, required this.label, required this.startValue, required this.endValue, required this.displayValue, required this.min, required this.max, required this.divisions, required this.onChanged, }); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(icon, color: Colors.white70, size: 14), const SizedBox(width: 4), Expanded( child: Text( label, style: const TextStyle(color: Colors.white70, fontSize: 12), ), ), Text( displayValue, style: const TextStyle( color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold, ), ), ], ), SliderTheme( data: SliderTheme.of(context).copyWith( activeTrackColor: Colors.white, inactiveTrackColor: Colors.white24, thumbColor: Colors.white, overlayColor: Colors.white24, trackHeight: 3, ), child: RangeSlider( values: RangeValues(startValue, endValue), min: min, max: max, divisions: divisions, onChanged: onChanged, ), ), ], ); } } // ── Slider Row ──────────────────────────────────────────────────── class _SliderRow extends StatelessWidget { final IconData icon; final String label; final double value; final String displayValue; final double min; final double max; final int divisions; final ValueChanged onChanged; const _SliderRow({ required this.icon, required this.label, required this.value, required this.displayValue, required this.min, required this.max, required this.divisions, required this.onChanged, }); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(icon, color: Colors.white70, size: 14), const SizedBox(width: 4), Expanded( child: Text( label, style: const TextStyle(color: Colors.white70, fontSize: 12), ), ), Text( displayValue, style: const TextStyle( color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold, ), ), ], ), SliderTheme( data: SliderTheme.of(context).copyWith( activeTrackColor: Colors.white, inactiveTrackColor: Colors.white24, thumbColor: Colors.white, overlayColor: Colors.white24, trackHeight: 3, ), child: Slider( value: value, min: min, max: max, divisions: divisions, onChanged: onChanged, ), ), ], ); } }