dashboard terhubung firebase
This commit is contained in:
parent
95949930ad
commit
2586f744a9
|
|
@ -50,6 +50,7 @@ class DefaultFirebaseOptions {
|
|||
messagingSenderId: '217854138058',
|
||||
projectId: 'project-ta-951b4',
|
||||
storageBucket: 'project-ta-951b4.firebasestorage.app',
|
||||
databaseURL: 'https://project-ta-951b4-default-rtdb.firebaseio.com',
|
||||
);
|
||||
|
||||
// Pilih project Anda > Project Settings > Your apps > Android app
|
||||
|
|
@ -60,6 +61,7 @@ class DefaultFirebaseOptions {
|
|||
messagingSenderId: '217854138058',
|
||||
projectId: 'project-ta-951b4',
|
||||
storageBucket: 'project-ta-951b4.firebasestorage.app',
|
||||
databaseURL: 'https://project-ta-951b4-default-rtdb.firebaseio.com',
|
||||
iosBundleId: 'com.kampus.apsgo',
|
||||
);
|
||||
|
||||
|
|
@ -69,6 +71,7 @@ class DefaultFirebaseOptions {
|
|||
messagingSenderId: '217854138058',
|
||||
projectId: 'project-ta-951b4',
|
||||
authDomain: 'project-ta-951b4.firebaseapp.com',
|
||||
databaseURL: 'https://project-ta-951b4-default-rtdb.firebaseio.com',
|
||||
storageBucket: 'project-ta-951b4.firebasestorage.app',
|
||||
measurementId: 'G-6ML8QQEGNZ',
|
||||
);
|
||||
|
|
@ -79,6 +82,7 @@ class DefaultFirebaseOptions {
|
|||
messagingSenderId: '217854138058',
|
||||
projectId: 'project-ta-951b4',
|
||||
storageBucket: 'project-ta-951b4.firebasestorage.app',
|
||||
databaseURL: 'https://project-ta-951b4-default-rtdb.firebaseio.com',
|
||||
iosBundleId: 'com.example.projectTa',
|
||||
);
|
||||
|
||||
|
|
@ -87,6 +91,7 @@ class DefaultFirebaseOptions {
|
|||
appId: '1:217854138058:web:233d93e01272a0870c4633',
|
||||
messagingSenderId: '217854138058',
|
||||
projectId: 'project-ta-951b4',
|
||||
databaseURL: 'https://project-ta-951b4-default-rtdb.firebaseio.com',
|
||||
authDomain: 'project-ta-951b4.firebaseapp.com',
|
||||
storageBucket: 'project-ta-951b4.firebasestorage.app',
|
||||
measurementId: 'G-SB8J6ZZQHZ',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import '../theme/app_color.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import '../services/firebase_database_service.dart';
|
||||
import 'kontrol_page.dart';
|
||||
import 'histori_page.dart';
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ class DashboardPage extends StatefulWidget {
|
|||
|
||||
class _DashboardPageState extends State<DashboardPage> {
|
||||
int _selectedIndex = 0;
|
||||
final _dbService = FirebaseDatabaseService();
|
||||
|
||||
final List<Map<String, dynamic>> _pages = [
|
||||
{'title': 'Dashboard', 'icon': Icons.dashboard},
|
||||
|
|
@ -248,47 +250,152 @@ class _DashboardPageState extends State<DashboardPage> {
|
|||
}
|
||||
|
||||
Widget _buildDashboard() {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Sensor Cards - Temperature, Humidity, Light
|
||||
_buildSensorCard(
|
||||
title: "Temperature",
|
||||
value: "30",
|
||||
icon: Icons.thermostat_outlined,
|
||||
color: Colors.orange,
|
||||
),
|
||||
_buildSensorCard(
|
||||
title: "Humidity",
|
||||
value: "70",
|
||||
icon: Icons.water_drop_outlined,
|
||||
color: Colors.blue,
|
||||
),
|
||||
_buildSensorCard(
|
||||
title: "Light",
|
||||
value: "85",
|
||||
icon: Icons.wb_sunny_outlined,
|
||||
color: Colors.amber,
|
||||
),
|
||||
return StreamBuilder<Map<String, dynamic>>(
|
||||
stream: _dbService.getSensorDataStream(),
|
||||
builder: (context, snapshot) {
|
||||
// Loading state
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
const SizedBox(height: 12),
|
||||
// Error state
|
||||
if (snapshot.hasError) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.error_outline, size: 48, color: Colors.red),
|
||||
const SizedBox(height: 16),
|
||||
Text('Error: ${snapshot.error}'),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () => setState(() {}),
|
||||
child: const Text('Retry'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Pot Cards - Pot 1 to Pot 5
|
||||
_buildPotCard(potNumber: 1, soilMoisture: "45"),
|
||||
_buildPotCard(potNumber: 2, soilMoisture: "52"),
|
||||
_buildPotCard(potNumber: 3, soilMoisture: "38"),
|
||||
_buildPotCard(potNumber: 4, soilMoisture: "61"),
|
||||
_buildPotCard(potNumber: 5, soilMoisture: "48"),
|
||||
],
|
||||
),
|
||||
// No data state
|
||||
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.cloud_off, size: 48, color: Colors.grey),
|
||||
SizedBox(height: 16),
|
||||
Text('No data available'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final data = snapshot.data!;
|
||||
final suhu = data['suhu'] ?? '0';
|
||||
final kelembapan = data['kelembapan'] ?? '0';
|
||||
final ldr = data['ldr'] ?? '0';
|
||||
final soil1 = data['soil_1'] ?? '0';
|
||||
final soil2 = data['soil_2'] ?? '0';
|
||||
final soil3 = data['soil_3'] ?? '0';
|
||||
final soil4 = data['soil_4'] ?? '0';
|
||||
final soil5 = data['soil_5'] ?? '0';
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Connection Status Indicator
|
||||
_buildConnectionStatus(),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Sensor Cards - Temperature, Humidity, Light
|
||||
_buildSensorCard(
|
||||
title: "Temperature",
|
||||
value: suhu,
|
||||
unit: "°C",
|
||||
icon: Icons.thermostat_outlined,
|
||||
color: Colors.orange,
|
||||
),
|
||||
_buildSensorCard(
|
||||
title: "Humidity",
|
||||
value: kelembapan,
|
||||
unit: "%",
|
||||
icon: Icons.water_drop_outlined,
|
||||
color: Colors.blue,
|
||||
),
|
||||
_buildSensorCard(
|
||||
title: "Light",
|
||||
value: ldr,
|
||||
unit: "%",
|
||||
icon: Icons.wb_sunny_outlined,
|
||||
color: Colors.amber,
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Pot Cards - Pot 1 to Pot 5
|
||||
_buildPotCard(potNumber: 1, soilMoisture: soil1),
|
||||
_buildPotCard(potNumber: 2, soilMoisture: soil2),
|
||||
_buildPotCard(potNumber: 3, soilMoisture: soil3),
|
||||
_buildPotCard(potNumber: 4, soilMoisture: soil4),
|
||||
_buildPotCard(potNumber: 5, soilMoisture: soil5),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildConnectionStatus() {
|
||||
return StreamBuilder<bool>(
|
||||
stream: _dbService.getConnectionStatus(),
|
||||
builder: (context, snapshot) {
|
||||
final isConnected = snapshot.data ?? false;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: isConnected
|
||||
? Colors.green.withOpacity(0.1)
|
||||
: Colors.red.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: isConnected ? Colors.green : Colors.red,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: isConnected ? Colors.green : Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
isConnected ? 'Connected' : 'Disconnected',
|
||||
style: TextStyle(
|
||||
color: isConnected ? Colors.green : Colors.red,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSensorCard({
|
||||
required String title,
|
||||
required String value,
|
||||
String unit = '',
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
}) {
|
||||
|
|
@ -325,9 +432,28 @@ class _DashboardPageState extends State<DashboardPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (unit.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 4, bottom: 2),
|
||||
child: Text(
|
||||
unit,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,199 @@
|
|||
import 'package:firebase_database/firebase_database.dart';
|
||||
|
||||
class FirebaseDatabaseService {
|
||||
static final FirebaseDatabaseService _instance =
|
||||
FirebaseDatabaseService._internal();
|
||||
factory FirebaseDatabaseService() => _instance;
|
||||
FirebaseDatabaseService._internal();
|
||||
|
||||
final DatabaseReference _database = FirebaseDatabase.instance.ref();
|
||||
|
||||
// ==================== SENSOR DATA ====================
|
||||
|
||||
/// Get real-time stream of sensor data
|
||||
Stream<Map<String, dynamic>> getSensorDataStream() {
|
||||
return _database.child('data').onValue.map((event) {
|
||||
if (event.snapshot.value != null) {
|
||||
final data = Map<String, dynamic>.from(
|
||||
event.snapshot.value as Map,
|
||||
);
|
||||
return {
|
||||
'suhu': _parseValue(data['suhu']),
|
||||
'kelembapan': _parseValue(data['kelembapan']),
|
||||
'ldr': _parseValue(data['ldr']),
|
||||
'soil_1': _parseValue(data['soil_1']),
|
||||
'soil_2': _parseValue(data['soil_2']),
|
||||
'soil_3': _parseValue(data['soil_3']),
|
||||
'soil_4': _parseValue(data['soil_4']),
|
||||
'soil_5': _parseValue(data['soil_5']),
|
||||
};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
/// Get sensor data once
|
||||
Future<Map<String, dynamic>> getSensorData() async {
|
||||
try {
|
||||
final snapshot = await _database.child('data').get();
|
||||
if (snapshot.exists) {
|
||||
final data = Map<String, dynamic>.from(snapshot.value as Map);
|
||||
return {
|
||||
'suhu': _parseValue(data['suhu']),
|
||||
'kelembapan': _parseValue(data['kelembapan']),
|
||||
'ldr': _parseValue(data['ldr']),
|
||||
'soil_1': _parseValue(data['soil_1']),
|
||||
'soil_2': _parseValue(data['soil_2']),
|
||||
'soil_3': _parseValue(data['soil_3']),
|
||||
'soil_4': _parseValue(data['soil_4']),
|
||||
'soil_5': _parseValue(data['soil_5']),
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error getting sensor data: $e');
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// ==================== AKTUATOR CONTROL ====================
|
||||
|
||||
/// Get real-time stream of aktuator status
|
||||
Stream<Map<String, bool>> getAktuatorStream() {
|
||||
return _database.child('aktuator').onValue.map((event) {
|
||||
if (event.snapshot.value != null) {
|
||||
final data = Map<String, dynamic>.from(
|
||||
event.snapshot.value as Map,
|
||||
);
|
||||
return {
|
||||
'mosvet_1': data['mosvet_1'] ?? false, // Pompa Air
|
||||
'mosvet_2': data['mosvet_2'] ?? false, // Pompa Pupuk
|
||||
'mosvet_3': data['mosvet_3'] ?? false, // Valve 1
|
||||
'mosvet_4': data['mosvet_4'] ?? false, // Valve 2
|
||||
'mosvet_5': data['mosvet_5'] ?? false, // Valve 3
|
||||
'mosvet_6': data['mosvet_6'] ?? false, // Valve 4
|
||||
'mosvet_7': data['mosvet_7'] ?? false, // Valve 5
|
||||
};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
/// Set aktuator status (untuk kontrol manual)
|
||||
Future<void> setAktuator(String mosfetName, bool value) async {
|
||||
try {
|
||||
await _database.child('aktuator/$mosfetName').set(value);
|
||||
} catch (e) {
|
||||
print('Error setting aktuator: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== KONTROL CONFIG ====================
|
||||
|
||||
/// Get real-time stream of kontrol configuration
|
||||
Stream<Map<String, dynamic>> getKontrolStream() {
|
||||
return _database.child('kontrol').onValue.map((event) {
|
||||
if (event.snapshot.value != null) {
|
||||
return Map<String, dynamic>.from(event.snapshot.value as Map);
|
||||
}
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
/// Get kontrol configuration once
|
||||
Future<Map<String, dynamic>> getKontrolConfig() async {
|
||||
try {
|
||||
final snapshot = await _database.child('kontrol').get();
|
||||
if (snapshot.exists) {
|
||||
return Map<String, dynamic>.from(snapshot.value as Map);
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error getting kontrol config: $e');
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/// Update kontrol configuration
|
||||
Future<void> updateKontrolConfig(Map<String, dynamic> config) async {
|
||||
try {
|
||||
await _database.child('kontrol').update(config);
|
||||
} catch (e) {
|
||||
print('Error updating kontrol config: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set batas threshold (batas_atas, batas_bawah)
|
||||
Future<void> setThreshold({int? batasAtas, int? batasBawah}) async {
|
||||
try {
|
||||
final updates = <String, dynamic>{};
|
||||
if (batasAtas != null) updates['batas_atas'] = batasAtas;
|
||||
if (batasBawah != null) updates['batas_bawah'] = batasBawah;
|
||||
await _database.child('kontrol').update(updates);
|
||||
} catch (e) {
|
||||
print('Error setting threshold: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set waktu penyiraman
|
||||
Future<void> setWaktuPenyiraman({
|
||||
String? waktu1,
|
||||
String? waktu2,
|
||||
bool? waktuEnabled,
|
||||
}) async {
|
||||
try {
|
||||
final updates = <String, dynamic>{};
|
||||
if (waktu1 != null) updates['waktu_1'] = waktu1;
|
||||
if (waktu2 != null) updates['waktu_2'] = waktu2;
|
||||
if (waktuEnabled != null) updates['waktu'] = waktuEnabled;
|
||||
await _database.child('kontrol').update(updates);
|
||||
} catch (e) {
|
||||
print('Error setting waktu: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set durasi penyiraman
|
||||
Future<void> setDurasi({int? durasi1, int? durasi2}) async {
|
||||
try {
|
||||
final updates = <String, dynamic>{};
|
||||
if (durasi1 != null) updates['durasi_1'] = durasi1;
|
||||
if (durasi2 != null) updates['durasi_2'] = durasi2;
|
||||
await _database.child('kontrol').update(updates);
|
||||
} catch (e) {
|
||||
print('Error setting durasi: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle mode otomatis
|
||||
Future<void> setOtomatis(bool value) async {
|
||||
try {
|
||||
await _database.child('kontrol/otomatis').set(value);
|
||||
} catch (e) {
|
||||
print('Error setting otomatis: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== HELPER FUNCTIONS ====================
|
||||
|
||||
/// Parse value dari Firebase (handle empty string)
|
||||
String _parseValue(dynamic value) {
|
||||
if (value == null || value == '') {
|
||||
return '0';
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/// Get database reference (untuk advanced usage)
|
||||
DatabaseReference get databaseRef => _database;
|
||||
|
||||
/// Check connection status
|
||||
Stream<bool> getConnectionStatus() {
|
||||
return _database.child('.info/connected').onValue.map((event) {
|
||||
return event.snapshot.value as bool? ?? false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,14 @@ import Foundation
|
|||
|
||||
import firebase_auth
|
||||
import firebase_core
|
||||
import firebase_database
|
||||
import google_sign_in_ios
|
||||
import shared_preferences_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin"))
|
||||
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
|
|
|
|||
24
pubspec.lock
24
pubspec.lock
|
|
@ -137,6 +137,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.24.1"
|
||||
firebase_database:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_database
|
||||
sha256: "35b37c04307b99c5f746387ce03292531c3aa1de91facffbd9cff5e069a8b5fd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.3.10"
|
||||
firebase_database_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_database_platform_interface
|
||||
sha256: "095342e96d94b486b8273afc6327f777d53b63a169bd4201e5153ee3b8210c11"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.6+10"
|
||||
firebase_database_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_database_web
|
||||
sha256: "05f9b871d97b3ca879937947d0728ea95294395e7ddd5685583e8662be99eb16"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.6+16"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ dependencies:
|
|||
rename: ^3.1.0
|
||||
firebase_core: ^3.8.1
|
||||
firebase_auth: ^5.3.4
|
||||
firebase_database: ^11.1.8
|
||||
google_sign_in: ^6.2.1
|
||||
shared_preferences: ^2.2.2
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue