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"
|
#include "Generated.xcconfig"
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
#include "Generated.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 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'firebase_service.dart';
|
||||||
import 'history_page.dart';
|
import 'history_page.dart';
|
||||||
|
|
||||||
class DashboardPage extends StatefulWidget {
|
class DashboardPage extends StatefulWidget {
|
||||||
|
@ -19,8 +21,9 @@ class _DashboardPageState extends State<DashboardPage>
|
||||||
int _selectedIndex = 0;
|
int _selectedIndex = 0;
|
||||||
late AnimationController _controller;
|
late AnimationController _controller;
|
||||||
late Animation<double> _fadeAnim;
|
late Animation<double> _fadeAnim;
|
||||||
|
final FirebaseService _firebaseService = FirebaseService();
|
||||||
|
|
||||||
List<HistoryEntry> history = [];
|
List<Map<String, dynamic>> history = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -34,6 +37,43 @@ class _DashboardPageState extends State<DashboardPage>
|
||||||
curve: Curves.easeOutCubic,
|
curve: Curves.easeOutCubic,
|
||||||
);
|
);
|
||||||
_controller.forward();
|
_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
|
@override
|
||||||
|
@ -51,19 +91,178 @@ class _DashboardPageState extends State<DashboardPage>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addHistory() {
|
void _addHistory() {
|
||||||
history.add(HistoryEntry(
|
final historyData = {
|
||||||
time: DateTime.now(),
|
'temperature': temperature,
|
||||||
temperature: temperature,
|
'humidity': humidity,
|
||||||
humidity: humidity,
|
'fanOn': fanOn,
|
||||||
fanOn: fanOn,
|
'pumpOn': pumpOn,
|
||||||
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) {
|
Widget _buildDashboard(BuildContext context) {
|
||||||
final size = MediaQuery.of(context).size;
|
final size = MediaQuery.of(context).size;
|
||||||
final width = size.width;
|
final width = size.width;
|
||||||
final height = size.height;
|
final height = size.height;
|
||||||
|
|
||||||
// Palet warna
|
// Palet warna
|
||||||
const greenPrimary = Color(0xFF1CB56B);
|
const greenPrimary = Color(0xFF1CB56B);
|
||||||
const greenGradientStart = Color(0xFF43EA7A);
|
const greenGradientStart = Color(0xFF43EA7A);
|
||||||
|
@ -73,20 +272,14 @@ class _DashboardPageState extends State<DashboardPage>
|
||||||
final time = TimeOfDay.now().format(context);
|
final time = TimeOfDay.now().format(context);
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
final headerHeight = height * 0.17;
|
final headerHeight = height * 0.15;
|
||||||
final logoSize = width * 0.10;
|
final logoSize = width * 0.08;
|
||||||
final welcomeFont = width * 0.045;
|
final welcomeFont = width * 0.04;
|
||||||
final timeFont = width * 0.032;
|
final timeFont = width * 0.03;
|
||||||
|
|
||||||
// Grid
|
// Grid
|
||||||
final gridPadding = width * 0.04;
|
final gridPadding = width * 0.04;
|
||||||
final gridSpacing = width * 0.03;
|
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(
|
return Scaffold(
|
||||||
backgroundColor: greenBg,
|
backgroundColor: greenBg,
|
||||||
|
@ -94,508 +287,203 @@ class _DashboardPageState extends State<DashboardPage>
|
||||||
index: _selectedIndex,
|
index: _selectedIndex,
|
||||||
children: [
|
children: [
|
||||||
// Dashboard utama
|
// Dashboard utama
|
||||||
Column(
|
SafeArea(
|
||||||
children: [
|
child: Column(
|
||||||
// Header
|
children: [
|
||||||
Stack(
|
// Header
|
||||||
children: [
|
Stack(
|
||||||
Container(
|
children: [
|
||||||
width: double.infinity,
|
Container(
|
||||||
height: headerHeight,
|
width: double.infinity,
|
||||||
decoration: BoxDecoration(
|
height: headerHeight,
|
||||||
image: DecorationImage(
|
|
||||||
image: AssetImage('assets/greenhouse.jpg'),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
bottomLeft: Radius.circular(32),
|
|
||||||
bottomRight: Radius.circular(32),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
image: DecorationImage(
|
||||||
begin: Alignment.topLeft,
|
image: AssetImage('assets/greenhouse.jpg'),
|
||||||
end: Alignment.bottomRight,
|
fit: BoxFit.cover,
|
||||||
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(
|
borderRadius: const BorderRadius.only(
|
||||||
bottomLeft: Radius.circular(32),
|
bottomLeft: Radius.circular(32),
|
||||||
bottomRight: Radius.circular(32),
|
bottomRight: Radius.circular(32),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Container(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
decoration: BoxDecoration(
|
||||||
children: [
|
gradient: LinearGradient(
|
||||||
Padding(
|
begin: Alignment.topLeft,
|
||||||
padding: EdgeInsets.only(left: 24, top: 24),
|
end: Alignment.bottomRight,
|
||||||
child: Column(
|
colors: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Colors.black.withOpacity(0.5),
|
||||||
children: [
|
Colors.black.withOpacity(0.3),
|
||||||
Container(
|
Colors.black.withOpacity(0.2),
|
||||||
padding: EdgeInsets.symmetric(
|
],
|
||||||
horizontal: 12,
|
stops: [0.0, 0.5, 1.0],
|
||||||
vertical: 6,
|
),
|
||||||
),
|
borderRadius: const BorderRadius.only(
|
||||||
decoration: BoxDecoration(
|
bottomLeft: Radius.circular(32),
|
||||||
color: Colors.white.withOpacity(0.15),
|
bottomRight: Radius.circular(32),
|
||||||
borderRadius: BorderRadius.circular(20),
|
),
|
||||||
border: Border.all(
|
),
|
||||||
color: Colors.white.withOpacity(0.2),
|
child: Row(
|
||||||
width: 1,
|
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(
|
SizedBox(height: 8),
|
||||||
'Welcome',
|
Container(
|
||||||
style: TextStyle(
|
width: logoSize,
|
||||||
|
height: logoSize,
|
||||||
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.w500,
|
borderRadius: BorderRadius.circular(16),
|
||||||
fontSize: welcomeFont * 1.1,
|
boxShadow: [
|
||||||
letterSpacing: 1.2,
|
BoxShadow(
|
||||||
shadows: [
|
color: Colors.black.withOpacity(0.15),
|
||||||
Shadow(
|
|
||||||
offset: Offset(1, 1),
|
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
color: Colors.black.withOpacity(0.3),
|
offset: Offset(0, 2),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
child: ClipRRect(
|
||||||
),
|
borderRadius: BorderRadius.circular(16),
|
||||||
SizedBox(height: 12),
|
child: BackdropFilter(
|
||||||
Container(
|
filter: ImageFilter.blur(
|
||||||
width: logoSize,
|
sigmaX: 5,
|
||||||
height: logoSize,
|
sigmaY: 5,
|
||||||
decoration: BoxDecoration(
|
),
|
||||||
color: Colors.white,
|
child: Padding(
|
||||||
borderRadius: BorderRadius.circular(20),
|
padding: EdgeInsets.all(
|
||||||
boxShadow: [
|
logoSize * 0.2,
|
||||||
BoxShadow(
|
),
|
||||||
color: Colors.black.withOpacity(0.15),
|
child: Image.asset('assets/logo.png'),
|
||||||
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'),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
const Spacer(),
|
||||||
const Spacer(),
|
SizedBox(width: logoSize * 0.5),
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
// Jam di luar header card, rata kanan
|
||||||
],
|
Positioned(
|
||||||
),
|
right: 20,
|
||||||
SizedBox(height: gridPadding),
|
top: headerHeight - (timeFont * 2.5),
|
||||||
// Grid 2x2
|
child: Container(
|
||||||
Expanded(
|
padding: const EdgeInsets.symmetric(
|
||||||
child: Padding(
|
horizontal: 12,
|
||||||
padding: EdgeInsets.symmetric(horizontal: gridPadding),
|
vertical: 6,
|
||||||
child: GridView.count(
|
),
|
||||||
crossAxisCount: 2,
|
decoration: BoxDecoration(
|
||||||
mainAxisSpacing: gridSpacing,
|
color: Colors.white.withOpacity(0.15),
|
||||||
crossAxisSpacing: gridSpacing,
|
borderRadius: BorderRadius.circular(5),
|
||||||
childAspectRatio: 1,
|
border: Border.all(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
color: Colors.white.withOpacity(0.2),
|
||||||
children: [
|
width: 1,
|
||||||
// Temperature
|
),
|
||||||
LayoutBuilder(
|
),
|
||||||
builder: (context, constraints) {
|
child: Row(
|
||||||
final cardW = constraints.maxWidth;
|
mainAxisSize: MainAxisSize.min,
|
||||||
final cardH = constraints.maxHeight;
|
children: [
|
||||||
final iconSize = cardW * 0.22;
|
Icon(
|
||||||
final labelFont = cardW * 0.10;
|
Icons.access_time_rounded,
|
||||||
final valueFont = cardW * 0.12;
|
|
||||||
final pad = cardH * 0.05;
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(cardRadius),
|
size: 16,
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.05),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: Offset(0, 3),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
const SizedBox(width: 6),
|
||||||
borderRadius: BorderRadius.circular(cardRadius),
|
Text(
|
||||||
child: BackdropFilter(
|
time,
|
||||||
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
style: TextStyle(
|
||||||
child: Padding(
|
color: Colors.white,
|
||||||
padding: EdgeInsets.all(pad),
|
fontSize: timeFont,
|
||||||
child: Column(
|
fontWeight: FontWeight.w500,
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
// Humidity
|
),
|
||||||
LayoutBuilder(
|
],
|
||||||
builder: (context, constraints) {
|
),
|
||||||
final cardW = constraints.maxWidth;
|
SizedBox(height: gridPadding),
|
||||||
final cardH = constraints.maxHeight;
|
// Grid 2x2
|
||||||
final iconSize = cardW * 0.22;
|
Expanded(
|
||||||
final labelFont = cardW * 0.10;
|
child: Padding(
|
||||||
final valueFont = cardW * 0.12;
|
padding: EdgeInsets.symmetric(horizontal: gridPadding),
|
||||||
final pad = cardH * 0.05;
|
child: GridView.count(
|
||||||
return Container(
|
crossAxisCount: 2,
|
||||||
decoration: BoxDecoration(
|
mainAxisSpacing: gridSpacing,
|
||||||
color: Colors.white,
|
crossAxisSpacing: gridSpacing,
|
||||||
borderRadius: BorderRadius.circular(cardRadius),
|
childAspectRatio: 1,
|
||||||
boxShadow: [
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
BoxShadow(
|
children: [
|
||||||
color: Colors.black.withOpacity(0.05),
|
// Temperature
|
||||||
blurRadius: 10,
|
_buildSensorCard(
|
||||||
offset: Offset(0, 3),
|
label: 'Temperature',
|
||||||
),
|
value: temperature,
|
||||||
],
|
unit: '°C',
|
||||||
),
|
icon: Icons.thermostat_rounded,
|
||||||
child: ClipRRect(
|
color: blueTemp,
|
||||||
borderRadius: BorderRadius.circular(cardRadius),
|
),
|
||||||
child: BackdropFilter(
|
// Humidity
|
||||||
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
_buildSensorCard(
|
||||||
child: Padding(
|
label: 'Humidity',
|
||||||
padding: EdgeInsets.all(pad),
|
value: humidity,
|
||||||
child: Column(
|
unit: '%',
|
||||||
mainAxisSize: MainAxisSize.min,
|
icon: Icons.water_drop_rounded,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
color: blueHumidity,
|
||||||
children: [
|
),
|
||||||
Container(
|
// Fan Control
|
||||||
padding: EdgeInsets.all(iconSize * 0.18),
|
_buildControlButton(
|
||||||
decoration: BoxDecoration(
|
isOn: fanOn,
|
||||||
color: blueHumidity.withOpacity(0.1),
|
onTap: _toggleFan,
|
||||||
borderRadius: BorderRadius.circular(12),
|
label: 'Kipas',
|
||||||
),
|
icon: Icons.ac_unit_rounded,
|
||||||
child: Icon(
|
color: greenPrimary,
|
||||||
Icons.water_drop_rounded,
|
),
|
||||||
color: blueHumidity,
|
// Pump Control
|
||||||
size: iconSize,
|
_buildControlButton(
|
||||||
),
|
isOn: pumpOn,
|
||||||
),
|
onTap: _togglePump,
|
||||||
SizedBox(height: pad * 0.7),
|
label: 'Pompa',
|
||||||
Text(
|
icon: Icons.water_rounded,
|
||||||
'Humidity',
|
color: greenPrimary,
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
// History page
|
// History page
|
||||||
HistoryPage(history: history),
|
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:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class HistoryEntry {
|
class HistoryPage extends StatefulWidget {
|
||||||
final DateTime time;
|
final List<Map<String, dynamic>> history;
|
||||||
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;
|
|
||||||
const HistoryPage({Key? key, required this.history}) : super(key: key);
|
const HistoryPage({Key? key, required this.history}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HistoryPage> createState() => _HistoryPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistoryPageState extends State<HistoryPage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(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(
|
return Scaffold(
|
||||||
backgroundColor: const Color(0xFFF6FFF9),
|
backgroundColor: const Color(0xFFF6FFF9),
|
||||||
body: Column(
|
body: Column(
|
||||||
|
@ -31,7 +26,7 @@ class HistoryPage extends StatelessWidget {
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 110,
|
height: 110,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
image: DecorationImage(
|
image: const DecorationImage(
|
||||||
image: AssetImage('assets/greenhouse.jpg'),
|
image: AssetImage('assets/greenhouse.jpg'),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
|
@ -50,7 +45,7 @@ class HistoryPage extends StatelessWidget {
|
||||||
Colors.black.withOpacity(0.3),
|
Colors.black.withOpacity(0.3),
|
||||||
Colors.black.withOpacity(0.2),
|
Colors.black.withOpacity(0.2),
|
||||||
],
|
],
|
||||||
stops: [0.0, 0.5, 1.0],
|
stops: const [0.0, 0.5, 1.0],
|
||||||
),
|
),
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
bottomLeft: Radius.circular(32),
|
bottomLeft: Radius.circular(32),
|
||||||
|
@ -60,7 +55,7 @@ class HistoryPage extends StatelessWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.history_rounded, color: Colors.white, size: 32),
|
const Icon(Icons.history_rounded, color: Colors.white, size: 32),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text(
|
Text(
|
||||||
'Riwayat Data',
|
'Riwayat Data',
|
||||||
|
@ -79,21 +74,21 @@ class HistoryPage extends StatelessWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
child:
|
child: widget.history.isEmpty
|
||||||
history.isEmpty
|
? const Center(
|
||||||
? Center(
|
child: Text(
|
||||||
child: Text(
|
'Belum ada riwayat data',
|
||||||
'Belum ada riwayat data',
|
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
),
|
||||||
),
|
)
|
||||||
)
|
: Card(
|
||||||
: Card(
|
elevation: 6,
|
||||||
elevation: 6,
|
shape: RoundedRectangleBorder(
|
||||||
shape: RoundedRectangleBorder(
|
borderRadius: BorderRadius.circular(22),
|
||||||
borderRadius: BorderRadius.circular(22),
|
),
|
||||||
),
|
child: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.all(16),
|
||||||
padding: const EdgeInsets.all(16),
|
child: SingleChildScrollView(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: DataTable(
|
child: DataTable(
|
||||||
|
@ -105,14 +100,14 @@ class HistoryPage extends StatelessWidget {
|
||||||
color: Color(0xFF1CB56B),
|
color: Color(0xFF1CB56B),
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
),
|
),
|
||||||
dataRowColor: MaterialStateProperty.resolveWith<
|
dataRowColor: MaterialStateProperty.resolveWith<Color?>(
|
||||||
Color?
|
(Set<MaterialState> states) {
|
||||||
>((Set<MaterialState> states) {
|
if (states.contains(MaterialState.selected)) {
|
||||||
if (states.contains(MaterialState.selected)) {
|
return Colors.green.withOpacity(0.15);
|
||||||
return Colors.green.withOpacity(0.15);
|
}
|
||||||
}
|
return null;
|
||||||
return null;
|
},
|
||||||
}),
|
),
|
||||||
columns: const [
|
columns: const [
|
||||||
DataColumn(label: Text('Waktu')),
|
DataColumn(label: Text('Waktu')),
|
||||||
DataColumn(label: Text('Temperature')),
|
DataColumn(label: Text('Temperature')),
|
||||||
|
@ -120,33 +115,35 @@ class HistoryPage extends StatelessWidget {
|
||||||
DataColumn(label: Text('Kipas')),
|
DataColumn(label: Text('Kipas')),
|
||||||
DataColumn(label: Text('Pompa')),
|
DataColumn(label: Text('Pompa')),
|
||||||
],
|
],
|
||||||
rows: List.generate(history.length, (i) {
|
rows: List.generate(sortedHistory.length, (i) {
|
||||||
final h = history[history.length - 1 - 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;
|
final isEven = i % 2 == 0;
|
||||||
|
|
||||||
return DataRow(
|
return DataRow(
|
||||||
color: MaterialStateProperty.all(
|
color: MaterialStateProperty.all(
|
||||||
isEven
|
isEven ? Colors.white : const Color(0xFFF1F8E9),
|
||||||
? Colors.white
|
|
||||||
: const Color(0xFFF1F8E9),
|
|
||||||
),
|
),
|
||||||
cells: [
|
cells: [
|
||||||
DataCell(
|
DataCell(
|
||||||
Text(
|
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),
|
style: const TextStyle(fontSize: 13),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
DataCell(
|
DataCell(
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
const Icon(
|
||||||
Icons.thermostat,
|
Icons.thermostat,
|
||||||
color: Color(0xFF4FC3F7),
|
color: Color(0xFF4FC3F7),
|
||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'${h.temperature.toStringAsFixed(1)} °C',
|
'${(h['temperature'] as num).toStringAsFixed(1)} °C',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
),
|
),
|
||||||
|
@ -157,14 +154,14 @@ class HistoryPage extends StatelessWidget {
|
||||||
DataCell(
|
DataCell(
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
const Icon(
|
||||||
Icons.water_drop,
|
Icons.water_drop,
|
||||||
color: Color(0xFF0288D1),
|
color: Color(0xFF0288D1),
|
||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'${h.humidity.toStringAsFixed(1)} %',
|
'${(h['humidity'] as num).toStringAsFixed(1)} %',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
),
|
),
|
||||||
|
@ -179,23 +176,19 @@ class HistoryPage extends StatelessWidget {
|
||||||
vertical: 4,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color:
|
color: h['fanOn'] == true
|
||||||
h.fanOn
|
? const Color(0xFFB9F6CA)
|
||||||
? const Color(0xFFB9F6CA)
|
: const Color(0xFFFFCDD2),
|
||||||
: const Color(0xFFFFCDD2),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
12,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
h.fanOn ? 'ON' : 'OFF',
|
h['fanOn'] == true ? 'ON' : 'OFF',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color:
|
color: h['fanOn'] == true
|
||||||
h.fanOn
|
? Colors.green[800]
|
||||||
? Colors.green[800]
|
: Colors.red[800],
|
||||||
: Colors.red[800],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -207,23 +200,19 @@ class HistoryPage extends StatelessWidget {
|
||||||
vertical: 4,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color:
|
color: h['pumpOn'] == true
|
||||||
h.pumpOn
|
? const Color(0xFFB9F6CA)
|
||||||
? const Color(0xFFB9F6CA)
|
: const Color(0xFFFFCDD2),
|
||||||
: const Color(0xFFFFCDD2),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
12,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
h.pumpOn ? 'ON' : 'OFF',
|
h['pumpOn'] == true ? 'ON' : 'OFF',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color:
|
color: h['pumpOn'] == true
|
||||||
h.pumpOn
|
? Colors.green[800]
|
||||||
? Colors.green[800]
|
: Colors.red[800],
|
||||||
: Colors.red[800],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -235,6 +224,7 @@ class HistoryPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'splash_screen.dart';
|
import 'splash_screen.dart';
|
||||||
import 'dashboard.dart';
|
import 'dashboard.dart';
|
||||||
|
import 'firebase_options.dart';
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await Firebase.initializeApp(
|
||||||
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
);
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,16 +19,17 @@ class MyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Demo',
|
title: 'Smart Farm',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
|
||||||
),
|
useMaterial3: true,
|
||||||
debugShowCheckedModeBanner: false,
|
),
|
||||||
initialRoute: '/',
|
debugShowCheckedModeBanner: false,
|
||||||
routes: {
|
initialRoute: '/',
|
||||||
'/': (context) => const SplashScreen(),
|
routes: {
|
||||||
'/home': (context) => const DashboardPage(),
|
'/': (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"
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||||
|
|
|
@ -5,6 +5,12 @@
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import cloud_firestore
|
||||||
|
import firebase_core
|
||||||
|
import firebase_database
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
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
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_flutterfire_internals:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _flutterfire_internals
|
||||||
|
sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.35"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -33,6 +41,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
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:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -57,6 +89,54 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
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:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -75,6 +155,19 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -139,6 +232,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -208,6 +309,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.1"
|
version: "14.3.1"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.7.0 <4.0.0"
|
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.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.8
|
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:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
|
@ -6,6 +6,12 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#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) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
CloudFirestorePluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("CloudFirestorePluginCApi"));
|
||||||
|
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
cloud_firestore
|
||||||
|
firebase_core
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|
Loading…
Reference in New Issue