sistem pengukuran evaporasi home page

This commit is contained in:
Mochamad ongki ramadani 2026-02-12 22:59:01 +07:00
parent 7dbad70214
commit 9ec9beda7b
10 changed files with 2750 additions and 43 deletions

View File

@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:klimatologiot/blocs/authentication_bloc/authentication_bloc.dart';
// import 'package:flutter_bloc/flutter_bloc.dart';
// import 'package:klimatologiot/blocs/authentication_bloc/authentication_bloc.dart';
import 'package:klimatologiot/screens/auth/views/welcome_screen.dart';
import 'package:klimatologiot/screens/home/views/home_screen.dart';
import 'package:klimatologiot/screens/home/home_screen.dart';
class MyAppView extends StatelessWidget {
const MyAppView({super.key});

View File

@ -0,0 +1,474 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import '../monitoring/evaporasi_monitoring_screen.dart';
import '../monitoring/wind_speed_monitoring_screen.dart';
import '../monitoring/air_quality_monitoring_screen.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Monitoring Klimatologi"),
centerTitle: true,
),
/// 🔹 SIDEBAR (MENU 3 GARIS DI KIRI)
drawer: Drawer(
child: SafeArea(
child: ListView(
padding: EdgeInsets.zero,
children: [
// 🔹 HEADER DENGAN AKUN INFO
UserAccountsDrawerHeader(
accountName: const Text(
"Admin Klimatologi",
style: TextStyle(fontWeight: FontWeight.bold),
),
accountEmail: const Text("admin@klimatologi.com"),
currentAccountPicture: CircleAvatar(
backgroundColor: Colors.blue,
child: const Text(
"A",
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
// 🔹 DASHBOARD MENU
ListTile(
leading: const Icon(Icons.dashboard),
title: const Text("Dashboard"),
onTap: () {
Navigator.pop(context);
},
),
const Divider(),
// 🔹 MONITORING MENU
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
"Monitoring",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
),
_buildDrawerItem(
context,
"Evaporasi",
Icons.opacity,
),
_buildDrawerItem(
context,
"Kecepatan Angin",
Icons.air,
),
_buildDrawerItem(
context,
"Kualitas Udara",
Icons.cloud,
),
],
),
),
),
/// 🔹 DASHBOARD UTAMA - 3 MONITORING
body: Padding(
padding: const EdgeInsets.all(16),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Selamat Datang Admin Klimatologi",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
// 🔹 3 BLOCK DATA HARI INI
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildDataBlock(
"Evaporasi",
"45.2",
"mm",
Colors.blue,
Icons.opacity,
),
_buildDataBlock(
"Kec. Angin",
"12.5",
"km/h",
Colors.green,
Icons.air,
),
_buildDataBlock(
"Kualitas Udara",
"78",
"AQI",
Colors.orange,
Icons.cloud,
),
],
),
const SizedBox(height: 30),
const Text(
"Grafik Monitoring",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 15),
_buildDashboardCard(context, "Evaporasi"),
const SizedBox(height: 20),
_buildDashboardCard(context, "Kecepatan Angin"),
const SizedBox(height: 20),
_buildDashboardCard(context, "Kualitas Udara"),
],
),
),
),
);
}
/// DATA BLOCK - PREVIEW DATA HARI INI
Widget _buildDataBlock(
String title,
String value,
String unit,
Color color,
IconData icon,
) {
return Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color, width: 2),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(icon, color: color, size: 28),
const SizedBox(height: 8),
Text(
title,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 6),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
value,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: color,
),
),
const SizedBox(width: 4),
Text(
unit,
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade700,
),
),
],
),
],
),
),
);
}
/// DRAWER ITEM
Widget _buildDrawerItem(
BuildContext context,
String title,
IconData icon,
) {
return ListTile(
leading: Icon(icon),
title: Text(title),
onTap: () {
Navigator.pop(context);
Widget screen;
if (title == "Evaporasi") {
screen = const EvaporasiMonitoringScreen();
} else if (title == "Kecepatan Angin") {
screen = const WindSpeedMonitoringScreen();
} else if (title == "Kualitas Udara") {
screen = const AirQualityMonitoringScreen();
} else {
return;
}
Navigator.push(
context,
MaterialPageRoute(builder: (context) => screen),
);
},
);
}
/// DASHBOARD CARD - PREVIEW GRAFIK 24 JAM
Widget _buildDashboardCard(BuildContext context, String title) {
return GestureDetector(
onTap: () {
Widget screen;
if (title == "Evaporasi") {
screen = const EvaporasiMonitoringScreen();
} else if (title == "Kecepatan Angin") {
screen = const WindSpeedMonitoringScreen();
} else if (title == "Kualitas Udara") {
screen = const AirQualityMonitoringScreen();
} else {
return;
}
Navigator.push(
context,
MaterialPageRoute(builder: (context) => screen),
);
},
child: Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.green.shade100,
borderRadius: BorderRadius.circular(20),
),
child: const Text(
"24 Jam",
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.green,
),
),
),
],
),
const SizedBox(height: 16),
SizedBox(
height: 200,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawHorizontalLine: true,
drawVerticalLine: false,
horizontalInterval: 1,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.shade300,
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 3,
getTitlesWidget: (value, meta) {
return Text(
'${value.toInt()}h',
style: const TextStyle(
fontSize: 10,
color: Colors.grey,
),
);
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 1,
getTitlesWidget: (value, meta) {
return Text(
value.toInt().toString(),
style: const TextStyle(
fontSize: 9,
color: Colors.grey,
),
);
},
reservedSize: 28,
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border(
left: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
bottom: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
),
),
lineBarsData: [
LineChartBarData(
spots: _getMonitoringData(title),
isCurved: true,
barWidth: 2.5,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: _getColorByTitle(title),
strokeWidth: 0,
);
},
),
color: _getColorByTitle(title),
belowBarData: BarAreaData(
show: true,
color: _getColorByTitle(title).withOpacity(0.1),
),
),
],
),
),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Rata-rata: ${_getAverageData(title)}",
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade700,
fontWeight: FontWeight.w600,
),
),
Text(
"Tap untuk detail",
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
fontStyle: FontStyle.italic,
),
),
],
),
],
),
),
),
);
}
/// GET MONITORING DATA FOR 24 HOURS
List<FlSpot> _getMonitoringData(String title) {
final dataMap = {
"Evaporasi": [
2.1, 2.3, 2.0, 1.9, 2.2, 2.5, 3.0, 3.5, 4.0, 4.2,
4.5, 4.8, 5.0, 5.2, 5.1, 4.9, 4.5, 4.0, 3.5, 3.0, 2.8, 2.5, 2.3, 2.2
],
"Kecepatan Angin": [
5.0, 5.2, 4.8, 4.5, 4.3, 4.6, 5.1, 6.0, 7.2, 8.5,
9.0, 10.2, 11.0, 10.5, 10.0, 9.2, 8.0, 6.5, 5.5, 5.0, 4.8, 4.5, 5.0, 5.2
],
"Kualitas Udara": [
68.0, 70.0, 72.0, 75.0, 78.0, 80.0, 78.0, 75.0, 72.0, 70.0,
68.0, 65.0, 60.0, 58.0, 60.0, 62.0, 65.0, 68.0, 70.0, 72.0, 75.0, 78.0, 76.0, 74.0
],
};
final data = dataMap[title] ?? [];
return List.generate(
data.length,
(index) => FlSpot(index.toDouble(), data[index]),
);
}
/// GET AVERAGE VALUE
String _getAverageData(String title) {
final dataMap = {
"Evaporasi": "3.5 mm",
"Kecepatan Angin": "6.8 km/h",
"Kualitas Udara": "70 AQI",
};
return dataMap[title] ?? "N/A";
}
/// GET COLOR BY TITLE
Color _getColorByTitle(String title) {
switch (title) {
case "Evaporasi":
return Colors.blue;
case "Kecepatan Angin":
return Colors.green;
case "Kualitas Udara":
return Colors.orange;
default:
return Colors.grey;
}
}
}

View File

@ -1,12 +0,0 @@
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget{
const HomeScreen ({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
);
}
}

View File

@ -0,0 +1,677 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'widgets/monitoring_shared.dart';
/// AIR QUALITY MONITORING SCREEN
class AirQualityMonitoringScreen extends StatefulWidget {
const AirQualityMonitoringScreen({super.key});
@override
State<AirQualityMonitoringScreen> createState() => _AirQualityMonitoringScreenState();
}
class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen> {
String _selectedPeriod = "Hari Ini";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Monitoring Kualitas Udara"),
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(16),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Kualitas Udara",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
/// GRAFIK MONITORING
_buildMonitoringGraphBlock(),
const SizedBox(height: 25),
/// PENJELASAN
_buildAirQualityExplanation(),
const SizedBox(height: 25),
/// STATISTIK TAHUNAN
_buildAnnualAirQualityGraphSection(),
],
),
),
),
);
}
/// EXPORT FUNCTIONS
void _exportDailyAirQualityData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING KUALITAS UDARA - HARIAN (24 JAM)");
csvContent.writeln("Tanggal: ${DateTime.now().toString().split(' ')[0]}");
csvContent.writeln("");
csvContent.writeln("Jam,AQI (Air Quality Index)");
final dailyData = [
68.0, 70.0, 72.0, 75.0, 78.0, 80.0, 78.0, 75.0, 72.0, 70.0,
68.0, 65.0, 60.0, 58.0, 60.0, 62.0, 65.0, 68.0, 70.0, 72.0, 75.0, 78.0, 76.0, 74.0
];
for (int i = 0; i < dailyData.length; i++) {
csvContent.writeln("$i:00,${dailyData[i]}");
}
csvContent.writeln("");
csvContent.writeln("Statistik Harian");
csvContent.writeln("Total: 1709 AQI");
csvContent.writeln("Rata-rata: 71.2 AQI");
csvContent.writeln("Maksimal: 80 AQI");
csvContent.writeln("Minimal: 58 AQI");
showExportPreview(context, csvContent.toString(), "Data Harian Kualitas Udara");
}
void _exportWeeklyAirQualityData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING KUALITAS UDARA - MINGGUAN");
csvContent.writeln("Tanggal: ${DateTime.now().toString().split(' ')[0]}");
csvContent.writeln("");
csvContent.writeln("Hari,Rata-rata AQI");
final days = ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu', 'Minggu'];
final weeklyData = [68.0, 72.0, 75.0, 78.0, 72.0, 68.0, 62.0];
for (int i = 0; i < days.length; i++) {
csvContent.writeln("${days[i]},${weeklyData[i]}");
}
csvContent.writeln("");
csvContent.writeln("Statistik Mingguan");
csvContent.writeln("Total: 495 AQI");
csvContent.writeln("Rata-rata: 70.7 AQI");
csvContent.writeln("Maksimal: 78 AQI");
csvContent.writeln("Minimal: 62 AQI");
showExportPreview(context, csvContent.toString(), "Data Mingguan Kualitas Udara");
}
void _exportMonthlyAirQualityData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING KUALITAS UDARA - BULANAN");
csvContent.writeln("Bulan: ${DateTime.now().toString().split(' ')[0].substring(0, 7)}");
csvContent.writeln("");
csvContent.writeln("Tanggal,Rata-rata AQI");
for (int i = 1; i <= 28; i++) {
final value = (70.0 + (i % 6) * 2.5).toStringAsFixed(1);
csvContent.writeln("$i,$value");
}
csvContent.writeln("");
csvContent.writeln("Statistik Bulanan");
csvContent.writeln("Total: 1990 AQI");
csvContent.writeln("Rata-rata: 71.1 AQI");
csvContent.writeln("Maksimal: 82 AQI");
csvContent.writeln("Minimal: 55 AQI");
showExportPreview(context, csvContent.toString(), "Data Bulanan Kualitas Udara");
}
void _exportAnnualAirQualityData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING KUALITAS UDARA - TAHUNAN");
csvContent.writeln("Tahun: 2025");
csvContent.writeln("");
csvContent.writeln("Bulan,Rata-rata AQI,AQI Max");
final months = [
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
];
final averages = [68.0, 70.0, 72.5, 75.0, 78.0, 80.0, 82.0, 80.0, 75.0, 70.0, 68.0, 65.0];
final maxAqi = [85.0, 88.0, 90.0, 95.0, 98.0, 100.0, 102.0, 100.0, 95.0, 90.0, 85.0, 80.0];
for (int i = 0; i < months.length; i++) {
csvContent.writeln("${months[i]},${averages[i]},${maxAqi[i]}");
}
csvContent.writeln("");
csvContent.writeln("STATISTIK TAHUNAN");
csvContent.writeln("Rata-rata Tahunan: 74.2 AQI");
csvContent.writeln("AQI Max Teringgi: 102.0");
csvContent.writeln("AQI Min Terendah: 65.0");
showExportPreview(context, csvContent.toString(), "Data Tahunan Kualitas Udara");
}
/// DATA GENERATORS
List<FlSpot> _getAirQualityPeriodData() {
switch (_selectedPeriod) {
case "Hari Ini":
return List.generate(24, (i) {
final values = [
68.0, 70.0, 72.0, 75.0, 78.0, 80.0, 78.0, 75.0, 72.0, 70.0,
68.0, 65.0, 60.0, 58.0, 60.0, 62.0, 65.0, 68.0, 70.0, 72.0, 75.0, 78.0, 76.0, 74.0
];
return FlSpot(i.toDouble(), values[i]);
});
case "Minggu Ini":
return [
const FlSpot(0, 68.0),
const FlSpot(1, 72.0),
const FlSpot(2, 75.0),
const FlSpot(3, 78.0),
const FlSpot(4, 72.0),
const FlSpot(5, 68.0),
const FlSpot(6, 62.0),
];
case "Bulan Ini":
return [
const FlSpot(0, 70.0),
const FlSpot(1, 72.5),
const FlSpot(2, 75.0),
const FlSpot(3, 77.5),
const FlSpot(4, 80.0),
const FlSpot(5, 82.0),
const FlSpot(6, 80.0),
const FlSpot(7, 78.0),
const FlSpot(8, 75.0),
const FlSpot(9, 72.0),
const FlSpot(10, 70.0),
const FlSpot(11, 68.0),
];
default:
return [];
}
}
/// UI BUILDERS
Widget _buildMonitoringGraphBlock() {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Status Kualitas Udara",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
decoration: BoxDecoration(
color: Colors.amber.shade100,
borderRadius: BorderRadius.circular(20),
),
child: const Text(
"Normal",
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.amber,
),
),
),
],
),
const SizedBox(height: 16),
/// PERIOD SELECTOR + EXPORT
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: ["Hari Ini", "Minggu Ini", "Bulan Ini"].map((period) {
return Padding(
padding: const EdgeInsets.only(right: 8),
child: ChoiceChip(
label: Text(period),
selected: _selectedPeriod == period,
onSelected: (selected) {
setState(() {
_selectedPeriod = period;
});
},
selectedColor: Colors.orange,
labelStyle: TextStyle(
color: _selectedPeriod == period
? Colors.white
: Colors.black,
),
),
);
}).toList(),
),
),
),
const SizedBox(width: 8),
ElevatedButton.icon(
onPressed: () {
if (_selectedPeriod == "Hari Ini") {
_exportDailyAirQualityData();
} else if (_selectedPeriod == "Minggu Ini") {
_exportWeeklyAirQualityData();
} else if (_selectedPeriod == "Bulan Ini") {
_exportMonthlyAirQualityData();
}
},
icon: const Icon(Icons.download, size: 16),
label: const Text("Export"),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
textStyle: const TextStyle(fontSize: 11),
),
),
],
),
const SizedBox(height: 16),
/// GRAPH
SizedBox(
height: 300,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawHorizontalLine: true,
drawVerticalLine: false,
horizontalInterval: 1,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.shade300,
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: getPeriodInterval(_selectedPeriod),
getTitlesWidget: (value, meta) {
return Text(
getPeriodLabel(_selectedPeriod, value),
style: const TextStyle(
fontSize: 10,
color: Colors.grey,
),
);
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 1,
getTitlesWidget: (value, meta) {
return Text(
value.toInt().toString(),
style: const TextStyle(
fontSize: 9,
color: Colors.grey,
),
);
},
reservedSize: 28,
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border(
left: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
bottom: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
),
),
lineBarsData: [
LineChartBarData(
spots: _getAirQualityPeriodData(),
isCurved: true,
barWidth: 2.5,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: Colors.orange,
strokeWidth: 0,
);
},
),
color: Colors.orange,
belowBarData: BarAreaData(
show: true,
color: Colors.orange.withOpacity(0.1),
),
),
],
),
),
),
],
),
),
);
}
Widget _buildAirQualityExplanation() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.orange.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Penjelasan Kualitas Udara",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.orange,
),
),
const SizedBox(height: 10),
const Text(
"Kualitas udara diukur menggunakan Air Quality Index (AQI) yang menunjukkan tingkat polusi udara. AQI berkisar dari 0-500, dengan nilai lebih rendah menunjukkan udara lebih bersih. Monitoring kualitas udara penting untuk kesehatan masyarakat, perencanaan lingkungan, dan identifikasi sumber polusi. Kategori: 0-50 (Baik), 51-100 (Wajar), 101-150 (Tidak Sehat), 151+ (Sangat Tidak Sehat).",
style: TextStyle(
fontSize: 12,
height: 1.6,
color: Colors.orange,
),
),
],
),
);
}
Widget _buildAnnualAirQualityGraphSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Statistik Tahunan Kualitas Udara",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 15),
/// STATS
Row(
children: [
Expanded(
child: StatCard(
title: "Rata-rata Tahunan",
value: "74.2 AQI",
color: Colors.orange,
icon: Icons.cloud,
),
),
const SizedBox(width: 10),
Expanded(
child: StatCard(
title: "AQI Maksimal",
value: "102.0",
color: Colors.red,
icon: Icons.warning,
),
),
],
),
const SizedBox(height: 20),
/// CHART
const Text(
"Grafik Tahunan - Rata-rata & AQI Maksimal",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: SizedBox(
height: 320,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawHorizontalLine: true,
drawVerticalLine: false,
horizontalInterval: 5,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.shade300,
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
final months = [
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'
];
return Text(
months[value.toInt()],
style: const TextStyle(
fontSize: 10,
color: Colors.grey,
),
);
},
reservedSize: 30,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
return Text(
'${value.toInt()} AQI',
style: const TextStyle(
fontSize: 9,
color: Colors.grey,
),
);
},
reservedSize: 50,
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border(
left: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
bottom: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
),
),
lineBarsData: [
/// Rata-rata
LineChartBarData(
spots: const [
FlSpot(0, 68.0),
FlSpot(1, 70.0),
FlSpot(2, 72.5),
FlSpot(3, 75.0),
FlSpot(4, 78.0),
FlSpot(5, 80.0),
FlSpot(6, 82.0),
FlSpot(7, 80.0),
FlSpot(8, 75.0),
FlSpot(9, 70.0),
FlSpot(10, 68.0),
FlSpot(11, 65.0),
],
isCurved: true,
barWidth: 2.5,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: Colors.orange,
strokeWidth: 0,
);
},
),
color: Colors.orange,
belowBarData: BarAreaData(
show: true,
color: Colors.orange.withOpacity(0.1),
),
),
/// AQI Max
LineChartBarData(
spots: const [
FlSpot(0, 85.0),
FlSpot(1, 88.0),
FlSpot(2, 90.0),
FlSpot(3, 95.0),
FlSpot(4, 98.0),
FlSpot(5, 100.0),
FlSpot(6, 102.0),
FlSpot(7, 100.0),
FlSpot(8, 95.0),
FlSpot(9, 90.0),
FlSpot(10, 85.0),
FlSpot(11, 80.0),
],
isCurved: true,
barWidth: 2.5,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: Colors.red,
strokeWidth: 0,
);
},
),
color: Colors.red,
),
],
),
),
),
),
),
const SizedBox(height: 15),
/// LEGEND
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 16,
height: 16,
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(3),
),
),
const SizedBox(width: 8),
const Text(
"Rata-rata AQI",
style: TextStyle(fontSize: 12),
),
const SizedBox(width: 20),
Container(
width: 16,
height: 16,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(3),
),
),
const SizedBox(width: 8),
const Text(
"AQI Maksimal",
style: TextStyle(fontSize: 12),
),
],
),
const SizedBox(height: 15),
Center(
child: ElevatedButton.icon(
onPressed: () => _exportAnnualAirQualityData(),
icon: const Icon(Icons.file_download),
label: const Text("Download Excel Tahunan"),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
),
),
],
);
}
}

View File

@ -0,0 +1,741 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'widgets/monitoring_shared.dart';
/// EVAPORASI MONITORING SCREEN
class EvaporasiMonitoringScreen extends StatefulWidget {
const EvaporasiMonitoringScreen({super.key});
@override
State<EvaporasiMonitoringScreen> createState() => _EvaporasiMonitoringScreenState();
}
class _EvaporasiMonitoringScreenState extends State<EvaporasiMonitoringScreen> {
String _selectedPeriod = "Hari Ini";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Monitoring Evaporasi"),
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(16),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Evaporasi",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
/// SISTEM KONTROL AIR OTOMATIS
_buildAutomaticWaterControlSystem(),
const SizedBox(height: 25),
/// GRAFIK MONITORING
_buildMonitoringGraphBlock(),
const SizedBox(height: 25),
/// PENJELASAN
_buildMonitoringExplanation(),
const SizedBox(height: 25),
/// STATISTIK TAHUNAN
_buildAnnualCombinedGraphSection(),
],
),
),
),
);
}
/// EXPORT FUNCTIONS
void _exportDailyData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING EVAPORASI - HARIAN (24 JAM)");
csvContent.writeln("Tanggal: ${DateTime.now().toString().split(' ')[0]}");
csvContent.writeln("");
csvContent.writeln("Jam,Nilai (mm)");
final dailyData = [
2.1, 2.3, 2.0, 1.9, 2.2, 2.5, 3.0, 3.5, 4.0, 4.2,
4.5, 4.8, 5.0, 5.2, 5.1, 4.9, 4.5, 4.0, 3.5, 3.0, 2.8, 2.5, 2.3, 2.2
];
for (int i = 0; i < dailyData.length; i++) {
csvContent.writeln("$i:00,${dailyData[i]}");
}
csvContent.writeln("");
csvContent.writeln("Statistik Harian");
csvContent.writeln("Total: 79.4 mm");
csvContent.writeln("Rata-rata: 3.3 mm");
csvContent.writeln("Maksimal: 5.2 mm");
csvContent.writeln("Minimal: 1.9 mm");
showExportPreview(context, csvContent.toString(), "Data Harian");
}
void _exportWeeklyData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING EVAPORASI - MINGGUAN");
csvContent.writeln("Tanggal: ${DateTime.now().toString().split(' ')[0]}");
csvContent.writeln("");
csvContent.writeln("Hari,Nilai (mm)");
final days = ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu', 'Minggu'];
final weeklyData = [3.5, 3.8, 4.2, 4.5, 4.0, 3.8, 3.2];
for (int i = 0; i < days.length; i++) {
csvContent.writeln("${days[i]},${weeklyData[i]}");
}
csvContent.writeln("");
csvContent.writeln("Statistik Mingguan");
csvContent.writeln("Total: 27.0 mm");
csvContent.writeln("Rata-rata: 3.86 mm");
csvContent.writeln("Maksimal: 4.5 mm");
csvContent.writeln("Minimal: 3.2 mm");
showExportPreview(context, csvContent.toString(), "Data Mingguan");
}
void _exportMonthlyData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING EVAPORASI - BULANAN");
csvContent.writeln("Bulan: ${DateTime.now().toString().split(' ')[0].substring(0, 7)}");
csvContent.writeln("");
csvContent.writeln("Tanggal,Nilai (mm)");
for (int i = 1; i <= 28; i++) {
final value = (3.0 + (i % 6) * 0.5).toStringAsFixed(1);
csvContent.writeln("$i,$value");
}
csvContent.writeln("");
csvContent.writeln("Statistik Bulanan");
csvContent.writeln("Total: 99.2 mm");
csvContent.writeln("Rata-rata: 3.54 mm");
csvContent.writeln("Maksimal: 5.0 mm");
csvContent.writeln("Minimal: 2.5 mm");
showExportPreview(context, csvContent.toString(), "Data Bulanan");
}
void _exportAnnualData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING EVAPORASI - TAHUNAN");
csvContent.writeln("Tahun: 2025");
csvContent.writeln("");
csvContent.writeln("Bulan,Pengisian (x),Rata-rata (mm)");
final months = [
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
];
final fillings = [12, 13, 15, 14, 13, 12, 14, 16, 13, 14, 15, 13];
final averages = [3.5, 3.8, 4.2, 4.5, 5.0, 5.2, 5.5, 5.8, 5.0, 4.5, 4.0, 3.7];
for (int i = 0; i < months.length; i++) {
csvContent.writeln("${months[i]},${fillings[i]},${averages[i]}");
}
csvContent.writeln("");
csvContent.writeln("STATISTIK TAHUNAN");
csvContent.writeln("Total Pengisian: 156x");
csvContent.writeln("Rata-rata Evaporasi: 4.4 mm");
csvContent.writeln("Total Evaporasi: 52.8 mm");
showExportPreview(context, csvContent.toString(), "Data Tahunan");
}
/// DATA GENERATORS
List<FlSpot> _getEvaporationPeriodData() {
switch (_selectedPeriod) {
case "Hari Ini":
return List.generate(24, (i) {
final values = [
2.1, 2.3, 2.0, 1.9, 2.2, 2.5, 3.0, 3.5, 4.0, 4.2,
4.5, 4.8, 5.0, 5.2, 5.1, 4.9, 4.5, 4.0, 3.5, 3.0, 2.8, 2.5, 2.3, 2.2
];
return FlSpot(i.toDouble(), values[i]);
});
case "Minggu Ini":
return [
const FlSpot(0, 3.5),
const FlSpot(1, 3.8),
const FlSpot(2, 4.2),
const FlSpot(3, 4.5),
const FlSpot(4, 4.0),
const FlSpot(5, 3.8),
const FlSpot(6, 3.2),
];
case "Bulan Ini":
return [
const FlSpot(0, 3.5),
const FlSpot(1, 3.8),
const FlSpot(2, 4.2),
const FlSpot(3, 4.5),
const FlSpot(4, 5.0),
const FlSpot(5, 5.2),
const FlSpot(6, 5.5),
const FlSpot(7, 5.8),
const FlSpot(8, 5.0),
const FlSpot(9, 4.5),
const FlSpot(10, 4.0),
const FlSpot(11, 3.7),
];
default:
return [];
}
}
/// UI BUILDERS
Widget _buildAutomaticWaterControlSystem() {
bool isActive = DateTime.now().hour >= 6 && DateTime.now().hour < 7;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Sistem Kontrol Air Otomatis",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Jadwal Operasional",
style: TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
const SizedBox(height: 4),
const Text(
"06.00 - 07.00 WIB",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 10,
),
decoration: BoxDecoration(
color: isActive ? Colors.blue : Colors.grey.shade300,
borderRadius: BorderRadius.circular(20),
),
child: Text(
isActive ? "AKTIF" : "TIDAK AKTIF",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: isActive ? Colors.white : Colors.grey.shade700,
),
),
),
],
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
"Sistem otomatis akan mengisi air pada waktu yang telah ditentukan untuk menjaga tingkat evaporasi pada level optimal.",
style: TextStyle(
fontSize: 11,
color: Colors.blue,
height: 1.5,
),
),
),
],
),
);
}
Widget _buildMonitoringGraphBlock() {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Status Evaporasi",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
decoration: BoxDecoration(
color: Colors.amber.shade100,
borderRadius: BorderRadius.circular(20),
),
child: const Text(
"Normal",
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.amber,
),
),
),
],
),
const SizedBox(height: 16),
/// PERIOD SELECTOR + EXPORT
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: ["Hari Ini", "Minggu Ini", "Bulan Ini"].map((period) {
return Padding(
padding: const EdgeInsets.only(right: 8),
child: ChoiceChip(
label: Text(period),
selected: _selectedPeriod == period,
onSelected: (selected) {
setState(() {
_selectedPeriod = period;
});
},
selectedColor: Colors.blue,
labelStyle: TextStyle(
color: _selectedPeriod == period
? Colors.white
: Colors.black,
),
),
);
}).toList(),
),
),
),
const SizedBox(width: 8),
ElevatedButton.icon(
onPressed: () {
if (_selectedPeriod == "Hari Ini") {
_exportDailyData();
} else if (_selectedPeriod == "Minggu Ini") {
_exportWeeklyData();
} else if (_selectedPeriod == "Bulan Ini") {
_exportMonthlyData();
}
},
icon: const Icon(Icons.download, size: 16),
label: const Text("Export"),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
textStyle: const TextStyle(fontSize: 11),
),
),
],
),
const SizedBox(height: 16),
/// GRAPH
SizedBox(
height: 300,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawHorizontalLine: true,
drawVerticalLine: false,
horizontalInterval: 1,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.shade300,
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: getPeriodInterval(_selectedPeriod),
getTitlesWidget: (value, meta) {
return Text(
getPeriodLabel(_selectedPeriod, value),
style: const TextStyle(
fontSize: 10,
color: Colors.grey,
),
);
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 1,
getTitlesWidget: (value, meta) {
return Text(
value.toInt().toString(),
style: const TextStyle(
fontSize: 9,
color: Colors.grey,
),
);
},
reservedSize: 28,
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border(
left: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
bottom: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
),
),
lineBarsData: [
LineChartBarData(
spots: _getEvaporationPeriodData(),
isCurved: true,
barWidth: 2.5,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: Colors.blue,
strokeWidth: 0,
);
},
),
color: Colors.blue,
belowBarData: BarAreaData(
show: true,
color: Colors.blue.withOpacity(0.1),
),
),
],
),
),
),
],
),
),
);
}
Widget _buildMonitoringExplanation() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.blue.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Penjelasan Evaporasi",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
const SizedBox(height: 10),
const Text(
"Evaporasi adalah proses penguapan air dari permukaan tanah atau air menuju atmosfer. Tingkat evaporasi dipengaruhi oleh suhu, kelembaban udara, kecepatan angin, dan intensitas cahaya matahari. Data ini penting untuk manajemen irigasi dan perencanaan kebutuhan air.",
style: TextStyle(
fontSize: 12,
height: 1.6,
color: Colors.blue,
),
),
],
),
);
}
Widget _buildAnnualCombinedGraphSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Statistik Tahunan & Rata-rata",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 15),
/// STATS
Row(
children: [
Expanded(
child: StatCard(
title: "Total Pengisian",
value: "156x",
color: Colors.blue,
icon: Icons.water_drop,
),
),
const SizedBox(width: 10),
Expanded(
child: StatCard(
title: "Rata-rata",
value: "4.2 mm",
color: Colors.green,
icon: Icons.trending_up,
),
),
],
),
const SizedBox(height: 20),
/// CHART
const Text(
"Grafik Tahunan - Pengisian Air & Rata-rata Evaporasi",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
SizedBox(
height: 320,
child: BarChart(
BarChartData(
gridData: FlGridData(
show: true,
drawHorizontalLine: true,
drawVerticalLine: false,
horizontalInterval: 2,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.shade300,
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
final months = [
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'
];
return Text(
months[value.toInt()],
style: const TextStyle(
fontSize: 10,
color: Colors.grey,
),
);
},
reservedSize: 30,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
return Text(
value.toInt().toString(),
style: const TextStyle(
fontSize: 9,
color: Colors.grey,
),
);
},
reservedSize: 28,
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
return Text(
'${value.toInt()} mm',
style: const TextStyle(
fontSize: 9,
color: Colors.green,
),
);
},
reservedSize: 45,
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border(
left: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
bottom: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
right: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
),
),
barGroups: [
buildBarGroup(0, 12),
buildBarGroup(1, 13),
buildBarGroup(2, 15),
buildBarGroup(3, 14),
buildBarGroup(4, 13),
buildBarGroup(5, 12),
buildBarGroup(6, 14),
buildBarGroup(7, 16),
buildBarGroup(8, 13),
buildBarGroup(9, 14),
buildBarGroup(10, 15),
buildBarGroup(11, 13),
],
rangeAnnotations: RangeAnnotations(
horizontalRangeAnnotations: [
HorizontalRangeAnnotation(
y1: 3.5,
y2: 5.8,
color: Colors.green.withOpacity(0.1),
),
],
),
),
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 16,
height: 16,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(3),
),
),
const SizedBox(width: 8),
const Text(
"Pengisian Air (kali)",
style: TextStyle(fontSize: 12),
),
const SizedBox(width: 20),
Container(
width: 16,
height: 16,
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.3),
borderRadius: BorderRadius.circular(3),
),
),
const SizedBox(width: 8),
const Text(
"Area Rata-rata (mm)",
style: TextStyle(fontSize: 12),
),
],
),
],
),
),
),
const SizedBox(height: 15),
Center(
child: ElevatedButton.icon(
onPressed: () => _exportAnnualData(),
icon: const Icon(Icons.file_download),
label: const Text("Download Excel Tahunan"),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
),
),
],
);
}
}

View File

@ -0,0 +1,130 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
/// SHOW EXPORT PREVIEW DIALOG
/// Helper function untuk menampilkan preview export data
void showExportPreview(BuildContext context, String content, String title) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Preview Export - $title"),
content: SizedBox(
width: double.maxFinite,
child: SingleChildScrollView(
child: Text(
content,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 10,
),
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Tutup"),
),
],
),
);
}
/// STAT CARD WIDGET
/// Reusable card untuk menampilkan statistik
class StatCard extends StatelessWidget {
final String title;
final String value;
final Color color;
final IconData icon;
const StatCard({
super.key,
required this.title,
required this.value,
required this.color,
required this.icon,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color, width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, color: color, size: 20),
const SizedBox(height: 8),
Text(
title,
style: const TextStyle(
fontSize: 11,
color: Colors.grey,
),
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
);
}
}
/// BUILD BAR GROUP FOR CHART
/// Helper untuk membuat bar group di BarChart
BarChartGroupData buildBarGroup(int x, double value) {
return BarChartGroupData(
x: x,
barRods: [
BarChartRodData(
toY: value.toDouble(),
color: Colors.blue,
width: 12,
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
),
],
);
}
/// GET PERIOD INTERVAL
/// Menentukan spacing interval berdasarkan periode
double getPeriodInterval(String period) {
switch (period) {
case "Hari Ini":
return 3;
case "Minggu Ini":
return 1;
case "Bulan Ini":
return 2;
default:
return 1;
}
}
/// GET PERIOD LABEL
/// Menentukan label untuk X-axis berdasarkan periode
String getPeriodLabel(String period, double value) {
switch (period) {
case "Hari Ini":
return '${value.toInt()}h';
case "Minggu Ini":
final days = ['Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab', 'Min'];
return days[value.toInt()];
case "Bulan Ini":
return 'M${value.toInt() + 1}';
default:
return '';
}
}

View File

@ -0,0 +1,677 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'widgets/monitoring_shared.dart';
/// WIND SPEED MONITORING SCREEN
class WindSpeedMonitoringScreen extends StatefulWidget {
const WindSpeedMonitoringScreen({super.key});
@override
State<WindSpeedMonitoringScreen> createState() => _WindSpeedMonitoringScreenState();
}
class _WindSpeedMonitoringScreenState extends State<WindSpeedMonitoringScreen> {
String _selectedPeriod = "Hari Ini";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Monitoring Kecepatan Angin"),
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(16),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Kecepatan Angin",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
/// GRAFIK MONITORING
_buildMonitoringGraphBlock(),
const SizedBox(height: 25),
/// PENJELASAN
_buildWindSpeedExplanation(),
const SizedBox(height: 25),
/// STATISTIK TAHUNAN
_buildAnnualWindGraphSection(),
],
),
),
),
);
}
/// EXPORT FUNCTIONS
void _exportDailyWindData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING KECEPATAN ANGIN - HARIAN (24 JAM)");
csvContent.writeln("Tanggal: ${DateTime.now().toString().split(' ')[0]}");
csvContent.writeln("");
csvContent.writeln("Jam,Kecepatan (km/h)");
final dailyData = [
5.0, 5.2, 4.8, 4.5, 4.3, 4.6, 5.1, 6.0, 7.2, 8.5,
9.0, 10.2, 11.0, 10.5, 10.0, 9.2, 8.0, 6.5, 5.5, 5.0, 4.8, 4.5, 5.0, 5.2
];
for (int i = 0; i < dailyData.length; i++) {
csvContent.writeln("$i:00,${dailyData[i]}");
}
csvContent.writeln("");
csvContent.writeln("Statistik Harian");
csvContent.writeln("Total: 165.8 km/h");
csvContent.writeln("Rata-rata: 6.9 km/h");
csvContent.writeln("Maksimal: 11.0 km/h");
csvContent.writeln("Minimal: 4.3 km/h");
showExportPreview(context, csvContent.toString(), "Data Harian Kecepatan Angin");
}
void _exportWeeklyWindData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING KECEPATAN ANGIN - MINGGUAN");
csvContent.writeln("Tanggal: ${DateTime.now().toString().split(' ')[0]}");
csvContent.writeln("");
csvContent.writeln("Hari,Rata-rata (km/h)");
final days = ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu', 'Minggu'];
final weeklyData = [6.5, 7.2, 7.8, 8.0, 7.5, 6.8, 5.5];
for (int i = 0; i < days.length; i++) {
csvContent.writeln("${days[i]},${weeklyData[i]}");
}
csvContent.writeln("");
csvContent.writeln("Statistik Mingguan");
csvContent.writeln("Total: 49.3 km/h");
csvContent.writeln("Rata-rata: 7.0 km/h");
csvContent.writeln("Maksimal: 8.0 km/h");
csvContent.writeln("Minimal: 5.5 km/h");
showExportPreview(context, csvContent.toString(), "Data Mingguan Kecepatan Angin");
}
void _exportMonthlyWindData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING KECEPATAN ANGIN - BULANAN");
csvContent.writeln("Bulan: ${DateTime.now().toString().split(' ')[0].substring(0, 7)}");
csvContent.writeln("");
csvContent.writeln("Tanggal,Rata-rata (km/h)");
for (int i = 1; i <= 28; i++) {
final value = (6.0 + (i % 6) * 0.8).toStringAsFixed(1);
csvContent.writeln("$i,$value");
}
csvContent.writeln("");
csvContent.writeln("Statistik Bulanan");
csvContent.writeln("Total: 180.5 km/h");
csvContent.writeln("Rata-rata: 6.4 km/h");
csvContent.writeln("Maksimal: 9.0 km/h");
csvContent.writeln("Minimal: 4.0 km/h");
showExportPreview(context, csvContent.toString(), "Data Bulanan Kecepatan Angin");
}
void _exportAnnualWindData() {
final StringBuffer csvContent = StringBuffer();
csvContent.writeln("DATA MONITORING KECEPATAN ANGIN - TAHUNAN");
csvContent.writeln("Tahun: 2025");
csvContent.writeln("");
csvContent.writeln("Bulan,Rata-rata (km/h),Kecepatan Max (km/h)");
final months = [
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
];
final averages = [5.5, 6.2, 6.8, 7.5, 7.8, 8.0, 8.2, 8.0, 7.2, 6.5, 5.8, 5.2];
final maxSpeed = [12.0, 13.5, 14.0, 15.5, 16.0, 16.2, 16.5, 16.0, 15.0, 14.0, 12.5, 11.0];
for (int i = 0; i < months.length; i++) {
csvContent.writeln("${months[i]},${averages[i]},${maxSpeed[i]}");
}
csvContent.writeln("");
csvContent.writeln("STATISTIK TAHUNAN");
csvContent.writeln("Rata-rata Tahunan: 7.1 km/h");
csvContent.writeln("Kecepatan Max Tertinggi: 16.5 km/h");
csvContent.writeln("Kecepatan Min Terendah: 5.2 km/h");
showExportPreview(context, csvContent.toString(), "Data Tahunan Kecepatan Angin");
}
/// DATA GENERATORS
List<FlSpot> _getWindSpeedPeriodData() {
switch (_selectedPeriod) {
case "Hari Ini":
return List.generate(24, (i) {
final values = [
5.0, 5.2, 4.8, 4.5, 4.3, 4.6, 5.1, 6.0, 7.2, 8.5,
9.0, 10.2, 11.0, 10.5, 10.0, 9.2, 8.0, 6.5, 5.5, 5.0, 4.8, 4.5, 5.0, 5.2
];
return FlSpot(i.toDouble(), values[i]);
});
case "Minggu Ini":
return [
const FlSpot(0, 6.5),
const FlSpot(1, 7.2),
const FlSpot(2, 7.8),
const FlSpot(3, 8.0),
const FlSpot(4, 7.5),
const FlSpot(5, 6.8),
const FlSpot(6, 5.5),
];
case "Bulan Ini":
return [
const FlSpot(0, 6.0),
const FlSpot(1, 6.5),
const FlSpot(2, 7.0),
const FlSpot(3, 7.5),
const FlSpot(4, 7.8),
const FlSpot(5, 8.0),
const FlSpot(6, 8.2),
const FlSpot(7, 8.0),
const FlSpot(8, 7.5),
const FlSpot(9, 7.0),
const FlSpot(10, 6.5),
const FlSpot(11, 6.0),
];
default:
return [];
}
}
/// UI BUILDERS
Widget _buildMonitoringGraphBlock() {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Status Kecepatan Angin",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
decoration: BoxDecoration(
color: Colors.amber.shade100,
borderRadius: BorderRadius.circular(20),
),
child: const Text(
"Normal",
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.amber,
),
),
),
],
),
const SizedBox(height: 16),
/// PERIOD SELECTOR + EXPORT
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: ["Hari Ini", "Minggu Ini", "Bulan Ini"].map((period) {
return Padding(
padding: const EdgeInsets.only(right: 8),
child: ChoiceChip(
label: Text(period),
selected: _selectedPeriod == period,
onSelected: (selected) {
setState(() {
_selectedPeriod = period;
});
},
selectedColor: Colors.green,
labelStyle: TextStyle(
color: _selectedPeriod == period
? Colors.white
: Colors.black,
),
),
);
}).toList(),
),
),
),
const SizedBox(width: 8),
ElevatedButton.icon(
onPressed: () {
if (_selectedPeriod == "Hari Ini") {
_exportDailyWindData();
} else if (_selectedPeriod == "Minggu Ini") {
_exportWeeklyWindData();
} else if (_selectedPeriod == "Bulan Ini") {
_exportMonthlyWindData();
}
},
icon: const Icon(Icons.download, size: 16),
label: const Text("Export"),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
textStyle: const TextStyle(fontSize: 11),
),
),
],
),
const SizedBox(height: 16),
/// GRAPH
SizedBox(
height: 300,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawHorizontalLine: true,
drawVerticalLine: false,
horizontalInterval: 1,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.shade300,
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: getPeriodInterval(_selectedPeriod),
getTitlesWidget: (value, meta) {
return Text(
getPeriodLabel(_selectedPeriod, value),
style: const TextStyle(
fontSize: 10,
color: Colors.grey,
),
);
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 1,
getTitlesWidget: (value, meta) {
return Text(
value.toInt().toString(),
style: const TextStyle(
fontSize: 9,
color: Colors.grey,
),
);
},
reservedSize: 28,
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border(
left: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
bottom: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
),
),
lineBarsData: [
LineChartBarData(
spots: _getWindSpeedPeriodData(),
isCurved: true,
barWidth: 2.5,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: Colors.green,
strokeWidth: 0,
);
},
),
color: Colors.green,
belowBarData: BarAreaData(
show: true,
color: Colors.green.withOpacity(0.1),
),
),
],
),
),
),
],
),
),
);
}
Widget _buildWindSpeedExplanation() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.green.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Penjelasan Kecepatan Angin",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
const SizedBox(height: 10),
const Text(
"Kecepatan angin adalah laju gerakan massa udara yang mengalir secara horizontal. Data kecepatan angin penting untuk prediksi cuaca, manajemen rawan angin, perencanaan energi terbarukan (angin), serta membantu dalam pengeringan hasil pertanian. Pengukuran dilakukan dalam satuan km/h.",
style: TextStyle(
fontSize: 12,
height: 1.6,
color: Colors.green,
),
),
],
),
);
}
Widget _buildAnnualWindGraphSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Statistik Tahunan Kecepatan Angin",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 15),
/// STATS
Row(
children: [
Expanded(
child: StatCard(
title: "Rata-rata Tahunan",
value: "7.1 km/h",
color: Colors.green,
icon: Icons.air,
),
),
const SizedBox(width: 10),
Expanded(
child: StatCard(
title: "Kecepatan Max",
value: "16.5 km/h",
color: Colors.red,
icon: Icons.trending_up,
),
),
],
),
const SizedBox(height: 20),
/// CHART
const Text(
"Grafik Tahunan - Rata-rata & Kecepatan Maksimal",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: SizedBox(
height: 320,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawHorizontalLine: true,
drawVerticalLine: false,
horizontalInterval: 2,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.shade300,
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
final months = [
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'
];
return Text(
months[value.toInt()],
style: const TextStyle(
fontSize: 10,
color: Colors.grey,
),
);
},
reservedSize: 30,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
return Text(
'${value.toInt()} km/h',
style: const TextStyle(
fontSize: 9,
color: Colors.grey,
),
);
},
reservedSize: 50,
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border(
left: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
bottom: BorderSide(
color: Colors.grey.shade300,
width: 1,
),
),
),
lineBarsData: [
/// Rata-rata
LineChartBarData(
spots: const [
FlSpot(0, 5.5),
FlSpot(1, 6.2),
FlSpot(2, 6.8),
FlSpot(3, 7.5),
FlSpot(4, 7.8),
FlSpot(5, 8.0),
FlSpot(6, 8.2),
FlSpot(7, 8.0),
FlSpot(8, 7.2),
FlSpot(9, 6.5),
FlSpot(10, 5.8),
FlSpot(11, 5.2),
],
isCurved: true,
barWidth: 2.5,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: Colors.green,
strokeWidth: 0,
);
},
),
color: Colors.green,
belowBarData: BarAreaData(
show: true,
color: Colors.green.withOpacity(0.1),
),
),
/// Max Speed
LineChartBarData(
spots: const [
FlSpot(0, 12.0),
FlSpot(1, 13.5),
FlSpot(2, 14.0),
FlSpot(3, 15.5),
FlSpot(4, 16.0),
FlSpot(5, 16.2),
FlSpot(6, 16.5),
FlSpot(7, 16.0),
FlSpot(8, 15.0),
FlSpot(9, 14.0),
FlSpot(10, 12.5),
FlSpot(11, 11.0),
],
isCurved: true,
barWidth: 2.5,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: Colors.red,
strokeWidth: 0,
);
},
),
color: Colors.red,
),
],
),
),
),
),
),
const SizedBox(height: 15),
/// LEGEND
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 16,
height: 16,
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(3),
),
),
const SizedBox(width: 8),
const Text(
"Rata-rata (km/h)",
style: TextStyle(fontSize: 12),
),
const SizedBox(width: 20),
Container(
width: 16,
height: 16,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(3),
),
),
const SizedBox(width: 8),
const Text(
"Kecepatan Max (km/h)",
style: TextStyle(fontSize: 12),
),
],
),
const SizedBox(height: 15),
Center(
child: ElevatedButton.icon(
onPressed: () => _exportAnnualWindData(),
icon: const Icon(Icons.file_download),
label: const Text("Download Excel Tahunan"),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
),
),
],
);
}
}

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7"
sha256: cd83f7d6bd4e4c0b0b4fef802e8796784032e1cc23d7b0e982cf5d05d9bbe182
url: "https://pub.dev"
source: hosted
version: "1.3.35"
version: "1.3.66"
async:
dependency: transitive
description:
@ -45,26 +45,26 @@ packages:
dependency: "direct main"
description:
name: cloud_firestore
sha256: a0f161b92610e078b4962d7e6ebeb66dc9cce0ada3514aeee442f68165d78185
sha256: "54484b2fc49f41b46f35b60a54b12351181eeaad22c0e3def276a81e17ae7c9b"
url: "https://pub.dev"
source: hosted
version: "4.17.5"
version: "6.1.2"
cloud_firestore_platform_interface:
dependency: transitive
description:
name: cloud_firestore_platform_interface
sha256: "6a55b319f8d33c307396b9104512e8130a61904528ab7bd8b5402678fca54b81"
sha256: dfaa8b2c0d0a824af289d4159816a5c78417feec264c2194081d645687195158
url: "https://pub.dev"
source: hosted
version: "6.2.5"
version: "7.0.6"
cloud_firestore_web:
dependency: transitive
description:
name: cloud_firestore_web
sha256: "89dfa1304d3da48b3039abbb2865e3d30896ef858e569a16804a99f4362283a9"
sha256: "35d01f502b3b701d700470d32a8f82704dac8341a66e86c074900cde5bab343d"
url: "https://pub.dev"
source: hosted
version: "3.12.5"
version: "5.1.2"
collection:
dependency: transitive
description:
@ -85,50 +85,50 @@ packages:
dependency: "direct main"
description:
name: firebase_auth
sha256: cfc2d970829202eca09e2896f0a5aa7c87302817ecc0bdfa954f026046bf10ba
sha256: b20d1540460814c5984474c1e9dd833bdbcff6ecd8d6ad86cc9da8cfd581c172
url: "https://pub.dev"
source: hosted
version: "4.20.0"
version: "6.1.4"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
sha256: a0270e1db3b2098a14cb2a2342b3cd2e7e458e0c391b1f64f6f78b14296ec093
sha256: fd0225320b6bbc92460c86352d16b60aea15f9ef88292774cca97b0522ea9f72
url: "https://pub.dev"
source: hosted
version: "7.3.0"
version: "8.1.6"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
sha256: "64e067e763c6378b7e774e872f0f59f6812885e43020e25cde08f42e9459837b"
sha256: be7dccb263b89fbda2a564de9d8193118196e8481ffb937222a025cdfdf82c40
url: "https://pub.dev"
source: hosted
version: "5.12.0"
version: "6.1.2"
firebase_core:
dependency: transitive
description:
name: firebase_core
sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c"
sha256: "923085c881663ef685269b013e241b428e1fb03cdd0ebde265d9b40ff18abf80"
url: "https://pub.dev"
source: hosted
version: "2.32.0"
version: "4.4.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: "8bcfad6d7033f5ea951d15b867622a824b13812178bfec0c779b9d81de011bbb"
sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64
url: "https://pub.dev"
source: hosted
version: "5.4.2"
version: "6.0.2"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88"
sha256: "83e7356c704131ca4d8d8dd57e360d8acecbca38b1a3705c7ae46cc34c708084"
url: "https://pub.dev"
source: hosted
version: "2.17.5"
version: "3.4.0"
flutter:
dependency: "direct main"
description: flutter
@ -138,10 +138,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
version: "3.0.2"
flutter_test:
dependency: "direct dev"
description: flutter
@ -152,6 +152,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_parser:
dependency: transitive
description:
@ -188,10 +196,10 @@ packages:
dependency: transitive
description:
name: lints
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "3.0.0"
matcher:
dependency: transitive
description:
@ -321,10 +329,10 @@ packages:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "1.1.1"
sdks:
dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
flutter: ">=3.22.0"

View File

@ -153,6 +153,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.3.1"
fl_chart:
dependency: "direct main"
description:
name: fl_chart
sha256: "00b74ae680df6b1135bdbea00a7d1fc072a9180b7c3f3702e4b19a9943f5ed7d"
url: "https://pub.dev"
source: hosted
version: "0.66.2"
flutter:
dependency: "direct main"
description: flutter

View File

@ -7,6 +7,11 @@ environment:
dependencies:
flutter:
sdk: flutter
<<<<<<< Updated upstream
=======
fl_chart: ^0.66.0
>>>>>>> Stashed changes
firebase_core: ^4.3.0
firebase_auth: ^6.1.3
cloud_firestore: ^6.1.1