done
This commit is contained in:
parent
6ded76c325
commit
0efd3de196
|
@ -1 +1,2 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '12.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_ios_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'firebase_service.dart';
|
||||
import 'history_page.dart';
|
||||
|
||||
class DashboardPage extends StatefulWidget {
|
||||
|
@ -19,8 +21,9 @@ class _DashboardPageState extends State<DashboardPage>
|
|||
int _selectedIndex = 0;
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _fadeAnim;
|
||||
final FirebaseService _firebaseService = FirebaseService();
|
||||
|
||||
List<HistoryEntry> history = [];
|
||||
List<Map<String, dynamic>> history = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -34,6 +37,43 @@ class _DashboardPageState extends State<DashboardPage>
|
|||
curve: Curves.easeOutCubic,
|
||||
);
|
||||
_controller.forward();
|
||||
|
||||
// Listen to sensor data
|
||||
_firebaseService.getSensorData().listen((data) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
temperature = data['temperature'] ?? 0.0;
|
||||
humidity = data['humidity'] ?? 0.0;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Listen to fan status from status/kipas
|
||||
_firebaseService.getFanStatus().listen((status) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
fanOn = status;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Listen to pump status from status/pompa
|
||||
_firebaseService.getPumpStatus().listen((status) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
pumpOn = status;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Listen to history data
|
||||
_firebaseService.getHistory().listen((data) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
history = data;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -51,19 +91,178 @@ class _DashboardPageState extends State<DashboardPage>
|
|||
}
|
||||
|
||||
void _addHistory() {
|
||||
history.add(HistoryEntry(
|
||||
time: DateTime.now(),
|
||||
temperature: temperature,
|
||||
humidity: humidity,
|
||||
fanOn: fanOn,
|
||||
pumpOn: pumpOn,
|
||||
));
|
||||
final historyData = {
|
||||
'temperature': temperature,
|
||||
'humidity': humidity,
|
||||
'fanOn': fanOn,
|
||||
'pumpOn': pumpOn,
|
||||
};
|
||||
_firebaseService.addHistory(historyData);
|
||||
}
|
||||
|
||||
// Update the fan toggle handler
|
||||
void _toggleFan() {
|
||||
final newStatus = !fanOn;
|
||||
setState(() {
|
||||
fanOn = newStatus;
|
||||
});
|
||||
// Update status/kipas to match the control state
|
||||
_firebaseService.updateFanStatus(newStatus);
|
||||
_addHistory();
|
||||
}
|
||||
|
||||
// Update the pump toggle handler
|
||||
void _togglePump() {
|
||||
final newStatus = !pumpOn;
|
||||
setState(() {
|
||||
pumpOn = newStatus;
|
||||
});
|
||||
// Update status/pompa to match the control state
|
||||
_firebaseService.updatePumpStatus(newStatus);
|
||||
_addHistory();
|
||||
}
|
||||
|
||||
Widget _buildControlButton({
|
||||
required bool isOn,
|
||||
required VoidCallback onTap,
|
||||
required String label,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
}) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 28),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
AnimatedContainer(
|
||||
duration: Duration(milliseconds: 200),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: isOn ? color : Colors.grey[200],
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: (isOn ? color : Colors.grey[300]!).withOpacity(
|
||||
0.3,
|
||||
),
|
||||
blurRadius: 4,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
isOn ? 'ON' : 'OFF',
|
||||
style: TextStyle(
|
||||
color: isOn ? Colors.white : Colors.grey[600],
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSensorCard({
|
||||
required String label,
|
||||
required double value,
|
||||
required String unit,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
}) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 28),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'${value.toStringAsFixed(1)} $unit',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDashboard(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final width = size.width;
|
||||
final height = size.height;
|
||||
|
||||
// Palet warna
|
||||
const greenPrimary = Color(0xFF1CB56B);
|
||||
const greenGradientStart = Color(0xFF43EA7A);
|
||||
|
@ -73,20 +272,14 @@ class _DashboardPageState extends State<DashboardPage>
|
|||
final time = TimeOfDay.now().format(context);
|
||||
|
||||
// Header
|
||||
final headerHeight = height * 0.17;
|
||||
final logoSize = width * 0.10;
|
||||
final welcomeFont = width * 0.045;
|
||||
final timeFont = width * 0.032;
|
||||
final headerHeight = height * 0.15;
|
||||
final logoSize = width * 0.08;
|
||||
final welcomeFont = width * 0.04;
|
||||
final timeFont = width * 0.03;
|
||||
|
||||
// Grid
|
||||
final gridPadding = width * 0.04;
|
||||
final gridSpacing = width * 0.03;
|
||||
final cardRadius = 18.0;
|
||||
final cardElevation = 0.0;
|
||||
final cardFont = width * 0.045;
|
||||
final cardIcon = width * 0.11;
|
||||
final buttonFont = width * 0.038;
|
||||
final buttonPad = width * 0.03;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: greenBg,
|
||||
|
@ -94,508 +287,203 @@ class _DashboardPageState extends State<DashboardPage>
|
|||
index: _selectedIndex,
|
||||
children: [
|
||||
// Dashboard utama
|
||||
Column(
|
||||
children: [
|
||||
// Header
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: headerHeight,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/greenhouse.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(32),
|
||||
bottomRight: Radius.circular(32),
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// Header
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: headerHeight,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.black.withOpacity(0.5),
|
||||
Colors.black.withOpacity(0.3),
|
||||
Colors.black.withOpacity(0.2),
|
||||
],
|
||||
stops: [0.0, 0.5, 1.0],
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/greenhouse.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(32),
|
||||
bottomRight: Radius.circular(32),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 24, top: 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
width: 1,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.black.withOpacity(0.5),
|
||||
Colors.black.withOpacity(0.3),
|
||||
Colors.black.withOpacity(0.2),
|
||||
],
|
||||
stops: [0.0, 0.5, 1.0],
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(32),
|
||||
bottomRight: Radius.circular(32),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20, top: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Welcome',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: welcomeFont,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'Welcome',
|
||||
style: TextStyle(
|
||||
SizedBox(height: 8),
|
||||
Container(
|
||||
width: logoSize,
|
||||
height: logoSize,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: welcomeFont * 1.1,
|
||||
letterSpacing: 1.2,
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: Offset(1, 1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.15),
|
||||
blurRadius: 8,
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
Container(
|
||||
width: logoSize,
|
||||
height: logoSize,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.15),
|
||||
blurRadius: 12,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 5,
|
||||
sigmaY: 5,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(logoSize * 0.13),
|
||||
child: Image.asset('assets/logo.png'),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 5,
|
||||
sigmaY: 5,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(
|
||||
logoSize * 0.2,
|
||||
),
|
||||
child: Image.asset('assets/logo.png'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(width: logoSize * 0.5),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Jam di luar header card, rata kanan
|
||||
Positioned(
|
||||
right: 24,
|
||||
top: headerHeight - (timeFont * 2.5),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
width: 1,
|
||||
const Spacer(),
|
||||
SizedBox(width: logoSize * 0.5),
|
||||
],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.access_time_rounded,
|
||||
color: Colors.white,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
time,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: timeFont,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 1.1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: gridPadding),
|
||||
// Grid 2x2
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: gridPadding),
|
||||
child: GridView.count(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: gridSpacing,
|
||||
crossAxisSpacing: gridSpacing,
|
||||
childAspectRatio: 1,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
// Temperature
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final cardW = constraints.maxWidth;
|
||||
final cardH = constraints.maxHeight;
|
||||
final iconSize = cardW * 0.22;
|
||||
final labelFont = cardW * 0.10;
|
||||
final valueFont = cardW * 0.12;
|
||||
final pad = cardH * 0.05;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
// Jam di luar header card, rata kanan
|
||||
Positioned(
|
||||
right: 20,
|
||||
top: headerHeight - (timeFont * 2.5),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.access_time_rounded,
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(cardRadius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 3),
|
||||
),
|
||||
],
|
||||
size: 16,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(cardRadius),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(pad),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(iconSize * 0.18),
|
||||
decoration: BoxDecoration(
|
||||
color: blueTemp.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.thermostat_rounded,
|
||||
color: blueTemp,
|
||||
size: iconSize,
|
||||
),
|
||||
),
|
||||
SizedBox(height: pad * 0.7),
|
||||
Text(
|
||||
'Temperature',
|
||||
style: TextStyle(
|
||||
fontSize: labelFont,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: blueTemp,
|
||||
),
|
||||
),
|
||||
SizedBox(height: pad * 0.3),
|
||||
Text(
|
||||
'${temperature.toStringAsFixed(1)} °C',
|
||||
style: TextStyle(
|
||||
fontSize: valueFont,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: blueTemp,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
time,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: timeFont,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
// Humidity
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final cardW = constraints.maxWidth;
|
||||
final cardH = constraints.maxHeight;
|
||||
final iconSize = cardW * 0.22;
|
||||
final labelFont = cardW * 0.10;
|
||||
final valueFont = cardW * 0.12;
|
||||
final pad = cardH * 0.05;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(cardRadius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(cardRadius),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(pad),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(iconSize * 0.18),
|
||||
decoration: BoxDecoration(
|
||||
color: blueHumidity.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.water_drop_rounded,
|
||||
color: blueHumidity,
|
||||
size: iconSize,
|
||||
),
|
||||
),
|
||||
SizedBox(height: pad * 0.7),
|
||||
Text(
|
||||
'Humidity',
|
||||
style: TextStyle(
|
||||
fontSize: labelFont,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: blueHumidity,
|
||||
),
|
||||
),
|
||||
SizedBox(height: pad * 0.3),
|
||||
Text(
|
||||
'${humidity.toStringAsFixed(1)} %',
|
||||
style: TextStyle(
|
||||
fontSize: valueFont,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: blueHumidity,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// Kipas
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final cardW = constraints.maxWidth;
|
||||
final cardH = constraints.maxHeight;
|
||||
final iconSize = cardW * 0.22;
|
||||
final labelFont = cardW * 0.10;
|
||||
final valueFont = cardW * 0.12;
|
||||
final pad = cardH * 0.05;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(cardRadius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(cardRadius),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(pad),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(iconSize * 0.18),
|
||||
decoration: BoxDecoration(
|
||||
color: greenPrimary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.ac_unit_rounded,
|
||||
color: greenPrimary,
|
||||
size: iconSize,
|
||||
),
|
||||
),
|
||||
SizedBox(height: pad * 0.7),
|
||||
Text(
|
||||
'Kipas',
|
||||
style: TextStyle(
|
||||
fontSize: labelFont,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: greenPrimary,
|
||||
),
|
||||
),
|
||||
SizedBox(height: pad * 0.5),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
fanOn = !fanOn;
|
||||
_addHistory();
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: pad * 1.2,
|
||||
vertical: pad * 0.7,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: fanOn ? greenPrimary : Colors.grey[200],
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: (fanOn ? greenPrimary : Colors.grey[300]!).withOpacity(0.3),
|
||||
blurRadius: 6,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
fanOn ? 'ON' : 'OFF',
|
||||
style: TextStyle(
|
||||
color: fanOn ? Colors.white : Colors.grey[600],
|
||||
fontSize: labelFont * 0.8,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// Pompa
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final cardW = constraints.maxWidth;
|
||||
final cardH = constraints.maxHeight;
|
||||
final iconSize = cardW * 0.22;
|
||||
final labelFont = cardW * 0.10;
|
||||
final valueFont = cardW * 0.12;
|
||||
final pad = cardH * 0.05;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(cardRadius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(cardRadius),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(pad),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(iconSize * 0.18),
|
||||
decoration: BoxDecoration(
|
||||
color: greenPrimary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.water_rounded,
|
||||
color: greenPrimary,
|
||||
size: iconSize,
|
||||
),
|
||||
),
|
||||
SizedBox(height: pad * 0.7),
|
||||
Text(
|
||||
'Pompa',
|
||||
style: TextStyle(
|
||||
fontSize: labelFont,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: greenPrimary,
|
||||
),
|
||||
),
|
||||
SizedBox(height: pad * 0.5),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
pumpOn = !pumpOn;
|
||||
_addHistory();
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: pad * 1.2,
|
||||
vertical: pad * 0.7,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: pumpOn ? greenPrimary : Colors.grey[200],
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: (pumpOn ? greenPrimary : Colors.grey[300]!).withOpacity(0.3),
|
||||
blurRadius: 6,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
pumpOn ? 'ON' : 'OFF',
|
||||
style: TextStyle(
|
||||
color: pumpOn ? Colors.white : Colors.grey[600],
|
||||
fontSize: labelFont * 0.8,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: gridPadding),
|
||||
// Grid 2x2
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: gridPadding),
|
||||
child: GridView.count(
|
||||
crossAxisCount: 2,
|
||||
mainAxisSpacing: gridSpacing,
|
||||
crossAxisSpacing: gridSpacing,
|
||||
childAspectRatio: 1,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
// Temperature
|
||||
_buildSensorCard(
|
||||
label: 'Temperature',
|
||||
value: temperature,
|
||||
unit: '°C',
|
||||
icon: Icons.thermostat_rounded,
|
||||
color: blueTemp,
|
||||
),
|
||||
// Humidity
|
||||
_buildSensorCard(
|
||||
label: 'Humidity',
|
||||
value: humidity,
|
||||
unit: '%',
|
||||
icon: Icons.water_drop_rounded,
|
||||
color: blueHumidity,
|
||||
),
|
||||
// Fan Control
|
||||
_buildControlButton(
|
||||
isOn: fanOn,
|
||||
onTap: _toggleFan,
|
||||
label: 'Kipas',
|
||||
icon: Icons.ac_unit_rounded,
|
||||
color: greenPrimary,
|
||||
),
|
||||
// Pump Control
|
||||
_buildControlButton(
|
||||
isOn: pumpOn,
|
||||
onTap: _togglePump,
|
||||
label: 'Pompa',
|
||||
icon: Icons.water_rounded,
|
||||
color: greenPrimary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
// History page
|
||||
HistoryPage(history: history),
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import 'package:firebase_core/firebase_core.dart';
|
||||
|
||||
class DefaultFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
return const FirebaseOptions(
|
||||
apiKey: 'AIzaSyBB40eRLlP7kp52g2Hvmx3PkrkzYjuFnpo',
|
||||
appId: '1:350710840656:android:d577c4c2427682b2408055',
|
||||
messagingSenderId: '350710840656',
|
||||
projectId: 'smartfarming-7bd96',
|
||||
databaseURL: 'https://smartfarming-7bd96-default-rtdb.firebaseio.com',
|
||||
storageBucket: 'smartfarming-7bd96.firebasestorage.app',
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import 'package:firebase_database/firebase_database.dart';
|
||||
|
||||
class FirebaseService {
|
||||
final DatabaseReference _database = FirebaseDatabase.instance.ref();
|
||||
|
||||
// Stream untuk mendapatkan data sensor DHT11
|
||||
Stream<Map<String, dynamic>> getSensorData() {
|
||||
return _database.child('dht11').onValue.map((event) {
|
||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||
if (data == null) return {};
|
||||
|
||||
return {
|
||||
'temperature': data['suhu']?.toDouble() ?? 0.0,
|
||||
'humidity': data['humidity']?.toDouble() ?? 0.0,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Stream untuk mendapatkan status kipas dari status/kipas
|
||||
Stream<bool> getFanStatus() {
|
||||
return _database.child('status/kipas').onValue.map((event) {
|
||||
final data = event.snapshot.value as bool?;
|
||||
return data ?? false;
|
||||
});
|
||||
}
|
||||
|
||||
// Stream untuk mendapatkan status pompa dari status/pompa
|
||||
Stream<bool> getPumpStatus() {
|
||||
return _database.child('status/pompa').onValue.map((event) {
|
||||
final data = event.snapshot.value as bool?;
|
||||
return data ?? false;
|
||||
});
|
||||
}
|
||||
|
||||
// Update status kipas langsung ke status/kipas dengan boolean
|
||||
Future<void> updateFanStatus(bool status) async {
|
||||
await _database.child('status/kipas').set(status);
|
||||
}
|
||||
|
||||
// Update status pompa langsung ke status/pompa dengan boolean
|
||||
Future<void> updatePumpStatus(bool status) async {
|
||||
await _database.child('status/pompa').set(status);
|
||||
}
|
||||
|
||||
// Stream untuk mendapatkan riwayat
|
||||
Stream<List<Map<String, dynamic>>> getHistory() {
|
||||
return _database.child('riwayat').onValue.map((event) {
|
||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||
if (data == null) return [];
|
||||
|
||||
List<Map<String, dynamic>> historyList = [];
|
||||
data.forEach((key, value) {
|
||||
if (value is Map) {
|
||||
historyList.add({
|
||||
'id': key,
|
||||
'temperature': value['temperature']?.toDouble() ?? 0.0,
|
||||
'humidity': value['humidity']?.toDouble() ?? 0.0,
|
||||
'fanOn': value['fanOn'] ?? false,
|
||||
'pumpOn': value['pumpOn'] ?? false,
|
||||
'timestamp': value['timestamp'] ?? 0,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Sort by timestamp descending (newest first)
|
||||
historyList.sort((a, b) => (b['timestamp'] as int).compareTo(a['timestamp'] as int));
|
||||
return historyList;
|
||||
});
|
||||
}
|
||||
|
||||
// Tambah riwayat
|
||||
Future<void> addHistory(Map<String, dynamic> data) async {
|
||||
await _database.child('riwayat').push().set({
|
||||
...data,
|
||||
'timestamp': ServerValue.timestamp,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,27 +1,22 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class HistoryEntry {
|
||||
final DateTime time;
|
||||
final double temperature;
|
||||
final double humidity;
|
||||
final bool fanOn;
|
||||
final bool pumpOn;
|
||||
HistoryEntry({
|
||||
required this.time,
|
||||
required this.temperature,
|
||||
required this.humidity,
|
||||
required this.fanOn,
|
||||
required this.pumpOn,
|
||||
});
|
||||
}
|
||||
|
||||
class HistoryPage extends StatelessWidget {
|
||||
final List<HistoryEntry> history;
|
||||
class HistoryPage extends StatefulWidget {
|
||||
final List<Map<String, dynamic>> history;
|
||||
const HistoryPage({Key? key, required this.history}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<HistoryPage> createState() => _HistoryPageState();
|
||||
}
|
||||
|
||||
class _HistoryPageState extends State<HistoryPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
// Sort history by timestamp descending (newest first)
|
||||
final sortedHistory = List<Map<String, dynamic>>.from(widget.history)
|
||||
..sort((a, b) => (b['timestamp'] as int).compareTo(a['timestamp'] as int));
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF6FFF9),
|
||||
body: Column(
|
||||
|
@ -31,7 +26,7 @@ class HistoryPage extends StatelessWidget {
|
|||
width: double.infinity,
|
||||
height: 110,
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: const DecorationImage(
|
||||
image: AssetImage('assets/greenhouse.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
|
@ -50,7 +45,7 @@ class HistoryPage extends StatelessWidget {
|
|||
Colors.black.withOpacity(0.3),
|
||||
Colors.black.withOpacity(0.2),
|
||||
],
|
||||
stops: [0.0, 0.5, 1.0],
|
||||
stops: const [0.0, 0.5, 1.0],
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(32),
|
||||
|
@ -60,7 +55,7 @@ class HistoryPage extends StatelessWidget {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.history_rounded, color: Colors.white, size: 32),
|
||||
const Icon(Icons.history_rounded, color: Colors.white, size: 32),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Riwayat Data',
|
||||
|
@ -79,21 +74,21 @@ class HistoryPage extends StatelessWidget {
|
|||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child:
|
||||
history.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
'Belum ada riwayat data',
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||
),
|
||||
)
|
||||
: Card(
|
||||
elevation: 6,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: widget.history.isEmpty
|
||||
? const Center(
|
||||
child: Text(
|
||||
'Belum ada riwayat data',
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||
),
|
||||
)
|
||||
: Card(
|
||||
elevation: 6,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SingleChildScrollView(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: DataTable(
|
||||
|
@ -105,14 +100,14 @@ class HistoryPage extends StatelessWidget {
|
|||
color: Color(0xFF1CB56B),
|
||||
fontSize: 15,
|
||||
),
|
||||
dataRowColor: MaterialStateProperty.resolveWith<
|
||||
Color?
|
||||
>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return Colors.green.withOpacity(0.15);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
dataRowColor: MaterialStateProperty.resolveWith<Color?>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return Colors.green.withOpacity(0.15);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
columns: const [
|
||||
DataColumn(label: Text('Waktu')),
|
||||
DataColumn(label: Text('Temperature')),
|
||||
|
@ -120,33 +115,35 @@ class HistoryPage extends StatelessWidget {
|
|||
DataColumn(label: Text('Kipas')),
|
||||
DataColumn(label: Text('Pompa')),
|
||||
],
|
||||
rows: List.generate(history.length, (i) {
|
||||
final h = history[history.length - 1 - i];
|
||||
rows: List.generate(sortedHistory.length, (i) {
|
||||
final h = sortedHistory[i];
|
||||
final timestamp = h['timestamp'] as int;
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
final formattedDate = DateFormat('dd/MM/yyyy HH:mm').format(date);
|
||||
final isEven = i % 2 == 0;
|
||||
|
||||
return DataRow(
|
||||
color: MaterialStateProperty.all(
|
||||
isEven
|
||||
? Colors.white
|
||||
: const Color(0xFFF1F8E9),
|
||||
isEven ? Colors.white : const Color(0xFFF1F8E9),
|
||||
),
|
||||
cells: [
|
||||
DataCell(
|
||||
Text(
|
||||
'${h.time.hour.toString().padLeft(2, '0')}:${h.time.minute.toString().padLeft(2, '0')}:${h.time.second.toString().padLeft(2, '0')}\n${h.time.day}/${h.time.month}/${h.time.year}',
|
||||
formattedDate,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
const Icon(
|
||||
Icons.thermostat,
|
||||
color: Color(0xFF4FC3F7),
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${h.temperature.toStringAsFixed(1)} °C',
|
||||
'${(h['temperature'] as num).toStringAsFixed(1)} °C',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
),
|
||||
|
@ -157,14 +154,14 @@ class HistoryPage extends StatelessWidget {
|
|||
DataCell(
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
const Icon(
|
||||
Icons.water_drop,
|
||||
color: Color(0xFF0288D1),
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${h.humidity.toStringAsFixed(1)} %',
|
||||
'${(h['humidity'] as num).toStringAsFixed(1)} %',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
),
|
||||
|
@ -179,23 +176,19 @@ class HistoryPage extends StatelessWidget {
|
|||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
h.fanOn
|
||||
? const Color(0xFFB9F6CA)
|
||||
: const Color(0xFFFFCDD2),
|
||||
borderRadius: BorderRadius.circular(
|
||||
12,
|
||||
),
|
||||
color: h['fanOn'] == true
|
||||
? const Color(0xFFB9F6CA)
|
||||
: const Color(0xFFFFCDD2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
h.fanOn ? 'ON' : 'OFF',
|
||||
h['fanOn'] == true ? 'ON' : 'OFF',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
h.fanOn
|
||||
? Colors.green[800]
|
||||
: Colors.red[800],
|
||||
color: h['fanOn'] == true
|
||||
? Colors.green[800]
|
||||
: Colors.red[800],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -207,23 +200,19 @@ class HistoryPage extends StatelessWidget {
|
|||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
h.pumpOn
|
||||
? const Color(0xFFB9F6CA)
|
||||
: const Color(0xFFFFCDD2),
|
||||
borderRadius: BorderRadius.circular(
|
||||
12,
|
||||
),
|
||||
color: h['pumpOn'] == true
|
||||
? const Color(0xFFB9F6CA)
|
||||
: const Color(0xFFFFCDD2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
h.pumpOn ? 'ON' : 'OFF',
|
||||
h['pumpOn'] == true ? 'ON' : 'OFF',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
h.pumpOn
|
||||
? Colors.green[800]
|
||||
: Colors.red[800],
|
||||
color: h['pumpOn'] == true
|
||||
? Colors.green[800]
|
||||
: Colors.red[800],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -235,6 +224,7 @@ class HistoryPage extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'splash_screen.dart';
|
||||
import 'dashboard.dart';
|
||||
import 'firebase_options.dart';
|
||||
|
||||
void main() {
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
|
@ -13,16 +19,17 @@ class MyApp extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
initialRoute: '/',
|
||||
routes: {
|
||||
'/': (context) => const SplashScreen(),
|
||||
'/home': (context) => const DashboardPage(),
|
||||
},
|
||||
);
|
||||
title: 'Smart Farm',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
|
||||
useMaterial3: true,
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
initialRoute: '/',
|
||||
routes: {
|
||||
'/': (context) => const SplashScreen(),
|
||||
'/home': (context) => const DashboardPage(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import cloud_firestore
|
||||
import firebase_core
|
||||
import firebase_database
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin"))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
platform :osx, '10.14'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_macos_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
|
||||
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
end
|
||||
end
|
111
pubspec.lock
111
pubspec.lock
|
@ -1,6 +1,14 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.35"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -33,6 +41,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
cloud_firestore:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cloud_firestore
|
||||
sha256: a0f161b92610e078b4962d7e6ebeb66dc9cce0ada3514aeee442f68165d78185
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.17.5"
|
||||
cloud_firestore_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cloud_firestore_platform_interface
|
||||
sha256: "6a55b319f8d33c307396b9104512e8130a61904528ab7bd8b5402678fca54b81"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.5"
|
||||
cloud_firestore_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cloud_firestore_web
|
||||
sha256: "89dfa1304d3da48b3039abbb2865e3d30896ef858e569a16804a99f4362283a9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.12.5"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -57,6 +89,54 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.32.0"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_platform_interface
|
||||
sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.0"
|
||||
firebase_core_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
sha256: ddd72baa6f727e5b23f32d9af23d7d453d67946f380bd9c21daf474ee0f7326e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.23.0"
|
||||
firebase_database:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_database
|
||||
sha256: "3b9ca306d26ad243ccbc4c717ff6e8563a080ebe11ee77fa7349b419c894b42d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.5.7"
|
||||
firebase_database_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_database_platform_interface
|
||||
sha256: "5864cc362275465e9bd682b243f19419c9d78b861c2db820241eea596ae3b320"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.5+35"
|
||||
firebase_database_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_database_web
|
||||
sha256: a6008395dd20e8b8dde0691b441c181a1216c3866f89f48dcb6889d34fd35905
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.5+7"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -75,6 +155,19 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -139,6 +232,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -208,6 +309,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.3.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
sdks:
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
flutter: ">=3.22.0"
|
||||
|
|
|
@ -35,6 +35,12 @@ dependencies:
|
|||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
|
||||
# Firebase
|
||||
firebase_core: ^2.24.2
|
||||
firebase_database: ^10.3.8
|
||||
cloud_firestore: ^4.14.0
|
||||
intl: ^0.20.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
|
|
@ -6,6 +6,12 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <cloud_firestore/cloud_firestore_plugin_c_api.h>
|
||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
CloudFirestorePluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("CloudFirestorePluginCApi"));
|
||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
cloud_firestore
|
||||
firebase_core
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
|
Loading…
Reference in New Issue