From 60b2047eaef9c0cbd24850ba2006c830c2b33dbc Mon Sep 17 00:00:00 2001 From: kleponijo <158453666+kleponijo@users.noreply.github.com> Date: Sun, 12 Apr 2026 17:28:16 +0700 Subject: [PATCH] jossss --- lib/screens/home/views/home_screen.dart | 774 +----------------- .../wind_speed/blocs/wind_speed_bloc.dart | 45 +- .../wind_speed/blocs/wind_speed_event.dart | 24 + .../wind_speed/blocs/wind_speed_state.dart | 33 + .../wind_speed/views/annual_section.dart | 226 ----- .../wind_speed/views/explanation.dart | 45 - .../wind_speed/views/graph_block.dart | 253 ------ .../wind_speed/views/status_block.dart | 95 --- .../wind_speed/views/wind_speed.dart | 1 + .../views/wind_speed_annual_section.dart | 226 ----- .../views/wind_speed_graph_block.dart | 253 ------ .../monitoring/wind_speed/wind_speed.dart | 272 ------ .../wind_speed_monitoring_screen.dart | 262 ------ .../lib/monitoring_repository.dart | 1 + .../lib/src/monitoring_repo.dart | 1 - 15 files changed, 98 insertions(+), 2413 deletions(-) delete mode 100644 lib/screens/monitoring/wind_speed/views/annual_section.dart delete mode 100644 lib/screens/monitoring/wind_speed/views/explanation.dart delete mode 100644 lib/screens/monitoring/wind_speed/views/graph_block.dart delete mode 100644 lib/screens/monitoring/wind_speed/views/status_block.dart create mode 100644 lib/screens/monitoring/wind_speed/views/wind_speed.dart delete mode 100644 lib/screens/monitoring/wind_speed/views/wind_speed_annual_section.dart delete mode 100644 lib/screens/monitoring/wind_speed/views/wind_speed_graph_block.dart delete mode 100644 lib/screens/monitoring/wind_speed/wind_speed.dart delete mode 100644 lib/screens/monitoring/wind_speed/wind_speed_monitoring_screen.dart diff --git a/lib/screens/home/views/home_screen.dart b/lib/screens/home/views/home_screen.dart index 8521eb9..2831845 100644 --- a/lib/screens/home/views/home_screen.dart +++ b/lib/screens/home/views/home_screen.dart @@ -1,780 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -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.dart'; -import '../../monitoring/air_quality_monitoring_screen.dart'; -class HomeScreen extends StatefulWidget { +class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); - @override - State createState() => _HomeScreenState(); -} - -// simple model to represent one activity log item -class ActivityItem { - final String type; // evaporasi, angin, udara, etc - final String message; - final DateTime time; - - ActivityItem({ - required this.type, - required this.message, - required this.time, - }); -} - -class _HomeScreenState extends State { - // Firebase realtime database references - final DatabaseReference _evaporasiRef = - FirebaseDatabase.instance.ref('kelompok1/evaporasi'); - final DatabaseReference _windSpeedRef = - FirebaseDatabase.instance.ref('anemometer/realtime'); - final DatabaseReference _airQualityRef = - FirebaseDatabase.instance.ref('air_quality/realtime'); - - // Stream subscriptions - StreamSubscription? _evaporasiSub; - StreamSubscription? _windSpeedSub; - StreamSubscription? _airQualitySub; - - // Realtime values - double _currentEvaporasi = 0.0; - double _currentWindSpeed = 0.0; - double _currentAirQuality = 0.0; - - DateTime? _evaporasiUpdateTime; - DateTime? _windSpeedUpdateTime; - DateTime? _airQualityUpdateTime; - - // Hourly data for charts (24 hours) - final List _evaporasiData = List.filled(24, 0.0); - final List _windSpeedData = List.filled(24, 0.0); - final List _airQualityData = List.filled(24, 0.0); - - // activity log - final List _activities = []; - - // Hourly aggregation helpers - double _evaporasiHourlyTotal = 0.0; - int _evaporasiHourlyCount = 0; - - double _windSpeedHourlyTotal = 0.0; - int _windSpeedHourlyCount = 0; - - double _airQualityHourlyTotal = 0.0; - int _airQualityHourlyCount = 0; - - int _currentHour = DateTime.now().hour; - - @override - void initState() { - super.initState(); - _evaporasiSub = _evaporasiRef.onValue.listen(_onEvaporasiData); - _windSpeedSub = _windSpeedRef.onValue.listen(_onWindSpeedData); - _airQualitySub = _airQualityRef.onValue.listen(_onAirQualityData); - } - - void _onEvaporasiData(DatabaseEvent event) { - final data = event.snapshot.value; - if (data is Map) { - final value = double.tryParse( - (data['evaporasi'] ?? data['nilai'] ?? 0).toString()) ?? - 0.0; - DateTime waktu; - - if (data.containsKey('timestamp')) { - final timestamp = - int.tryParse(data['timestamp']?.toString() ?? '0') ?? 0; - waktu = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); - } else { - final hour = int.tryParse(data['jam']?.toString() ?? '0') ?? 0; - final minute = int.tryParse(data['menit']?.toString() ?? '0') ?? 0; - final now = DateTime.now(); - waktu = DateTime(now.year, now.month, now.day, hour, minute); - } - - setState(() { - _currentEvaporasi = value; - _evaporasiUpdateTime = waktu; - - // add to activity log - _activities.insert( - 0, - ActivityItem( - type: "evaporasi", - message: "Evaporasi tercatat ${value.toStringAsFixed(1)} mm", - time: waktu, - ), - ); - if (_activities.length > 50) _activities.removeLast(); - - final incomingHour = waktu.hour; - - if (incomingHour != _currentHour) { - if (_evaporasiHourlyCount > 0) { - final avg = _evaporasiHourlyTotal / _evaporasiHourlyCount; - _evaporasiData[_currentHour] = avg; - } - - _evaporasiHourlyTotal = 0.0; - _evaporasiHourlyCount = 0; - - _currentHour = incomingHour; - - if (incomingHour == 0) { - for (int i = 0; i < 24; i++) { - _evaporasiData[i] = 0.0; - } - } - } - - _evaporasiHourlyTotal += value; - _evaporasiHourlyCount++; - }); - } - } - - void _onWindSpeedData(DatabaseEvent event) { - final data = event.snapshot.value; - if (data is Map) { - final value = (data['kecepatan'] ?? 0).toDouble(); - final timestamp = data['timestamp'] ?? 0; - final waktu = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); - - setState(() { - _currentWindSpeed = value; - _windSpeedUpdateTime = waktu; - - // add to activity log - _activities.insert( - 0, - ActivityItem( - type: "angin", - message: - "Kecepatan angin update menjadi ${value.toStringAsFixed(1)} km/h", - time: waktu, - ), - ); - if (_activities.length > 50) _activities.removeLast(); - - final incomingHour = waktu.hour; - - if (incomingHour != _currentHour) { - if (_windSpeedHourlyCount > 0) { - final avg = _windSpeedHourlyTotal / _windSpeedHourlyCount; - _windSpeedData[_currentHour] = avg; - } - - _windSpeedHourlyTotal = 0.0; - _windSpeedHourlyCount = 0; - - _currentHour = incomingHour; - - if (incomingHour == 0) { - for (int i = 0; i < 24; i++) { - _windSpeedData[i] = 0.0; - } - } - } - - _windSpeedHourlyTotal += value; - _windSpeedHourlyCount++; - }); - } - } - - void _onAirQualityData(DatabaseEvent event) { - final data = event.snapshot.value; - if (data is Map) { - final value = (data['aqi'] ?? 0).toDouble(); - final timestamp = data['timestamp'] ?? 0; - final waktu = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); - - setState(() { - _currentAirQuality = value; - _airQualityUpdateTime = waktu; - - // add to activity log - _activities.insert( - 0, - ActivityItem( - type: "udara", - message: "AQI berubah menjadi ${value.toStringAsFixed(0)}", - time: waktu, - ), - ); - if (_activities.length > 50) _activities.removeLast(); - - final incomingHour = waktu.hour; - - if (incomingHour != _currentHour) { - if (_airQualityHourlyCount > 0) { - final avg = _airQualityHourlyTotal / _airQualityHourlyCount; - _airQualityData[_currentHour] = avg; - } - - _airQualityHourlyTotal = 0.0; - _airQualityHourlyCount = 0; - - _currentHour = incomingHour; - - if (incomingHour == 0) { - for (int i = 0; i < 24; i++) { - _airQualityData[i] = 0.0; - } - } - } - - _airQualityHourlyTotal += value; - _airQualityHourlyCount++; - }); - } - } - - @override - void dispose() { - _evaporasiSub?.cancel(); - _windSpeedSub?.cancel(); - _airQualitySub?.cancel(); - super.dispose(); - } - @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 (dynamic dari AuthenticationBloc) - BlocBuilder( - builder: (context, state) { - final user = state.user; - final displayName = (user != null && user.name.isNotEmpty) - ? user.name - : 'Pengguna'; - final email = (user != null && user.email.isNotEmpty) - ? user.email - : 'Tidak ada email'; - final initials = (displayName.isNotEmpty) - ? displayName - .trim() - .split(' ') - .map((e) => e.isNotEmpty ? e[0] : '') - .take(2) - .join() - : 'U'; - - return UserAccountsDrawerHeader( - accountName: Text( - displayName, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - accountEmail: Text(email), - currentAccountPicture: CircleAvatar( - backgroundColor: Colors.blue, - child: Text( - initials.toUpperCase(), - style: const 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, - ), - const Divider(), - ListTile( - leading: const Icon(Icons.logout), - title: const Text('Logout'), - onTap: () async { - Navigator.pop(context); - // trigger logout via bloc - final bloc = context.read(); - bloc.add(const AuthenticationLogoutRequested()); - }, - ), - ], - ), - ), - ), - - /// 🔹 DASHBOARD UTAMA - 3 MONITORING - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - BlocBuilder( - builder: (context, state) { - final name = state.user?.name.isNotEmpty == true - ? state.user!.name - : 'Pengguna'; - return Text( - 'Selamat Datang $name', - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - ), - ); - }, - ), - const SizedBox(height: 10), - const Text( - "Recent Activity", - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - Expanded( - child: _activities.isEmpty - ? const Center(child: Text("Belum ada aktivitas")) - : ListView.builder( - itemCount: _activities.length, - itemBuilder: (context, index) { - final item = _activities[index]; - return _buildActivityTile(item); - }, - ), - ), - ], - ), - ), - ); - } - - /// 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 _getMonitoringData(String title) { - late final List data; - - switch (title) { - case "Evaporasi": - data = _evaporasiData; - break; - case "Kecepatan Angin": - data = _windSpeedData; - break; - case "Kualitas Udara": - data = _airQualityData; - break; - default: - data = []; - } - - return List.generate( - data.length, - (index) => FlSpot(index.toDouble(), data[index]), - ); - } - - /// GET AVERAGE VALUE - String _getAverageData(String title) { - late final double average; - late final String unit; - - switch (title) { - case "Evaporasi": - average = _evaporasiData.isNotEmpty - ? _evaporasiData.reduce((a, b) => a + b) / _evaporasiData.length - : 0.0; - unit = "mm"; - break; - case "Kecepatan Angin": - average = _windSpeedData.isNotEmpty - ? _windSpeedData.reduce((a, b) => a + b) / _windSpeedData.length - : 0.0; - unit = "km/h"; - break; - case "Kualitas Udara": - average = _airQualityData.isNotEmpty - ? _airQualityData.reduce((a, b) => a + b) / _airQualityData.length - : 0.0; - unit = "AQI"; - break; - default: - return "N/A"; - } - - return "${average.toStringAsFixed(1)} $unit"; - } - - /// 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; - } - } - - /// ACTIVITY TILE - Widget _buildActivityTile(ActivityItem item) { - IconData icon; - Color color; - - switch (item.type) { - case "evaporasi": - icon = Icons.opacity; - color = Colors.blue; - break; - case "angin": - icon = Icons.air; - color = Colors.green; - break; - case "udara": - icon = Icons.cloud; - color = Colors.orange; - break; - default: - icon = Icons.info; - color = Colors.grey; - } - - return Card( - margin: const EdgeInsets.only(bottom: 12), - child: ListTile( - leading: CircleAvatar( - backgroundColor: color.withOpacity(0.2), - child: Icon(icon, color: color), - ), - title: Text(item.message), - subtitle: Text( - "${item.time.hour}:${item.time.minute.toString().padLeft(2, '0')}", - ), - ), - ); + return Scaffold(); } } diff --git a/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart b/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart index b320ff6..e5b929f 100644 --- a/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart +++ b/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart @@ -1,15 +1,44 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; import 'package:monitoring_repository/monitoring_repository.dart'; -class WindSpeedBloc { - final MonitoringRepository _repo; +part 'wind_speed_event.dart'; +part 'wind_speed_state.dart'; - WindSpeedBloc({required MonitoringRepository repo}) : _repo = repo; +class WindSpeedBloc extends Bloc { + final MonitoringRepository _repository; + StreamSubscription? _subscription; - 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); + WindSpeedBloc({required MonitoringRepository repository}) + : _repository = repository, + super(const WindSpeedState()) { + // Handler saat aplikasi minta mulai monitoring + on((event, emit) { + _subscription?.cancel(); + _subscription = _repository + .getSensorStream( + 'anemometer/realtime', (json) => MyWindSpeed.fromJson(json)) + .listen((data) => add(_WindSpeedUpdated(data))); + }); + + // Handler saat ada data baru masuk (Pindahan logika dari Screen) + on<_WindSpeedUpdated>((event, emit) { + // Di sini kamu bisa tambahkan logika hitung rata-rata/grafik harian + emit(state.copyWith( + currentSpeed: event.data.speed, + isLoading: false, + )); + }); + + on((event, emit) { + emit(state.copyWith(selectedPeriod: event.period)); }); } + + @override + Future close() { + _subscription?.cancel(); // Supaya tidak bocor memorinya + return super.close(); + } } diff --git a/lib/screens/monitoring/wind_speed/blocs/wind_speed_event.dart b/lib/screens/monitoring/wind_speed/blocs/wind_speed_event.dart index e69de29..445c8c6 100644 --- a/lib/screens/monitoring/wind_speed/blocs/wind_speed_event.dart +++ b/lib/screens/monitoring/wind_speed/blocs/wind_speed_event.dart @@ -0,0 +1,24 @@ +part of 'wind_speed_bloc.dart'; + +sealed class WindSpeedEvent extends Equatable { + const WindSpeedEvent(); + @override + List get props => []; +} + +// Perintah untuk mulai dengerin data Firebase +class WatchWindSpeedStarted extends WindSpeedEvent {} + +// Perintah kalau user ganti filter (Hari Ini, Minggu Ini, dll) +class WindSpeedPeriodChanged extends WindSpeedEvent { + final String period; + const WindSpeedPeriodChanged(this.period); +} + +// Event internal: saat data baru masuk dari Firebase +class _WindSpeedUpdated extends WindSpeedEvent { + final MyWindSpeed data; + const _WindSpeedUpdated(this.data); + @override + List get props => [data]; +} diff --git a/lib/screens/monitoring/wind_speed/blocs/wind_speed_state.dart b/lib/screens/monitoring/wind_speed/blocs/wind_speed_state.dart index e69de29..fa9b167 100644 --- a/lib/screens/monitoring/wind_speed/blocs/wind_speed_state.dart +++ b/lib/screens/monitoring/wind_speed/blocs/wind_speed_state.dart @@ -0,0 +1,33 @@ +part of 'wind_speed_bloc.dart'; + +class WindSpeedState extends Equatable { + final double currentSpeed; + final String selectedPeriod; + final List dailySpeeds; // Untuk grafik + final bool isLoading; + + const WindSpeedState({ + this.currentSpeed = 0.0, + this.selectedPeriod = "Hari Ini", + this.dailySpeeds = const [], + this.isLoading = true, + }); + + WindSpeedState copyWith({ + double? currentSpeed, + String? selectedPeriod, + List? dailySpeeds, + bool? isLoading, + }) { + return WindSpeedState( + currentSpeed: currentSpeed ?? this.currentSpeed, + selectedPeriod: selectedPeriod ?? this.selectedPeriod, + dailySpeeds: dailySpeeds ?? this.dailySpeeds, + isLoading: isLoading ?? this.isLoading, + ); + } + + @override + List get props => + [currentSpeed, selectedPeriod, dailySpeeds, isLoading]; +} diff --git a/lib/screens/monitoring/wind_speed/views/annual_section.dart b/lib/screens/monitoring/wind_speed/views/annual_section.dart deleted file mode 100644 index b414642..0000000 --- a/lib/screens/monitoring/wind_speed/views/annual_section.dart +++ /dev/null @@ -1,226 +0,0 @@ -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, - ), - ), - ), - ], - ); - } -} diff --git a/lib/screens/monitoring/wind_speed/views/explanation.dart b/lib/screens/monitoring/wind_speed/views/explanation.dart deleted file mode 100644 index 49b3340..0000000 --- a/lib/screens/monitoring/wind_speed/views/explanation.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter/material.dart'; - -class WindSpeedExplanation extends StatelessWidget { - const WindSpeedExplanation({super.key}); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - 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 [ - Text( - "Penjelasan Kecepatan Angin", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Color.fromARGB(255, 0, 0, 0), - ), - ), - SizedBox(height: 10), - 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: Color.fromARGB(255, 0, 0, 0), - ), - ), - ], - ), - ); - } -} diff --git a/lib/screens/monitoring/wind_speed/views/graph_block.dart b/lib/screens/monitoring/wind_speed/views/graph_block.dart deleted file mode 100644 index c24a730..0000000 --- a/lib/screens/monitoring/wind_speed/views/graph_block.dart +++ /dev/null @@ -1,253 +0,0 @@ -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 dailySpeeds; - final bool isOnline; - final ValueChanged onPeriodChanged; - final ValueChanged onExportPressed; - - const WindSpeedGraphBlock({ - super.key, - required this.selectedPeriod, - required this.dailySpeeds, - required this.isOnline, - required this.onPeriodChanged, - required this.onExportPressed, - }); - - List _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), - ), - ), - ], - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/screens/monitoring/wind_speed/views/status_block.dart b/lib/screens/monitoring/wind_speed/views/status_block.dart deleted file mode 100644 index 24b305a..0000000 --- a/lib/screens/monitoring/wind_speed/views/status_block.dart +++ /dev/null @@ -1,95 +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); - - /// ====== 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, - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/screens/monitoring/wind_speed/views/wind_speed.dart b/lib/screens/monitoring/wind_speed/views/wind_speed.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/screens/monitoring/wind_speed/views/wind_speed.dart @@ -0,0 +1 @@ + diff --git a/lib/screens/monitoring/wind_speed/views/wind_speed_annual_section.dart b/lib/screens/monitoring/wind_speed/views/wind_speed_annual_section.dart deleted file mode 100644 index b414642..0000000 --- a/lib/screens/monitoring/wind_speed/views/wind_speed_annual_section.dart +++ /dev/null @@ -1,226 +0,0 @@ -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, - ), - ), - ), - ], - ); - } -} diff --git a/lib/screens/monitoring/wind_speed/views/wind_speed_graph_block.dart b/lib/screens/monitoring/wind_speed/views/wind_speed_graph_block.dart deleted file mode 100644 index c24a730..0000000 --- a/lib/screens/monitoring/wind_speed/views/wind_speed_graph_block.dart +++ /dev/null @@ -1,253 +0,0 @@ -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 dailySpeeds; - final bool isOnline; - final ValueChanged onPeriodChanged; - final ValueChanged onExportPressed; - - const WindSpeedGraphBlock({ - super.key, - required this.selectedPeriod, - required this.dailySpeeds, - required this.isOnline, - required this.onPeriodChanged, - required this.onExportPressed, - }); - - List _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), - ), - ), - ], - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/screens/monitoring/wind_speed/wind_speed.dart b/lib/screens/monitoring/wind_speed/wind_speed.dart deleted file mode 100644 index db2c12a..0000000 --- a/lib/screens/monitoring/wind_speed/wind_speed.dart +++ /dev/null @@ -1,272 +0,0 @@ -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 createState() => - _WindSpeedMonitoringScreenState(); -} - -class _WindSpeedMonitoringScreenState extends State { - String _selectedPeriod = "Hari Ini"; - - // realtime values - DateTime? _lastUpdateTime; - double _currentSpeed = 0.0; - final List _dailySpeeds = List.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, - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/screens/monitoring/wind_speed/wind_speed_monitoring_screen.dart b/lib/screens/monitoring/wind_speed/wind_speed_monitoring_screen.dart deleted file mode 100644 index b60ca89..0000000 --- a/lib/screens/monitoring/wind_speed/wind_speed_monitoring_screen.dart +++ /dev/null @@ -1,262 +0,0 @@ -import 'dart:async'; - -import 'package:firebase_database/firebase_database.dart'; -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 createState() => - _WindSpeedMonitoringScreenState(); -} - -class _WindSpeedMonitoringScreenState extends State { - String _selectedPeriod = "Hari Ini"; - - 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, - ), - ], - ), - ), - ), - ); - } -} diff --git a/packages/monitoring_repository/lib/monitoring_repository.dart b/packages/monitoring_repository/lib/monitoring_repository.dart index 626b971..e5ff71e 100644 --- a/packages/monitoring_repository/lib/monitoring_repository.dart +++ b/packages/monitoring_repository/lib/monitoring_repository.dart @@ -2,3 +2,4 @@ library monitoring_repository; export 'src/monitoring_repo.dart'; export 'src/firebase_monitoring_repo.dart'; +export 'src/models/models.dart'; diff --git a/packages/monitoring_repository/lib/src/monitoring_repo.dart b/packages/monitoring_repository/lib/src/monitoring_repo.dart index 74f922f..d97747b 100644 --- a/packages/monitoring_repository/lib/src/monitoring_repo.dart +++ b/packages/monitoring_repository/lib/src/monitoring_repo.dart @@ -1,4 +1,3 @@ -import 'package:firebase_database/firebase_database.dart'; import 'models/models.dart'; abstract class MonitoringRepository {