784 lines
27 KiB
Dart
784 lines
27 KiB
Dart
import 'dart:async';
|
||
|
||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||
import 'package:firebase_core/firebase_core.dart';
|
||
import 'package:firebase_database/firebase_database.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||
|
||
import 'history_page.dart';
|
||
import 'background_service.dart';
|
||
|
||
void main() async {
|
||
WidgetsFlutterBinding.ensureInitialized();
|
||
|
||
// Inisialisasi Firebase hanya jika belum ada instance
|
||
if (Firebase.apps.isEmpty) {
|
||
try {
|
||
await Firebase.initializeApp(
|
||
options: const FirebaseOptions(
|
||
apiKey: 'AIzaSyBoYp4GpkwF-aMPHVW1gs7PmOj5ucF4xZs',
|
||
appId: '1:289397695071:android:f6e034034faf366003b1ea',
|
||
messagingSenderId: '289397695071',
|
||
projectId: 'monitoring-hujan',
|
||
databaseURL: 'https://monitoring-hujan-default-rtdb.firebaseio.com',
|
||
),
|
||
);
|
||
print('✅ Firebase berhasil diinisialisasi di main');
|
||
} catch (e) {
|
||
print('❌ Error inisialisasi Firebase di main: $e');
|
||
}
|
||
} else {
|
||
print('ℹ️ Firebase sudah diinisialisasi sebelumnya');
|
||
}
|
||
|
||
// Inisialisasi background service
|
||
try {
|
||
await initializeService();
|
||
print('✅ Background service berhasil diinisialisasi');
|
||
} catch (e) {
|
||
print('❌ Error inisialisasi background service: $e');
|
||
}
|
||
|
||
runApp(const MyApp());
|
||
}
|
||
|
||
class MyApp extends StatelessWidget {
|
||
const MyApp({super.key});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return MaterialApp(
|
||
debugShowCheckedModeBanner: false,
|
||
title: 'Monitoring Kolam',
|
||
localizationsDelegates: const [
|
||
GlobalMaterialLocalizations.delegate,
|
||
GlobalWidgetsLocalizations.delegate,
|
||
GlobalCupertinoLocalizations.delegate,
|
||
],
|
||
supportedLocales: const [
|
||
Locale('id', 'ID'),
|
||
Locale('en', 'US'),
|
||
],
|
||
locale: const Locale('id', 'ID'),
|
||
theme: ThemeData(
|
||
colorScheme: ColorScheme.fromSeed(
|
||
seedColor: const Color(0xFF2196F3),
|
||
primary: const Color(0xFF2196F3),
|
||
secondary: const Color(0xFF03A9F4),
|
||
),
|
||
useMaterial3: true,
|
||
),
|
||
home: const MainPage(),
|
||
);
|
||
}
|
||
}
|
||
|
||
class MainPage extends StatefulWidget {
|
||
const MainPage({super.key});
|
||
|
||
@override
|
||
State<MainPage> createState() => _MainPageState();
|
||
}
|
||
|
||
class _MainPageState extends State<MainPage> {
|
||
int _currentIndex = 0;
|
||
late List<Widget> _pages;
|
||
Timer? _saveTimer;
|
||
Timer? _countdownTimer;
|
||
int _remainingSeconds = 60;
|
||
Duration _saveInterval = const Duration(minutes: 1);
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_initializeApp();
|
||
_startSaveTimer();
|
||
_startCountdownTimer();
|
||
}
|
||
|
||
Future<void> _initializeApp() async {
|
||
_pages = [
|
||
MonitoringPage(
|
||
key: UniqueKey(),
|
||
onDataUpdate: (s, k, c, ka) => _updateSensorData(s, k, c, ka),
|
||
onIntervalChange: _changeSaveInterval,
|
||
currentInterval: _saveInterval,
|
||
),
|
||
const HistoryPage(),
|
||
];
|
||
setState(() {});
|
||
}
|
||
|
||
void _changeSaveInterval(Duration newInterval) {
|
||
print(
|
||
'🔄 Mengubah interval dari ${_saveInterval.inSeconds}s ke ${newInterval.inSeconds}s');
|
||
setState(() {
|
||
_saveInterval = newInterval;
|
||
_startSaveTimer();
|
||
});
|
||
print('✅ Interval berhasil diubah');
|
||
}
|
||
|
||
void _updateSensorData(
|
||
double suhu, double kelembaban, double cahaya, double rainVal) {
|
||
// No need to update state here as _saveToFirestore is handled by background service
|
||
}
|
||
|
||
void _startSaveTimer() {
|
||
_saveTimer?.cancel();
|
||
_remainingSeconds = _saveInterval.inSeconds;
|
||
print('⏰ Timer dimulai dengan interval: ${_saveInterval.inSeconds} detik');
|
||
_saveTimer = Timer.periodic(_saveInterval, (timer) async {
|
||
print('⏰ Timer terpanggil - mengirim data ke Firestore...');
|
||
await _saveFromRealtimeToFirestore();
|
||
setState(() {
|
||
_remainingSeconds = _saveInterval.inSeconds;
|
||
});
|
||
print('⏰ Timer selesai - menunggu interval berikutnya');
|
||
});
|
||
}
|
||
|
||
void _startCountdownTimer() {
|
||
_countdownTimer?.cancel();
|
||
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||
if (!mounted) return;
|
||
setState(() {
|
||
if (_remainingSeconds > 0) {
|
||
_remainingSeconds--;
|
||
} else {
|
||
_remainingSeconds = _saveInterval.inSeconds;
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
Future<void> _saveFromRealtimeToFirestore() async {
|
||
try {
|
||
print('🔄 Memulai pengiriman data ke Firestore...');
|
||
final dbRef = FirebaseDatabase.instance.ref();
|
||
final snapshot = await dbRef.get();
|
||
|
||
if (snapshot.value != null) {
|
||
final data = snapshot.value as Map<dynamic, dynamic>;
|
||
final suhu = double.parse(data['temperature']?.toString() ?? '0');
|
||
final kelembaban = double.parse(data['humidity']?.toString() ?? '0');
|
||
final cahaya = double.parse(data['ldr']?.toString() ?? '0');
|
||
final rainValue = double.parse(data['rain']?.toString() ?? '0');
|
||
final statusHujan = data['statusHujan']?.toString() ?? '';
|
||
final statusCahaya = data['statusCahaya']?.toString() ?? '';
|
||
|
||
print(
|
||
'📊 Data sensor: Suhu=$suhu, Kelembaban=$kelembaban, Cahaya=$cahaya, Hujan=$rainValue');
|
||
|
||
final docRef =
|
||
await FirebaseFirestore.instance.collection('sensor_history').add({
|
||
'temperature': suhu,
|
||
'humidity': kelembaban,
|
||
'light': cahaya,
|
||
'rain': rainValue,
|
||
'rain_status': statusHujan,
|
||
'weather_status': statusCahaya,
|
||
'timestamp': FieldValue.serverTimestamp(),
|
||
});
|
||
|
||
print('✅ Data berhasil dikirim ke Firestore dengan ID: ${docRef.id}');
|
||
} else {
|
||
print('⚠️ Tidak ada data dari Realtime Database');
|
||
}
|
||
} catch (e) {
|
||
print('❌ Error auto save dari RTDB ke Firestore: $e');
|
||
print('🔍 Detail error: ${e.toString()}');
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_saveTimer?.cancel();
|
||
_countdownTimer?.cancel();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final minutes = (_remainingSeconds ~/ 60).toString().padLeft(2, '0');
|
||
final seconds = (_remainingSeconds % 60).toString().padLeft(2, '0');
|
||
|
||
return Scaffold(
|
||
body: Stack(
|
||
children: [
|
||
if (_pages.isNotEmpty) _pages[_currentIndex],
|
||
if (_currentIndex == 0)
|
||
Positioned(
|
||
bottom: 0,
|
||
left: 0,
|
||
right: 0,
|
||
child: Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||
borderRadius:
|
||
const BorderRadius.vertical(top: Radius.circular(16)),
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Icon(Icons.timer, color: Colors.blue),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
'Next save in: $minutes:$seconds',
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
color: Colors.blue,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
bottomNavigationBar: BottomNavigationBar(
|
||
currentIndex: _currentIndex,
|
||
onTap: (idx) => setState(() => _currentIndex = idx),
|
||
items: const [
|
||
BottomNavigationBarItem(
|
||
icon: Icon(Icons.monitor_heart),
|
||
label: 'Monitoring',
|
||
),
|
||
BottomNavigationBarItem(
|
||
icon: Icon(Icons.history),
|
||
label: 'Riwayat',
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class MonitoringPage extends StatefulWidget {
|
||
final Function(double, double, double, double) onDataUpdate;
|
||
final Function(Duration) onIntervalChange;
|
||
final Duration currentInterval;
|
||
|
||
const MonitoringPage(
|
||
{Key? key,
|
||
required this.onDataUpdate,
|
||
required this.onIntervalChange,
|
||
required this.currentInterval})
|
||
: super(key: key);
|
||
|
||
@override
|
||
State<MonitoringPage> createState() => _MonitoringPageState();
|
||
}
|
||
|
||
class _MonitoringPageState extends State<MonitoringPage> {
|
||
final DatabaseReference _database = FirebaseDatabase.instance.ref();
|
||
double suhu = 30.0;
|
||
double kelembaban = 70.0;
|
||
double cahaya = 800.0;
|
||
double _rainVal = 0.0;
|
||
String cuaca = 'Cerah';
|
||
bool isLoading = true;
|
||
String? error;
|
||
|
||
// --- Servo & Mode State ---
|
||
bool _modeManual = true;
|
||
bool _kontrolServo = true;
|
||
bool _servo = true;
|
||
bool _isUpdatingServo = false;
|
||
|
||
// --- Firebase Get Function ---
|
||
Future<void> firebaseGet() async {
|
||
try {
|
||
final snapshot = await _database.get();
|
||
if (snapshot.value != null) {
|
||
final data = snapshot.value as Map<dynamic, dynamic>;
|
||
setState(() {
|
||
_modeManual = data['modeManual'] ?? true;
|
||
_kontrolServo = data['kontrolServo'] ?? true;
|
||
_servo = data['servo'] ?? true;
|
||
});
|
||
}
|
||
} catch (e) {
|
||
debugPrint('firebaseGet error: $e');
|
||
}
|
||
}
|
||
|
||
// --- Update Servo in Firebase ---
|
||
Future<void> updateServo(bool value) async {
|
||
setState(() => _isUpdatingServo = true);
|
||
try {
|
||
await _database.child('servo').set(value);
|
||
setState(() => _servo = value);
|
||
} catch (e) {
|
||
debugPrint('updateServo error: $e');
|
||
}
|
||
setState(() => _isUpdatingServo = false);
|
||
}
|
||
|
||
// --- Update Mode in Firebase ---
|
||
Future<void> updateModeManual(bool value) async {
|
||
try {
|
||
await _database.child('modeManual').set(value);
|
||
setState(() => _modeManual = value);
|
||
} catch (e) {
|
||
debugPrint('updateModeManual error: $e');
|
||
}
|
||
}
|
||
|
||
// --- Update KontrolServo in Firebase ---
|
||
Future<void> updateKontrolServo(bool value) async {
|
||
try {
|
||
await _database.child('kontrolServo').set(value);
|
||
setState(() => _kontrolServo = value);
|
||
} catch (e) {
|
||
debugPrint('updateKontrolServo error: $e');
|
||
}
|
||
}
|
||
|
||
double _calculateRainPercent(double kadarAir) {
|
||
// Menghitung persentase hujan dari nilai kadar air
|
||
return ((4095 - (kadarAir / 100 * 4095)) / 4095) * 100;
|
||
}
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
firebaseGet();
|
||
_setupRealtimeUpdates();
|
||
}
|
||
|
||
String _updateStatusCuaca(double cahaya) {
|
||
// Status cuaca berdasarkan nilai LDR
|
||
return (cahaya < 700) ? 'Terang' : 'Gelap';
|
||
}
|
||
|
||
Color _getWeatherStatusColor(String status) {
|
||
// Warna untuk setiap status cuaca
|
||
switch (status) {
|
||
case 'Terang':
|
||
return Colors.orange;
|
||
case 'Gelap':
|
||
return Colors.grey.shade800;
|
||
default:
|
||
return Colors.grey;
|
||
}
|
||
}
|
||
|
||
void _setupRealtimeUpdates() {
|
||
_database.onValue.listen((event) async {
|
||
if (!mounted) return;
|
||
if (event.snapshot.value != null) {
|
||
final data = event.snapshot.value as Map<dynamic, dynamic>;
|
||
setState(() {
|
||
isLoading = false;
|
||
suhu = double.parse(data['temperature']?.toString() ?? '30.0');
|
||
kelembaban = double.parse(data['humidity']?.toString() ?? '70.0');
|
||
cahaya = double.parse(data['ldr']?.toString() ?? '800.0');
|
||
_rainVal = double.parse(data['rain']?.toString() ?? '0');
|
||
_modeManual = data['modeManual'] ?? true;
|
||
_kontrolServo = data['kontrolServo'] ?? true;
|
||
_servo = data['servo'] ?? true;
|
||
});
|
||
// --- Servo Logic ---
|
||
if (_modeManual) {
|
||
// Manual: servo follows kontrolServo
|
||
if (_servo != _kontrolServo) {
|
||
await updateServo(_kontrolServo);
|
||
}
|
||
} else {
|
||
// Otomatis: servo reacts to rain
|
||
// Konsisten dengan _getStatusHujan: < 500 = Hujan
|
||
bool autoServo = _rainVal < 500; // Tertutup jika hujan (< 500)
|
||
if (_servo != autoServo) {
|
||
await updateServo(autoServo);
|
||
}
|
||
}
|
||
} else {
|
||
setState(() {
|
||
isLoading = false;
|
||
error = 'Tidak ada data';
|
||
});
|
||
}
|
||
}, onError: (e) {
|
||
if (mounted) {
|
||
setState(() {
|
||
error = 'Error: $e';
|
||
isLoading = false;
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: const Row(
|
||
children: [
|
||
Text('Monitoring Penjemuran Ikan',
|
||
style: TextStyle(
|
||
color: Colors.white, fontWeight: FontWeight.bold)),
|
||
],
|
||
),
|
||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||
actions: [
|
||
PopupMenuButton<Duration>(
|
||
icon: const Icon(Icons.timer, color: Colors.white),
|
||
initialValue: widget.currentInterval,
|
||
onSelected: (value) {
|
||
widget.onIntervalChange(value);
|
||
},
|
||
itemBuilder: (context) => [
|
||
const PopupMenuItem(
|
||
value: Duration(seconds: 30),
|
||
child: Text('30 detik'),
|
||
),
|
||
const PopupMenuItem(
|
||
value: Duration(minutes: 1),
|
||
child: Text('1 menit'),
|
||
),
|
||
const PopupMenuItem(
|
||
value: Duration(minutes: 5),
|
||
child: Text('5 menit'),
|
||
),
|
||
const PopupMenuItem(
|
||
value: Duration(minutes: 10),
|
||
child: Text('10 menit'),
|
||
),
|
||
const PopupMenuItem(
|
||
value: Duration(minutes: 30),
|
||
child: Text('30 menit'),
|
||
),
|
||
const PopupMenuItem(
|
||
value: Duration(hours: 1),
|
||
child: Text('1 jam'),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
body: isLoading
|
||
? const Center(child: CircularProgressIndicator())
|
||
: error != null
|
||
? Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Icon(
|
||
Icons.error_outline,
|
||
color: Colors.red,
|
||
size: 60,
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
error!,
|
||
style: const TextStyle(color: Colors.red),
|
||
),
|
||
const SizedBox(height: 16),
|
||
ElevatedButton(
|
||
onPressed: _setupRealtimeUpdates,
|
||
child: const Text('Coba Lagi'),
|
||
),
|
||
],
|
||
),
|
||
)
|
||
: Container(
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
begin: Alignment.topCenter,
|
||
end: Alignment.bottomCenter,
|
||
colors: [Colors.blue.shade50, Colors.white],
|
||
),
|
||
),
|
||
child: SingleChildScrollView(
|
||
padding: const EdgeInsets.all(16.0),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// --- Info Cards ---
|
||
_buildInfoCard(
|
||
'Suhu Udara',
|
||
'${suhu.toStringAsFixed(1)}°C',
|
||
Colors.red,
|
||
Icons.thermostat,
|
||
_getStatusSuhu(suhu),
|
||
),
|
||
const SizedBox(height: 16),
|
||
_buildInfoCard(
|
||
'Kelembaban',
|
||
'${kelembaban.toStringAsFixed(1)}%',
|
||
Colors.blue,
|
||
Icons.water_drop,
|
||
_getStatusKelembaban(kelembaban),
|
||
),
|
||
const SizedBox(height: 16),
|
||
_buildInfoCard(
|
||
'Intensitas Cahaya',
|
||
'${cahaya.toStringAsFixed(0)}',
|
||
Colors.orange,
|
||
Icons.wb_sunny,
|
||
_getStatusCuaca(cahaya),
|
||
),
|
||
const SizedBox(height: 16),
|
||
_buildInfoCard(
|
||
'Curah Hujan',
|
||
'${_rainVal.toStringAsFixed(0)}',
|
||
Colors.brown,
|
||
Icons.water_damage,
|
||
_getStatusHujan(_rainVal),
|
||
),
|
||
const SizedBox(height: 32),
|
||
// --- Manual/Otomatis Control (moved to bottom, improved UI) ---
|
||
Card(
|
||
elevation: 4,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(16),
|
||
),
|
||
margin: const EdgeInsets.only(top: 8, bottom: 16),
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(20.0),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Icon(Icons.settings_remote,
|
||
color: Theme.of(context)
|
||
.colorScheme
|
||
.primary),
|
||
const SizedBox(width: 10),
|
||
const Text(
|
||
'Kontrol Servo & Mode',
|
||
style: TextStyle(
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: 18,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 16),
|
||
Row(
|
||
children: [
|
||
const Text('Manual',
|
||
style: TextStyle(fontSize: 16)),
|
||
Switch(
|
||
value: _modeManual,
|
||
onChanged: (val) async {
|
||
await updateModeManual(val);
|
||
if (!val) {
|
||
// If switching to otomatis, set kontrolServo to false
|
||
await updateKontrolServo(false);
|
||
}
|
||
},
|
||
),
|
||
const Text('Otomatis',
|
||
style: TextStyle(fontSize: 16)),
|
||
],
|
||
),
|
||
if (_modeManual)
|
||
Padding(
|
||
padding: const EdgeInsets.only(top: 8.0),
|
||
child: Row(
|
||
children: [
|
||
const Text('Servo:',
|
||
style: TextStyle(fontSize: 16)),
|
||
const SizedBox(width: 8),
|
||
_servo
|
||
? const Icon(Icons.lock,
|
||
color: Colors.blue)
|
||
: const Icon(Icons.lock_open,
|
||
color: Colors.orange),
|
||
const SizedBox(width: 8),
|
||
Switch(
|
||
value: _kontrolServo,
|
||
onChanged: (val) async {
|
||
await updateKontrolServo(val);
|
||
},
|
||
),
|
||
_isUpdatingServo
|
||
? const SizedBox(
|
||
width: 16,
|
||
height: 16,
|
||
child:
|
||
CircularProgressIndicator(
|
||
strokeWidth: 2),
|
||
)
|
||
: const SizedBox.shrink(),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
_kontrolServo
|
||
? 'Tertutup'
|
||
: 'Terbuka',
|
||
style: TextStyle(
|
||
color: _kontrolServo
|
||
? Colors.blue
|
||
: Colors.orange,
|
||
fontWeight: FontWeight.bold,
|
||
)),
|
||
],
|
||
),
|
||
),
|
||
if (!_modeManual)
|
||
Padding(
|
||
padding: const EdgeInsets.only(top: 8.0),
|
||
child: Row(
|
||
children: [
|
||
const Icon(Icons.info_outline,
|
||
color: Colors.grey),
|
||
const SizedBox(width: 8),
|
||
Text(
|
||
'Servo otomatis berdasarkan curah hujan',
|
||
style: TextStyle(
|
||
color: Colors.grey.shade700,
|
||
fontSize: 10),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(height: 80), // Space for countdown
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildInfoCard(
|
||
String title, String value, Color color, IconData icon, String status) {
|
||
return Card(
|
||
elevation: 4,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(16.0),
|
||
child: Row(
|
||
children: [
|
||
Icon(
|
||
icon,
|
||
size: 40,
|
||
color: color,
|
||
),
|
||
const SizedBox(width: 16),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
title,
|
||
style: const TextStyle(
|
||
fontSize: 16,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Row(
|
||
children: [
|
||
Text(
|
||
title == 'Intensitas Cahaya'
|
||
? '${cahaya.toStringAsFixed(0)}'
|
||
: value,
|
||
style: TextStyle(
|
||
fontSize: 24,
|
||
fontWeight: FontWeight.bold,
|
||
color: color,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||
decoration: BoxDecoration(
|
||
color: title == 'Suhu Udara'
|
||
? _getStatusSuhuColor(status).withOpacity(0.2)
|
||
: _getStatusColor(status).withOpacity(0.2),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Text(
|
||
status,
|
||
style: TextStyle(
|
||
color: title == 'Status Cuaca'
|
||
? _getWeatherStatusColor(status)
|
||
: title == 'Suhu Udara'
|
||
? _getStatusSuhuColor(status)
|
||
: _getStatusColor(status),
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
String _getStatusSuhu(double suhu) {
|
||
// Status suhu berdasarkan nilai sensor
|
||
if (suhu <= 20) return 'Dingin';
|
||
if (suhu <= 23) return 'Sejuk';
|
||
if (suhu <= 26) return 'Normal';
|
||
if (suhu <= 27) return 'Hangat';
|
||
return 'Panas';
|
||
}
|
||
|
||
String _getStatusKelembaban(double kelembaban) {
|
||
// Status kelembaban berdasarkan nilai sensor
|
||
if (kelembaban < 40) return 'Kering';
|
||
if (kelembaban > 80) return 'Basah';
|
||
return 'Normal';
|
||
}
|
||
|
||
String _getStatusHujan(double kadar) {
|
||
// Status hujan berdasarkan nilai sensor
|
||
return (kadar < 500) ? "Hujan" : "Tidak Hujan";
|
||
}
|
||
|
||
String _getStatusCuaca(double cahaya) {
|
||
// Status cahaya berdasarkan nilai LDR
|
||
return (cahaya < 700) ? "Terang" : "Gelap";
|
||
}
|
||
|
||
Color _getStatusColor(String status) {
|
||
// Warna untuk setiap status sensor
|
||
switch (status) {
|
||
case 'Hujan':
|
||
return Colors.blue.shade700;
|
||
case 'Tidak Hujan':
|
||
return Colors.orange;
|
||
case 'Cerah':
|
||
return Colors.orange;
|
||
default:
|
||
return Colors.grey;
|
||
}
|
||
}
|
||
|
||
Color _getStatusSuhuColor(String status) {
|
||
// Warna untuk setiap status suhu
|
||
switch (status) {
|
||
case 'Dingin':
|
||
return Colors.blue.shade700;
|
||
case 'Sejuk':
|
||
return Colors.blue.shade400;
|
||
case 'Normal':
|
||
return Colors.orange;
|
||
case 'Hangat':
|
||
return Colors.orange.shade700;
|
||
case 'Panas':
|
||
return Colors.red;
|
||
default:
|
||
return Colors.grey;
|
||
}
|
||
}
|
||
}
|