HydroNutrify_Ari_e41221567/lib/ui/shared/growth_phase_card.dart

473 lines
15 KiB
Dart

import 'package:flutter/material.dart';
import 'package:mobile_monitoring/core/constants/constants.dart';
import 'package:mobile_monitoring/ui/widgets/growth_phase_service.dart';
import 'package:mobile_monitoring/core/utils/phase_display_utils.dart';
class GrowthPhaseCard extends StatefulWidget {
const GrowthPhaseCard({super.key});
@override
State<GrowthPhaseCard> createState() => _GrowthPhaseCardState();
}
class _GrowthPhaseCardState extends State<GrowthPhaseCard> {
final _growthPhaseService = GrowthPhaseService();
void _showSnackBar(String msg, {bool isError = false}) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(msg),
backgroundColor: isError
? AppColors.error
: (msg.contains('') ? AppColors.success : AppColors.info),
duration: const Duration(seconds: 2),
),
);
}
Future<void> _setPlantingDate() async {
final userId = _growthPhaseService.getCurrentUserId();
if (userId.isEmpty) return;
final picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2020),
lastDate: DateTime.now(),
builder: (context, child) => Theme(
data: Theme.of(context).copyWith(
colorScheme: const ColorScheme.light(primary: AppColors.primary),
),
child: child!,
),
);
if (picked != null) {
final result = await _growthPhaseService.savePlantingDate(
userId: userId,
plantingDate: picked,
);
_showSnackBar(
result.success ? '${result.message}!' : result.message,
isError: !result.success,
);
}
}
Future<void> _resetCycle() async {
final userId = _growthPhaseService.getCurrentUserId();
if (userId.isEmpty) return;
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Reset Siklus?'),
content: const Text('Data tanggal tanam sebelumnya akan dihapus.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Batal'),
),
ElevatedButton(
onPressed: () => Navigator.pop(ctx, true),
style: ElevatedButton.styleFrom(backgroundColor: AppColors.error),
child: const Text('Reset'),
),
],
),
);
if (confirmed == true) {
final result = await _growthPhaseService.resetCycle(userId);
_showSnackBar(
result.success ? '${result.message}!' : result.message,
isError: !result.success,
);
}
}
Future<void> _markHarvest() async {
final userId = _growthPhaseService.getCurrentUserId();
if (userId.isEmpty) return;
final result = await _growthPhaseService.markHarvest(userId);
_showSnackBar(
result.success ? '${result.message}' : result.message,
isError: !result.success,
);
}
Future<void> _updatePhaseIfChanged(
String currentPhaseInDb,
String calculatedPhase,
int days,
DateTime startDate,
) async {
final userId = _growthPhaseService.getCurrentUserId();
if (userId.isEmpty) return;
try {
await _growthPhaseService.updatePhaseIfChanged(
userId: userId,
currentPhaseInDb: currentPhaseInDb,
calculatedPhase: calculatedPhase,
days: days,
startDate: startDate,
);
_showSnackBar('✓ Fase otomatis diperbarui ke $calculatedPhase');
} catch (e) {
debugPrint('✗ Error updating phase: $e');
}
}
@override
Widget build(BuildContext context) {
final user = _growthPhaseService.getCurrentUser();
if (user == null) {
return _buildCard(
child: const Text('Silakan login untuk melihat fase pertumbuhan.'),
);
}
return StreamBuilder<Map<String, dynamic>?>(
stream: _growthPhaseService.activeCycleStream(user.uid),
builder: (context, snapshot) {
if (snapshot.hasError) {
return _buildCard(child: Text('Error: ${snapshot.error}'));
}
if (snapshot.connectionState == ConnectionState.waiting) {
return _buildCard(
child: const Center(child: CircularProgressIndicator()),
);
}
final data = snapshot.data;
if (data == null) {
return _buildEmptyState();
}
final startDate = _growthPhaseService.extractStartDate(data);
final currentPhaseInDb = data['growth_phase'] as String?;
if (startDate != null) {
final days = DateTime.now().difference(startDate).inDays;
final calculatedPhase = _growthPhaseService.calculateCurrentPhase(
days,
);
final currentPhase =
(currentPhaseInDb != null && currentPhaseInDb.isNotEmpty)
? currentPhaseInDb
: calculatedPhase;
if (currentPhaseInDb != null && currentPhaseInDb != calculatedPhase) {
Future.microtask(
() => _updatePhaseIfChanged(
currentPhaseInDb,
calculatedPhase,
days,
startDate,
),
);
}
return _buildActiveState(
daysSincePlanting: days,
startDate: startDate,
currentPhase: currentPhase,
);
}
return _buildEmptyState();
},
);
}
Widget _buildCard({required Widget child}) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: _growthPhaseService.applyAlpha(AppColors.darkGray, 0.1),
spreadRadius: 2,
blurRadius: 10,
),
],
),
child: child,
);
}
Widget _buildEmptyState() {
return _buildCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: _growthPhaseService.applyAlpha(AppColors.primary, 0.1),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.eco,
color: AppColors.primary,
size: 24,
),
),
const SizedBox(width: 12),
const Expanded(
child: Text(
'Fase Pertumbuhan',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
],
),
const SizedBox(height: 16),
const Text(
'Belum ada siklus tanam aktif',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _setPlantingDate,
icon: const Icon(Icons.event, size: 20),
label: const Text('Mulai Tanam'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: AppColors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
),
);
}
Widget _buildActiveState({
required int daysSincePlanting,
DateTime? startDate,
required String currentPhase,
}) {
final phaseColor = _growthPhaseService.getPhaseColor(currentPhase);
final phaseData = _growthPhaseService.getPhaseData(daysSincePlanting);
final phaseDay = phaseData['day'] as int;
final phaseDuration = phaseData['duration'] as int;
final progress = phaseData['progress'] as double;
return _buildCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: _growthPhaseService.applyAlpha(phaseColor, 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(Icons.eco, color: phaseColor, size: 24),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Fase Pertumbuhan',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
'Hari ke-$daysSincePlanting',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
),
),
IconButton(
icon: const Icon(Icons.refresh, size: 20),
onPressed: _resetCycle,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: _growthPhaseService.applyAlpha(phaseColor, 0.15),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: phaseColor, width: 1.5),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.circle, color: phaseColor, size: 12),
const SizedBox(width: 8),
Text(
'Fase $currentPhase',
style: TextStyle(
color: phaseColor,
fontWeight: FontWeight.bold,
),
),
Text(
' (Hari $phaseDay/$phaseDuration)',
style: TextStyle(color: phaseColor, fontSize: 12),
),
],
),
),
const SizedBox(height: 16),
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
value: progress.clamp(0.0, 1.0),
backgroundColor: Colors.grey[200],
color: phaseColor,
minHeight: 8,
),
),
const SizedBox(height: 20),
const Divider(),
const SizedBox(height: 12),
Row(
children: List.generate(PhaseDisplayUtils.ordered.length, (index) {
final phase = PhaseDisplayUtils.ordered[index];
final isActive =
phase.name.toLowerCase() == currentPhase.toLowerCase();
final isPassed = daysSincePlanting >= phase.startDay;
return Expanded(
child: Padding(
padding: EdgeInsets.only(
right: index < PhaseDisplayUtils.ordered.length - 1 ? 8 : 0,
),
child: _buildPhaseItem(
phase.name,
phase.dayRangeLabel,
phase.color,
isActive,
isPassed,
),
),
);
}),
),
if (startDate != null) ...[
const SizedBox(height: 16),
Row(
children: [
Icon(Icons.event, size: 16, color: Colors.grey[600]),
const SizedBox(width: 6),
Text(
'Mulai: ${_growthPhaseService.formatDate(startDate)}',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
),
],
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: _resetCycle,
icon: const Icon(Icons.refresh, size: 16),
label: const Text('Reset'),
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.error,
side: const BorderSide(color: AppColors.error),
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: _markHarvest,
icon: const Icon(Icons.agriculture, size: 16),
label: const Text('Panen'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
),
),
),
],
),
],
),
);
}
Widget _buildPhaseItem(
String name,
String range,
Color color,
bool isActive,
bool isPassed,
) {
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isActive
? _growthPhaseService.applyAlpha(color, 0.15)
: (isPassed ? Colors.grey[100] : Colors.grey[50]),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isActive
? color
: (isPassed ? Colors.grey : Colors.grey[300]!),
width: isActive ? 2 : 1,
),
),
child: Column(
children: [
Icon(
isPassed ? Icons.check_circle : Icons.circle_outlined,
size: 18,
color: isActive
? color
: (isPassed ? Colors.grey : Colors.grey[400]),
),
const SizedBox(height: 4),
Text(
name,
style: TextStyle(
fontSize: 10,
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
color: isActive
? color
: (isPassed ? Colors.grey : Colors.grey[600]),
),
textAlign: TextAlign.center,
),
Text(
range,
style: TextStyle(fontSize: 8, color: Colors.grey[500]),
textAlign: TextAlign.center,
),
],
),
);
}
}