oke
This commit is contained in:
parent
5d7d61fbaa
commit
3675c0188d
12
lib/app.dart
12
lib/app.dart
|
|
@ -2,19 +2,19 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:klimatologiot/app_view.dart';
|
||||
import 'package:user_repository/user_repository.dart';
|
||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||
import 'blocs/authentication_bloc/authentication_bloc.dart';
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
final UserRepository userRepository;
|
||||
const MyApp(this.userRepository, {super.key});
|
||||
final MonitoringRepository monitoringRepository;
|
||||
const MyApp(this.userRepository, this.monitoringRepository, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RepositoryProvider<AuthenticationBloc>(
|
||||
create: (context) => AuthenticationBloc(
|
||||
userRepository: userRepository
|
||||
),
|
||||
child: const MyAppView(),
|
||||
create: (context) => AuthenticationBloc(userRepository: userRepository),
|
||||
child: const MyAppView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
|
||||
part 'monitoring_event.dart';
|
||||
part 'monitoring_state.dart';
|
||||
|
||||
class MonitoringBloc extends Bloc<MonitoringEvent, MonitoringState> {
|
||||
final DatabaseReference _realtimeRef =
|
||||
FirebaseDatabase.instance.ref('anemometer/realtime');
|
||||
StreamSubscription<DatabaseEvent>? _realtimeSub;
|
||||
|
||||
MonitoringBloc() : super(const MonitoringState.initial()) {
|
||||
on<LoadMonitoringData>(_onLoadMonitoringData);
|
||||
on<MonitoringDataUpdated>(_onMonitoringDataUpdated);
|
||||
}
|
||||
|
||||
void _onLoadMonitoringData(
|
||||
LoadMonitoringData event, Emitter<MonitoringState> emit) {
|
||||
emit(const MonitoringState.loading());
|
||||
|
||||
// Start listening to realtime data
|
||||
_realtimeSub = _realtimeRef.onValue.listen((event) {
|
||||
final data = event.snapshot.value;
|
||||
if (data is Map) {
|
||||
final speed = (data['kecepatan'] ?? 0).toDouble();
|
||||
final timestamp = data['timestamp'] ?? 0;
|
||||
final waktu = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||
|
||||
add(MonitoringDataUpdated(speed: speed, timestamp: waktu));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onMonitoringDataUpdated(
|
||||
MonitoringDataUpdated event, Emitter<MonitoringState> emit) {
|
||||
emit(MonitoringState.loaded(
|
||||
currentSpeed: event.speed,
|
||||
lastUpdateTime: event.timestamp,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_realtimeSub?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
part of 'monitoring_bloc.dart';
|
||||
|
||||
sealed class MonitoringEvent extends Equatable {
|
||||
const MonitoringEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadMonitoringData extends MonitoringEvent {
|
||||
const LoadMonitoringData();
|
||||
}
|
||||
|
||||
class MonitoringDataUpdated extends MonitoringEvent {
|
||||
final double speed;
|
||||
final DateTime timestamp;
|
||||
|
||||
const MonitoringDataUpdated({
|
||||
required this.speed,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [speed, timestamp];
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
part of 'monitoring_bloc.dart';
|
||||
|
||||
class MonitoringState extends Equatable {
|
||||
final MonitoringStatus status;
|
||||
final double? currentSpeed;
|
||||
final DateTime? lastUpdateTime;
|
||||
|
||||
const MonitoringState._({
|
||||
this.status = MonitoringStatus.initial,
|
||||
this.currentSpeed,
|
||||
this.lastUpdateTime,
|
||||
});
|
||||
|
||||
const MonitoringState.initial() : this._();
|
||||
|
||||
const MonitoringState.loading() : this._(status: MonitoringStatus.loading);
|
||||
|
||||
const MonitoringState.loaded({
|
||||
required double currentSpeed,
|
||||
required DateTime lastUpdateTime,
|
||||
}) : this._(
|
||||
status: MonitoringStatus.loaded,
|
||||
currentSpeed: currentSpeed,
|
||||
lastUpdateTime: lastUpdateTime,
|
||||
);
|
||||
|
||||
const MonitoringState.error(String message)
|
||||
: this._(status: MonitoringStatus.error);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, currentSpeed, lastUpdateTime];
|
||||
}
|
||||
|
||||
enum MonitoringStatus { initial, loading, loaded, error }
|
||||
|
|
@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||
import 'package:klimatologiot/simple_bloc_observer.dart';
|
||||
import 'package:klimatologiot/app.dart';
|
||||
import 'package:user_repository/user_repository.dart';
|
||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||
import 'firebase_options.dart';
|
||||
|
||||
void main() async {
|
||||
|
|
@ -12,5 +13,8 @@ void main() async {
|
|||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
Bloc.observer = SimpleBlocObserver();
|
||||
runApp(MyApp(FirebaseUserRepo()));
|
||||
}
|
||||
runApp(MyApp(
|
||||
FirebaseUserRepo(),
|
||||
FirebaseMonitoringRepo(),
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'package:firebase_database/firebase_database.dart';
|
|||
import 'dart:async';
|
||||
import '../../../blocs/authentication_bloc/authentication_bloc.dart';
|
||||
import '../../monitoring/evaporasi_monitoring_screen.dart';
|
||||
import '../../monitoring/wind_speed/wind_speed_monitoring_screen.dart';
|
||||
import '../../monitoring/wind_speed/wind_speed.dart';
|
||||
import '../../monitoring/air_quality_monitoring_screen.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'widgets/monitoring_shared.dart';
|
||||
import 'shared/widgets/monitoring_shared.dart';
|
||||
|
||||
/// AIR QUALITY MONITORING SCREEN
|
||||
class AirQualityMonitoringScreen extends StatefulWidget {
|
||||
const AirQualityMonitoringScreen({super.key});
|
||||
|
||||
@override
|
||||
State<AirQualityMonitoringScreen> createState() => _AirQualityMonitoringScreenState();
|
||||
State<AirQualityMonitoringScreen> createState() =>
|
||||
_AirQualityMonitoringScreenState();
|
||||
}
|
||||
|
||||
class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen> {
|
||||
class _AirQualityMonitoringScreenState
|
||||
extends State<AirQualityMonitoringScreen> {
|
||||
String _selectedPeriod = "Hari Ini";
|
||||
|
||||
@override
|
||||
|
|
@ -61,8 +63,30 @@ class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen>
|
|||
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
|
||||
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]}");
|
||||
|
|
@ -75,7 +99,8 @@ class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen>
|
|||
csvContent.writeln("Maksimal: 80 AQI");
|
||||
csvContent.writeln("Minimal: 58 AQI");
|
||||
|
||||
showExportPreview(context, csvContent.toString(), "Data Harian Kualitas Udara");
|
||||
showExportPreview(
|
||||
context, csvContent.toString(), "Data Harian Kualitas Udara");
|
||||
}
|
||||
|
||||
void _exportWeeklyAirQualityData() {
|
||||
|
|
@ -85,7 +110,15 @@ class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen>
|
|||
csvContent.writeln("");
|
||||
csvContent.writeln("Hari,Rata-rata AQI");
|
||||
|
||||
final days = ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu', 'Minggu'];
|
||||
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++) {
|
||||
|
|
@ -99,13 +132,15 @@ class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen>
|
|||
csvContent.writeln("Maksimal: 78 AQI");
|
||||
csvContent.writeln("Minimal: 62 AQI");
|
||||
|
||||
showExportPreview(context, csvContent.toString(), "Data Mingguan Kualitas Udara");
|
||||
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(
|
||||
"Bulan: ${DateTime.now().toString().split(' ')[0].substring(0, 7)}");
|
||||
csvContent.writeln("");
|
||||
csvContent.writeln("Tanggal,Rata-rata AQI");
|
||||
|
||||
|
|
@ -121,7 +156,8 @@ class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen>
|
|||
csvContent.writeln("Maksimal: 82 AQI");
|
||||
csvContent.writeln("Minimal: 55 AQI");
|
||||
|
||||
showExportPreview(context, csvContent.toString(), "Data Bulanan Kualitas Udara");
|
||||
showExportPreview(
|
||||
context, csvContent.toString(), "Data Bulanan Kualitas Udara");
|
||||
}
|
||||
|
||||
void _exportAnnualAirQualityData() {
|
||||
|
|
@ -132,11 +168,47 @@ class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen>
|
|||
csvContent.writeln("Bulan,Rata-rata AQI,AQI Max");
|
||||
|
||||
final months = [
|
||||
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
|
||||
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
|
||||
'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
|
||||
];
|
||||
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]}");
|
||||
|
|
@ -148,7 +220,8 @@ class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen>
|
|||
csvContent.writeln("AQI Max Teringgi: 102.0");
|
||||
csvContent.writeln("AQI Min Terendah: 65.0");
|
||||
|
||||
showExportPreview(context, csvContent.toString(), "Data Tahunan Kualitas Udara");
|
||||
showExportPreview(
|
||||
context, csvContent.toString(), "Data Tahunan Kualitas Udara");
|
||||
}
|
||||
|
||||
/// DATA GENERATORS
|
||||
|
|
@ -157,8 +230,30 @@ class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen>
|
|||
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
|
||||
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]);
|
||||
});
|
||||
|
|
@ -244,7 +339,8 @@ class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen>
|
|||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: ["Hari Ini", "Minggu Ini", "Bulan Ini"].map((period) {
|
||||
children:
|
||||
["Hari Ini", "Minggu Ini", "Bulan Ini"].map((period) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: ChoiceChip(
|
||||
|
|
@ -504,8 +600,18 @@ class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen>
|
|||
showTitles: true,
|
||||
getTitlesWidget: (value, meta) {
|
||||
final months = [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
|
||||
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'Mei',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Agu',
|
||||
'Sep',
|
||||
'Okt',
|
||||
'Nov',
|
||||
'Des'
|
||||
];
|
||||
return Text(
|
||||
months[value.toInt()],
|
||||
|
|
@ -588,6 +694,7 @@ class _AirQualityMonitoringScreenState extends State<AirQualityMonitoringScreen>
|
|||
color: Colors.orange.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
|
||||
/// AQI Max
|
||||
LineChartBarData(
|
||||
spots: const [
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:fl_chart/fl_chart.dart';
|
|||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'dart:async';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'widgets/monitoring_shared.dart';
|
||||
import 'shared/widgets/monitoring_shared.dart';
|
||||
|
||||
/// EVAPORASI MONITORING SCREEN
|
||||
class EvaporasiMonitoringScreen extends StatefulWidget {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||
|
||||
class WindSpeedBloc {
|
||||
final MonitoringRepository _repo;
|
||||
|
||||
WindSpeedBloc({required MonitoringRepository repo}) : _repo = repo;
|
||||
|
||||
void startListening() {
|
||||
// 3. Sekarang kamu bisa akses _repo di dalam lingkup class ini
|
||||
_repo.getSensorStream('anemometer/realtime').listen((event) {
|
||||
// Olah datanya di sini
|
||||
print(event.snapshot.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
|
||||
import '../../shared/widgets/monitoring_shared.dart';
|
||||
|
||||
class WindSpeedAnnualSection extends StatelessWidget {
|
||||
final VoidCallback onExportAnnual;
|
||||
|
||||
const WindSpeedAnnualSection({
|
||||
super.key,
|
||||
required this.onExportAnnual,
|
||||
});
|
||||
final double _rataRata = 0.0;
|
||||
final double _kecepatanMax = 0.0;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Statistik Tahunan Kecepatan Angin",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: StatCard(
|
||||
title: "Rata-rata Tahunan",
|
||||
value: this._rataRata.toStringAsFixed(2),
|
||||
color: const Color.fromARGB(255, 76, 120, 175),
|
||||
icon: Icons.air,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: StatCard(
|
||||
title: "Kecepatan Max",
|
||||
value: this._kecepatanMax.toStringAsFixed(2),
|
||||
color: const Color.fromARGB(255, 109, 109, 109),
|
||||
icon: Icons.trending_up,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
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: const Color.fromARGB(255, 224, 224, 224),
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
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: 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),
|
||||
),
|
||||
),
|
||||
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),
|
||||
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: onExportAnnual,
|
||||
icon: const Icon(Icons.file_download),
|
||||
label: const Text("Download Excel Tahunan"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,10 +8,16 @@ class WindSpeedExplanation extends StatelessWidget {
|
|||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.green.shade200),
|
||||
),
|
||||
color: const Color.fromARGB(255, 232, 240, 245),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 3,
|
||||
spreadRadius: 0,
|
||||
offset: const Offset(0, 5),
|
||||
)
|
||||
]),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: const [
|
||||
|
|
@ -20,7 +26,7 @@ class WindSpeedExplanation extends StatelessWidget {
|
|||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green,
|
||||
color: Color.fromARGB(255, 0, 0, 0),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
|
|
@ -29,7 +35,7 @@ class WindSpeedExplanation extends StatelessWidget {
|
|||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1.6,
|
||||
color: Colors.green,
|
||||
color: Color.fromARGB(255, 0, 0, 0),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
|
||||
import '../../shared/widgets/monitoring_shared.dart';
|
||||
|
||||
class WindSpeedGraphBlock extends StatelessWidget {
|
||||
final String selectedPeriod;
|
||||
final List<double> dailySpeeds;
|
||||
final bool isOnline;
|
||||
final ValueChanged<String> onPeriodChanged;
|
||||
final ValueChanged<String> onExportPressed;
|
||||
|
||||
const WindSpeedGraphBlock({
|
||||
super.key,
|
||||
required this.selectedPeriod,
|
||||
required this.dailySpeeds,
|
||||
required this.isOnline,
|
||||
required this.onPeriodChanged,
|
||||
required this.onExportPressed,
|
||||
});
|
||||
|
||||
List<FlSpot> _getPeriodSpots() {
|
||||
switch (selectedPeriod) {
|
||||
case "Hari Ini":
|
||||
return List.generate(24, (i) {
|
||||
final value = dailySpeeds[i];
|
||||
return FlSpot(i.toDouble(), value);
|
||||
});
|
||||
case "Minggu Ini":
|
||||
return const [
|
||||
FlSpot(0, 6.5),
|
||||
FlSpot(1, 7.2),
|
||||
FlSpot(2, 7.8),
|
||||
FlSpot(3, 8.0),
|
||||
FlSpot(4, 7.5),
|
||||
FlSpot(5, 6.8),
|
||||
FlSpot(6, 5.5),
|
||||
];
|
||||
case "Bulan Ini":
|
||||
return const [
|
||||
FlSpot(0, 6.0),
|
||||
FlSpot(1, 6.5),
|
||||
FlSpot(2, 7.0),
|
||||
FlSpot(3, 7.5),
|
||||
FlSpot(4, 7.8),
|
||||
FlSpot(5, 8.0),
|
||||
FlSpot(6, 8.2),
|
||||
FlSpot(7, 8.0),
|
||||
FlSpot(8, 7.5),
|
||||
FlSpot(9, 7.0),
|
||||
FlSpot(10, 6.5),
|
||||
FlSpot(11, 6.0),
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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:
|
||||
isOnline ? Colors.green.shade100 : Colors.red.shade100,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
isOnline ? "ONLINE" : "OFFLINE",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isOnline ? Colors.green : Colors.red,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
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) {
|
||||
if (selected) {
|
||||
onPeriodChanged(period);
|
||||
}
|
||||
},
|
||||
side: BorderSide.none,
|
||||
backgroundColor: Colors.grey.withOpacity(0.1),
|
||||
selectedColor:
|
||||
const Color.fromARGB(255, 0, 136, 255),
|
||||
labelStyle: TextStyle(
|
||||
color: selectedPeriod == period
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => onExportPressed(selectedPeriod),
|
||||
icon: const Icon(Icons.download, size: 16),
|
||||
label: const Text("Export"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color.fromARGB(255, 0, 145, 255),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 6,
|
||||
),
|
||||
textStyle: const TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
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: _getPeriodSpots(),
|
||||
isCurved: true,
|
||||
barWidth: 2.5,
|
||||
dotData: FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, index) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 4,
|
||||
color: const Color.fromARGB(255, 0, 191, 255),
|
||||
strokeWidth: 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
color: const Color.fromARGB(255, 0, 191, 255),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
color: const Color.fromARGB(255, 0, 115, 255)
|
||||
.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class WindSpeedStatusBlock extends StatelessWidget {
|
||||
final double currentSpeed;
|
||||
final bool isOnline;
|
||||
final DateTime lastUpdateTime;
|
||||
|
||||
const WindSpeedStatusBlock({
|
||||
super.key,
|
||||
required this.currentSpeed,
|
||||
required this.isOnline,
|
||||
required this.lastUpdateTime,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final formattedTime =
|
||||
DateFormat("dd MMM yyyy • HH:mm:ss").format(lastUpdateTime);
|
||||
|
||||
/// ====== Current Speed bagian kode kecepatan angin ===== ///
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.withOpacity(0.12),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Kecepatan Angin",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"${currentSpeed.toStringAsFixed(2)} km/h",
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color.fromARGB(255, 0, 0, 0),
|
||||
),
|
||||
),
|
||||
|
||||
/// ==== bagian online offline status tampilan ==== ///
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isOnline ? Colors.green.shade100 : Colors.red.shade100,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
isOnline ? "ONLINE" : "OFFLINE",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isOnline ? Colors.green : Colors.red,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
"Update: $formattedTime",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
|
||||
import '../../widgets/monitoring_shared.dart';
|
||||
import '../../shared/widgets/monitoring_shared.dart';
|
||||
|
||||
class WindSpeedAnnualSection extends StatelessWidget {
|
||||
final VoidCallback onExportAnnual;
|
||||
|
|
@ -31,7 +31,7 @@ class WindSpeedAnnualSection extends StatelessWidget {
|
|||
child: StatCard(
|
||||
title: "Rata-rata Tahunan",
|
||||
value: this._rataRata.toStringAsFixed(2),
|
||||
color: Colors.green,
|
||||
color: const Color.fromARGB(255, 76, 120, 175),
|
||||
icon: Icons.air,
|
||||
),
|
||||
),
|
||||
|
|
@ -40,7 +40,7 @@ class WindSpeedAnnualSection extends StatelessWidget {
|
|||
child: StatCard(
|
||||
title: "Kecepatan Max",
|
||||
value: this._kecepatanMax.toStringAsFixed(2),
|
||||
color: Colors.red,
|
||||
color: const Color.fromARGB(255, 109, 109, 109),
|
||||
icon: Icons.trending_up,
|
||||
),
|
||||
),
|
||||
|
|
@ -73,7 +73,7 @@ class WindSpeedAnnualSection extends StatelessWidget {
|
|||
horizontalInterval: 2,
|
||||
getDrawingHorizontalLine: (value) {
|
||||
return FlLine(
|
||||
color: Colors.grey.shade300,
|
||||
color: const Color.fromARGB(255, 224, 224, 224),
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
|
|
@ -142,7 +142,7 @@ class WindSpeedAnnualSection extends StatelessWidget {
|
|||
),
|
||||
LineChartBarData(
|
||||
spots: const [
|
||||
FlSpot(0, 12.0), //sadds
|
||||
FlSpot(0, 12.0),
|
||||
FlSpot(1, 13.5),
|
||||
FlSpot(2, 14.0),
|
||||
FlSpot(3, 15.5),
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
|
||||
import '../../widgets/monitoring_shared.dart';
|
||||
import '../../shared/widgets/monitoring_shared.dart';
|
||||
|
||||
class WindSpeedGraphBlock extends StatelessWidget {
|
||||
final String selectedPeriod;
|
||||
|
|
@ -119,7 +119,10 @@ class WindSpeedGraphBlock extends StatelessWidget {
|
|||
onPeriodChanged(period);
|
||||
}
|
||||
},
|
||||
selectedColor: Colors.green,
|
||||
side: BorderSide.none,
|
||||
backgroundColor: Colors.grey.withOpacity(0.1),
|
||||
selectedColor:
|
||||
const Color.fromARGB(255, 0, 136, 255),
|
||||
labelStyle: TextStyle(
|
||||
color: selectedPeriod == period
|
||||
? Colors.white
|
||||
|
|
@ -137,7 +140,7 @@ class WindSpeedGraphBlock extends StatelessWidget {
|
|||
icon: const Icon(Icons.download, size: 16),
|
||||
label: const Text("Export"),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.orange,
|
||||
backgroundColor: const Color.fromARGB(255, 0, 145, 255),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
|
|
@ -226,15 +229,16 @@ class WindSpeedGraphBlock extends StatelessWidget {
|
|||
getDotPainter: (spot, percent, barData, index) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 4,
|
||||
color: Colors.green,
|
||||
color: const Color.fromARGB(255, 0, 191, 255),
|
||||
strokeWidth: 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
color: Colors.green,
|
||||
color: const Color.fromARGB(255, 0, 191, 255),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
color: Colors.green.withOpacity(0.1),
|
||||
color: const Color.fromARGB(255, 0, 115, 255)
|
||||
.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class WindSpeedStatusBlock extends StatelessWidget {
|
||||
final double currentSpeed;
|
||||
final bool isOnline;
|
||||
final DateTime lastUpdateTime;
|
||||
|
||||
const WindSpeedStatusBlock({
|
||||
super.key,
|
||||
required this.currentSpeed,
|
||||
required this.isOnline,
|
||||
required this.lastUpdateTime,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final formattedTime =
|
||||
DateFormat("dd MMM yyyy • HH:mm:ss").format(lastUpdateTime);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"${currentSpeed.toStringAsFixed(2)} km/h",
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 5,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isOnline ? Colors.green.shade100 : Colors.red.shade100,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
isOnline ? "ONLINE" : "OFFLINE",
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isOnline ? Colors.green : Colors.red,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"Update: $formattedTime",
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
import 'dart:async';
|
||||
|
||||
import '../';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../shared/widgets/monitoring_shared.dart';
|
||||
import 'views/annual_section.dart';
|
||||
import 'views/explanation.dart';
|
||||
import 'views/graph_block.dart';
|
||||
import 'views/status_block.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class WindSpeedMonitoringScreen extends StatefulWidget {
|
||||
const WindSpeedMonitoringScreen({super.key});
|
||||
|
||||
@override
|
||||
State<WindSpeedMonitoringScreen> createState() =>
|
||||
_WindSpeedMonitoringScreenState();
|
||||
}
|
||||
|
||||
class _WindSpeedMonitoringScreenState extends State<WindSpeedMonitoringScreen> {
|
||||
String _selectedPeriod = "Hari Ini";
|
||||
|
||||
// realtime values
|
||||
DateTime? _lastUpdateTime;
|
||||
double _currentSpeed = 0.0;
|
||||
final List<double> _dailySpeeds = List<double>.filled(24, 0.0);
|
||||
|
||||
// hourly aggregation helpers (PRO version)
|
||||
double _hourlyTotalSpeed = 0.0;
|
||||
int _hourlyCount = 0;
|
||||
int _currentHour = DateTime.now().hour;
|
||||
|
||||
bool get _isOnline {
|
||||
if (_lastUpdateTime == null) return false;
|
||||
final diff = DateTime.now().difference(_lastUpdateTime!);
|
||||
return diff.inSeconds <= 5;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_realtimeSub = _realtimeRef.onValue.listen(_onRealtimeData);
|
||||
}
|
||||
|
||||
void _onRealtimeData(DatabaseEvent event) {
|
||||
final data = event.snapshot.value;
|
||||
|
||||
if (data is Map) {
|
||||
final speed = (data['kecepatan'] ?? 0).toDouble();
|
||||
final timestamp = data['timestamp'] ?? 0;
|
||||
|
||||
final waktu =
|
||||
DateTime.fromMillisecondsSinceEpoch(timestamp * 1000).toLocal();
|
||||
|
||||
setState(() {
|
||||
_currentSpeed = speed;
|
||||
_lastUpdateTime = waktu;
|
||||
|
||||
final incomingHour = waktu.hour;
|
||||
|
||||
if (incomingHour != _currentHour) {
|
||||
if (_hourlyCount > 0) {
|
||||
final avg = _hourlyTotalSpeed / _hourlyCount;
|
||||
_dailySpeeds[_currentHour] = avg;
|
||||
}
|
||||
|
||||
_hourlyTotalSpeed = 0.0;
|
||||
_hourlyCount = 0;
|
||||
_currentHour = incomingHour;
|
||||
|
||||
if (incomingHour == 0) {
|
||||
for (int i = 0; i < 24; i++) {
|
||||
_dailySpeeds[i] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_hourlyTotalSpeed += speed;
|
||||
_hourlyCount++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_realtimeSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleExportSelectedPeriod(String period) {
|
||||
switch (period) {
|
||||
case "Hari Ini":
|
||||
_exportDailyWindData();
|
||||
break;
|
||||
case "Minggu Ini":
|
||||
_exportWeeklyWindData();
|
||||
break;
|
||||
case "Bulan Ini":
|
||||
_exportMonthlyWindData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 = [];
|
||||
|
||||
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 = [];
|
||||
final maxSpeed = [];
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
"Wind Speed",
|
||||
style: GoogleFonts.rubik(
|
||||
color: Colors.black, fontWeight: FontWeight.bold),
|
||||
),
|
||||
centerTitle: true,
|
||||
backgroundColor: const Color.fromARGB(37, 158, 158, 158),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_lastUpdateTime != null)
|
||||
WindSpeedStatusBlock(
|
||||
currentSpeed: _currentSpeed,
|
||||
isOnline: _isOnline,
|
||||
lastUpdateTime: _lastUpdateTime!,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
WindSpeedGraphBlock(
|
||||
selectedPeriod: _selectedPeriod,
|
||||
dailySpeeds: _dailySpeeds,
|
||||
isOnline: _isOnline,
|
||||
onPeriodChanged: (period) {
|
||||
setState(() {
|
||||
_selectedPeriod = period;
|
||||
});
|
||||
},
|
||||
onExportPressed: _handleExportSelectedPeriod,
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
const WindSpeedExplanation(),
|
||||
const SizedBox(height: 25),
|
||||
WindSpeedAnnualSection(
|
||||
onExportAnnual: _exportAnnualWindData,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,11 @@ import 'dart:async';
|
|||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../widgets/monitoring_shared.dart';
|
||||
import 'widgets/wind_speed_annual_section.dart';
|
||||
import 'widgets/wind_speed_explanation.dart';
|
||||
import 'widgets/wind_speed_graph_block.dart';
|
||||
import 'widgets/wind_speed_status_block.dart';
|
||||
import '../shared/widgets/monitoring_shared.dart';
|
||||
import 'views/annual_section.dart';
|
||||
import 'views/explanation.dart';
|
||||
import 'views/graph_block.dart';
|
||||
import 'views/status_block.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class WindSpeedMonitoringScreen extends StatefulWidget {
|
||||
|
|
@ -21,21 +21,6 @@ class WindSpeedMonitoringScreen extends StatefulWidget {
|
|||
class _WindSpeedMonitoringScreenState extends State<WindSpeedMonitoringScreen> {
|
||||
String _selectedPeriod = "Hari Ini";
|
||||
|
||||
// realtime database state
|
||||
final DatabaseReference _realtimeRef =
|
||||
FirebaseDatabase.instance.ref('anemometer/realtime');
|
||||
StreamSubscription<DatabaseEvent>? _realtimeSub;
|
||||
|
||||
// realtime values
|
||||
DateTime? _lastUpdateTime;
|
||||
double _currentSpeed = 0.0;
|
||||
final List<double> _dailySpeeds = List<double>.filled(24, 0.0);
|
||||
|
||||
// hourly aggregation helpers (PRO version)
|
||||
double _hourlyTotalSpeed = 0.0;
|
||||
int _hourlyCount = 0;
|
||||
int _currentHour = DateTime.now().hour;
|
||||
|
||||
bool get _isOnline {
|
||||
if (_lastUpdateTime == null) return false;
|
||||
final diff = DateTime.now().difference(_lastUpdateTime!);
|
||||
|
|
@ -55,7 +40,8 @@ class _WindSpeedMonitoringScreenState extends State<WindSpeedMonitoringScreen> {
|
|||
final speed = (data['kecepatan'] ?? 0).toDouble();
|
||||
final timestamp = data['timestamp'] ?? 0;
|
||||
|
||||
final waktu = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||
final waktu =
|
||||
DateTime.fromMillisecondsSinceEpoch(timestamp * 1000).toLocal();
|
||||
|
||||
setState(() {
|
||||
_currentSpeed = speed;
|
||||
|
|
@ -113,32 +99,7 @@ class _WindSpeedMonitoringScreenState extends State<WindSpeedMonitoringScreen> {
|
|||
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
|
||||
];
|
||||
final dailyData = [];
|
||||
|
||||
for (int i = 0; i < dailyData.length; i++) {
|
||||
csvContent.writeln("$i:00,${dailyData[i]}");
|
||||
|
|
@ -233,34 +194,8 @@ class _WindSpeedMonitoringScreenState extends State<WindSpeedMonitoringScreen> {
|
|||
'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
|
||||
];
|
||||
final averages = [];
|
||||
final maxSpeed = [];
|
||||
|
||||
for (int i = 0; i < months.length; i++) {
|
||||
csvContent.writeln("${months[i]},${averages[i]},${maxSpeed[i]}");
|
||||
|
|
@ -294,20 +229,13 @@ class _WindSpeedMonitoringScreenState extends State<WindSpeedMonitoringScreen> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Kecepatan Angin",
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (_lastUpdateTime != null)
|
||||
WindSpeedStatusBlock(
|
||||
currentSpeed: _currentSpeed,
|
||||
isOnline: _isOnline,
|
||||
lastUpdateTime: _lastUpdateTime!,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
WindSpeedGraphBlock(
|
||||
selectedPeriod: _selectedPeriod,
|
||||
dailySpeeds: _dailySpeeds,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
library monitoring_repository;
|
||||
|
||||
export 'src/monitoring_repo.dart';
|
||||
export 'src/firebase_monitoring_repo.dart';
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import 'dart:developer';
|
||||
import 'dart:async';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||
|
||||
/// === ambil data dari realtime database firebase === ///
|
||||
class FirebaseMonitoringRepo implements MonitoringRepository {
|
||||
final FirebaseDatabase _db = FirebaseDatabase.instance;
|
||||
|
||||
// Satu fungsi untuk semua jenis sensor
|
||||
// Kamu cukup masukkan "path" database-nya saja
|
||||
@override
|
||||
Stream<DatabaseEvent> getSensorStream(String path) {
|
||||
return _db.ref(path).onValue;
|
||||
}
|
||||
|
||||
@override
|
||||
// Jika ingin mengambil data sekali saja (bukan stream)
|
||||
Future<DataSnapshot> getSensorSnapshot(String path) async {
|
||||
return await _db.ref(path).get();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export 'wind_speed.dart';
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
class MyWindSpeed {
|
||||
double speed;
|
||||
int pulse;
|
||||
DateTime timestamp;
|
||||
|
||||
MyWindSpeed({
|
||||
required this.speed,
|
||||
required this.pulse,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
factory MyWindSpeed.fromJson(Map<dynamic, dynamic> json) {
|
||||
return MyWindSpeed(
|
||||
speed: (json['speed'] ?? 0).toDouble(),
|
||||
pulse: (json['pulse'] ?? 0) as int,
|
||||
timestamp: DateTime.fromMillisecondsSinceEpoch(
|
||||
(json['timestamp'] ?? 0) * 1000, // kalau dari Unix detik
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import 'package:firebase_database/firebase_database.dart';
|
||||
|
||||
abstract class MonitoringRepository {
|
||||
Stream<DatabaseEvent> getSensorStream(String path);
|
||||
|
||||
Future<DataSnapshot> getSensorSnapshot(String path);
|
||||
}
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
sha256: f698de6eb8a0dd7a9a931bbfe13568e8b77e702eb2deb13dd83480c5373e7746
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.68"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
firebase_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: "2f988dab915efde3b3105268dbd69efce0e8570d767a218ccd914afd0c10c8cc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.0"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_platform_interface
|
||||
sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
firebase_core_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
sha256: "1399ab1f0ac3b503d8a9be64a4c997fc066bbf33f701f42866e5569f26205ebe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.5.1"
|
||||
firebase_database:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_database
|
||||
sha256: "05358855166c943a2fe9321bce69ab6b583c9a49e70e01b5297ef03f8bea50b9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.2.0"
|
||||
firebase_database_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_database_platform_interface
|
||||
sha256: "83278cc315ad0f36a5e30fc7f3d1c6edc722340347d41ea17f57e52176bb98f8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
firebase_database_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_database_web
|
||||
sha256: "77df7f7c59f4bba33f06644f76e28a8acc594487f1aaa4c79947abf491a7ae70"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.7+5"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
rxdart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: rxdart
|
||||
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.27.7"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.7"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.2"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
sdks:
|
||||
dart: ">=3.10.7 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
name: monitoring_repository
|
||||
description: 'A new Flutter package project.'
|
||||
version: 0.0.1
|
||||
homepage:
|
||||
|
||||
environment:
|
||||
sdk: ^3.10.7
|
||||
flutter: '>=1.17.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
rxdart: ^0.27.7
|
||||
firebase_database: ^12.1.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# To add assets to your package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/to/asset-from-package
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
||||
# To add custom fonts to your package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/to/font-from-package
|
||||
|
|
@ -432,6 +432,13 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
monitoring_repository:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "packages/monitoring_repository"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
native_toolchain_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -669,5 +676,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.10.3 <4.0.0"
|
||||
dart: ">=3.10.7 <4.0.0"
|
||||
flutter: ">=3.38.4"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
name: klimatologiot
|
||||
description: "A new Flutter project."
|
||||
description: 'A new Flutter project.'
|
||||
publish_to: 'none'
|
||||
version: 1.0.0+1
|
||||
environment:
|
||||
|
|
@ -22,6 +22,8 @@ dependencies:
|
|||
rxdart: ^0.27.7
|
||||
user_repository:
|
||||
path: packages/user_repository
|
||||
monitoring_repository:
|
||||
path: packages/monitoring_repository
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^3.0.0
|
||||
|
|
|
|||
|
|
@ -5,15 +5,80 @@
|
|||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||
import 'package:user_repository/user_repository.dart';
|
||||
import '../lib/app.dart';
|
||||
|
||||
import 'package:klimatologiot/main.dart';
|
||||
// import 'package:klimatologiot/main.dart';
|
||||
|
||||
class FakeUserRepository implements UserRepository {
|
||||
@override
|
||||
Stream<MyUser?> get user => const Stream.empty();
|
||||
|
||||
@override
|
||||
Future<void> logOut() async {}
|
||||
|
||||
@override
|
||||
Future<void> setUserData(MyUser user) {
|
||||
// TODO: implement setUserData
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> signIn(String email, String password) {
|
||||
// TODO: implement signIn
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> signInWithGoogle() {
|
||||
// TODO: implement signInWithGoogle
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MyUser> signUp(MyUser myUser, String password) {
|
||||
// TODO: implement signUp
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
// tambahin kalau ada method lain
|
||||
}
|
||||
|
||||
class FakeMonitoringRepository implements MonitoringRepository {
|
||||
get _db => null;
|
||||
|
||||
// Satu fungsi untuk semua jenis sensor
|
||||
// Kamu cukup masukkan "path" database-nya saja
|
||||
|
||||
@override
|
||||
// Jika ingin mengambil data sekali saja (bukan stream)
|
||||
Future<DataSnapshot> getSensorSnapshot(String path) async {
|
||||
return await _db.ref(path).get();
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<DatabaseEvent> getSensorStream(String path) {
|
||||
// TODO: implement getSensorStream
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
testWidgets('App loads', (WidgetTester tester) async {
|
||||
final fakeRepo = FakeUserRepository();
|
||||
final fakeMonitoring = FakeUserRepository();
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
await tester.pumpWidget(
|
||||
MyApp(fakeRepo, fakeMonitoring as MonitoringRepository),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(MaterialApp), findsOneWidget);
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
|
|
|
|||
Loading…
Reference in New Issue