diff --git a/analyze_log.txt b/analyze_log.txt deleted file mode 100644 index fe29a4a..0000000 Binary files a/analyze_log.txt and /dev/null differ diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html deleted file mode 100644 index 9ff7a95..0000000 --- a/android/build/reports/problems/problems-report.html +++ /dev/null @@ -1,663 +0,0 @@ - - - - - - - - - - - - - Gradle Configuration Cache - - - -
- -
- Loading... -
- - - - - - diff --git a/build_log.txt b/build_log.txt deleted file mode 100644 index 1b849d5..0000000 Binary files a/build_log.txt and /dev/null differ diff --git a/build_log2.txt b/build_log2.txt deleted file mode 100644 index 8da8340..0000000 Binary files a/build_log2.txt and /dev/null differ diff --git a/lib/core/utils/time_series_mapper.dart b/lib/core/utils/time_series_mapper.dart index 1300b84..0fa54cb 100644 --- a/lib/core/utils/time_series_mapper.dart +++ b/lib/core/utils/time_series_mapper.dart @@ -1,5 +1,3 @@ -import 'package:monitoring_repository/monitoring_repository.dart'; - class TimeSeriesMapper { /// ========================= /// 📅 DAILY (24 JAM) @@ -144,30 +142,4 @@ class TimeSeriesMapper { return result; } - - // ── Tambahkan ini setelah method smooth() ────────────────────── - - /// Shortcut untuk model yang implement HasTimestamp. - /// Dari: TimeSeriesMapper.toDaily(data: h, getTime: (e) => e.timestamp, getValue: (e) => e.speed) - /// Ke: TimeSeriesMapper.dailyFrom(h, (e) => e.speed) - static List dailyFrom( - List data, - double Function(T) getValue, - ) => - smooth( - toDaily(data: data, getTime: (e) => e.timestamp, getValue: getValue)); - - static List weeklyFrom( - List data, - double Function(T) getValue, - ) => - smooth(toWeekly( - data: data, getTime: (e) => e.timestamp, getValue: getValue)); - - static List monthlyFrom( - List data, - double Function(T) getValue, - ) => - smooth(toMonthly( - data: data, getTime: (e) => e.timestamp, getValue: getValue)); } 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 05f0616..9a995a7 100644 --- a/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart +++ b/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart @@ -48,16 +48,32 @@ class WindSpeedBloc extends Bloc { final history = await _repository.getSensorHistory( 'anemometer/history', (json) => MyWindSpeed.fromJson(json), - orderByChild: 'timestamp', - limit: 500, ); - final dailyGraph = - TimeSeriesMapper.dailyFrom(history, (e) => e.speed); - final weekly = - TimeSeriesMapper.weeklyFrom(history, (e) => e.speed); - final monthly = - TimeSeriesMapper.monthlyFrom(history, (e) => e.speed); + // ✅ Ini saja yang benar + final dailyGraph = TimeSeriesMapper.smooth( + TimeSeriesMapper.toDaily( + data: history, + getTime: (e) => e.timestamp, + getValue: (e) => e.speed, + ), + ); + + final weekly = TimeSeriesMapper.smooth( + TimeSeriesMapper.toWeekly( + data: history, + getTime: (e) => e.timestamp, + getValue: (e) => e.speed, + ), + ); + + final monthly = TimeSeriesMapper.smooth( + TimeSeriesMapper.toMonthly( + data: history, + getTime: (e) => e.timestamp, + getValue: (e) => e.speed, + ), + ); // Cek kondisi awal dari history if (history.isNotEmpty) { @@ -125,11 +141,48 @@ class WindSpeedBloc extends Bloc { /// ========================= /// 📊 CHANGE PERIOD /// ========================= - void _onPeriodChanged( + Future _onPeriodChanged( WindSpeedPeriodChanged event, Emitter emit, - ) { + ) async { emit(state.copyWith(selectedPeriod: event.period)); + + final history = state.history; + + List raw; + + if (event.period == "Minggu Ini") { + raw = TimeSeriesMapper.toWeekly( + data: history, + getTime: (e) => e.timestamp, + getValue: (e) => e.speed, + ); + } else if (event.period == "Bulan Ini") { + raw = TimeSeriesMapper.toMonthly( + data: history, + getTime: (e) => e.timestamp, + getValue: (e) => e.speed, + ); + } else { + raw = TimeSeriesMapper.toDaily( + data: history, + getTime: (e) => e.timestamp, + getValue: (e) => e.speed, + ); + } + + /// 🔥 baru di-smooth + final updatedGraph = TimeSeriesMapper.smooth(raw); + + emit(state.copyWith( + dailySpeeds: + event.period == "Hari Ini" ? updatedGraph : state.dailySpeeds, + weeklySpeeds: + event.period == "Minggu Ini" ? updatedGraph : state.weeklySpeeds, + monthlySpeeds: + event.period == "Bulan Ini" ? updatedGraph : state.monthlySpeeds, + isLoading: false, + )); } @override diff --git a/packages/monitoring_repository/lib/monitoring_repository.dart b/packages/monitoring_repository/lib/monitoring_repository.dart index b69e0b2..e5ff71e 100644 --- a/packages/monitoring_repository/lib/monitoring_repository.dart +++ b/packages/monitoring_repository/lib/monitoring_repository.dart @@ -3,4 +3,3 @@ library monitoring_repository; export 'src/monitoring_repo.dart'; export 'src/firebase_monitoring_repo.dart'; export 'src/models/models.dart'; -export 'src/utils/timestamp_parser.dart'; diff --git a/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart b/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart index a8f54eb..f7cf78c 100644 --- a/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart +++ b/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart @@ -1,61 +1,72 @@ +// import 'dart:developer'; import 'dart:async'; +// import 'package:rxdart/rxdart.dart'; import 'package:firebase_database/firebase_database.dart'; -import 'monitoring_repo.dart'; -import 'models/has_timestamp.dart'; -import 'models/models.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 getSensorStream( String path, T Function(Map json) mapper, ) { + /// === ambil data mentah dari firebase === /// return _db.ref(path).onValue.map((event) { - final value = event.snapshot.value; - return mapper(value is Map ? value : {}); + final Object? value = event.snapshot.value; + + if (value is Map) { + return mapper(value); + } else { + // Jika data kosong atau bukan Map, berikan Map kosong agar mapper tidak crash + return mapper({}); + } }); } @override + // Jika ingin mengambil data sekali saja (bukan stream) Future getSensorSnapshot( String path, T Function(Map json) mapper, ) async { final snapshot = await _db.ref(path).get(); - return mapper(snapshot.value as Map? ?? {}); + final data = snapshot.value as Map? ?? {}; + return mapper(data); } @override - Future> getSensorHistory( + Future> getSensorHistory( String path, - T Function(Map json) mapper, { - String? orderByChild, - int limit = 500, - }) async { + T Function(Map json) mapper, + ) async { try { - Query query = _db.ref(path); + // Ambil data dari path history + final snapshot = await _db.ref(path).get(); - if (orderByChild != null) { - query = query.orderByChild(orderByChild).limitToLast(limit); - } else { - query = query.limitToLast(limit); + if (snapshot.exists && snapshot.value is Map) { + final Map data = snapshot.value as Map; + + final list = data.values.map((item) { + return mapper(item as Map); + }).toList(); + + // ✅ Sort by timestamp — Firebase push tidak jamin urutan + list.sort((a, b) { + if (a is MyWindSpeed && b is MyWindSpeed) { + return a.timestamp.compareTo(b.timestamp); + } + return 0; + }); + + return list; } - - final snapshot = await query.get(); - if (!snapshot.exists || snapshot.value is! Map) return []; - - final data = snapshot.value as Map; - final list = data.values - .whereType() - .map((item) => mapper(item)) - .toList(); - - // Sort generik — works untuk SEMUA sensor karena T extends HasTimestamp - list.sort((a, b) => a.timestamp.compareTo(b.timestamp)); - return list; - } catch (_) { + return []; + } catch (e) { return []; } } diff --git a/packages/monitoring_repository/lib/src/models/evaporasi.dart b/packages/monitoring_repository/lib/src/models/evaporasi.dart index caec82e..907cbc5 100644 --- a/packages/monitoring_repository/lib/src/models/evaporasi.dart +++ b/packages/monitoring_repository/lib/src/models/evaporasi.dart @@ -1,13 +1,7 @@ -import 'has_timestamp.dart'; -import '../utils/timestamp_parser.dart'; - -class Evaporasi implements HasTimestamp { +class Evaporasi { final double evaporasi; final double suhu; final double tinggiAir; - final String status; - - @override final DateTime timestamp; Evaporasi({ @@ -15,7 +9,6 @@ class Evaporasi implements HasTimestamp { required this.suhu, required this.tinggiAir, required this.timestamp, - this.status = '', }); static final empty = Evaporasi( @@ -26,22 +19,27 @@ class Evaporasi implements HasTimestamp { ); factory Evaporasi.fromJson(Map json) { - // ✅ Handle perbedaan field name antara history dan realtime: - // History path : suhu, tinggi, evaporasi, waktu - // Realtime path : suhu_air, tinggi_air, evaporasi, waktu, status - final suhu = (json['suhu'] ?? json['suhu_air'] ?? 0).toDouble(); - final tinggi = (json['tinggi'] ?? json['tinggi_air'] ?? 0).toDouble(); + final now = DateTime.now(); + DateTime timestamp = now; - // ✅ Prioritas: timestamp (Unix, kalau firmware sudah diupdate) - // → fallback ke waktu ("HH:MM:SS") - final rawTime = json['timestamp'] ?? json['waktu']; + final waktuStr = json['waktu'] as String?; + if (waktuStr != null) { + final parts = waktuStr.split(':'); + if (parts.length >= 2) { + final jam = int.tryParse(parts[0]) ?? 0; + final menit = int.tryParse(parts[1]) ?? 0; + final detik = parts.length >= 3 ? (int.tryParse(parts[2]) ?? 0) : 0; + timestamp = DateTime(now.year, now.month, now.day, jam, menit, detik); + } + } return Evaporasi( evaporasi: (json['evaporasi'] ?? 0).toDouble(), - suhu: suhu, - tinggiAir: tinggi, - status: json['status'] ?? '', - timestamp: TimestampParser.parse(rawTime), // ✅ pakai TimestampParser + suhu: (json['suhu_air'] ?? 0).toDouble(), + tinggiAir: (json['tinggi_air'] ?? 0).toDouble(), + + /// 🔥 bikin timestamp dari jam & menit + timestamp: timestamp, ); } } diff --git a/packages/monitoring_repository/lib/src/models/has_timestamp.dart b/packages/monitoring_repository/lib/src/models/has_timestamp.dart deleted file mode 100644 index 46a4e63..0000000 --- a/packages/monitoring_repository/lib/src/models/has_timestamp.dart +++ /dev/null @@ -1,5 +0,0 @@ -/// Semua model sensor wajib implement ini. -/// Memungkinkan sorting & TimeSeriesMapper yang generik. -abstract class HasTimestamp { - DateTime get timestamp; -} diff --git a/packages/monitoring_repository/lib/src/models/models.dart b/packages/monitoring_repository/lib/src/models/models.dart index c51ddde..ae80da6 100644 --- a/packages/monitoring_repository/lib/src/models/models.dart +++ b/packages/monitoring_repository/lib/src/models/models.dart @@ -1,4 +1,3 @@ export 'wind_speed.dart'; export 'evaporasi.dart'; export 'atmospheric_conditions.dart'; -export 'has_timestamp.dart'; diff --git a/packages/monitoring_repository/lib/src/models/wind_speed.dart b/packages/monitoring_repository/lib/src/models/wind_speed.dart index 90ab814..b35d05a 100644 --- a/packages/monitoring_repository/lib/src/models/wind_speed.dart +++ b/packages/monitoring_repository/lib/src/models/wind_speed.dart @@ -1,23 +1,23 @@ -import 'has_timestamp.dart'; -import '../utils/timestamp_parser.dart'; +class MyWindSpeed { + double speed; -class MyWindSpeed implements HasTimestamp { - final double speed; - - @override - final DateTime timestamp; + DateTime timestamp; MyWindSpeed({required this.speed, required this.timestamp}); static final empty = MyWindSpeed( speed: 0.0, + timestamp: DateTime.fromMillisecondsSinceEpoch(0), ); factory MyWindSpeed.fromJson(Map json) { return MyWindSpeed( speed: (json['speed'] ?? 0).toDouble(), - timestamp: TimestampParser.parse(json['timestamp']), + + timestamp: DateTime.fromMillisecondsSinceEpoch( + (json['timestamp'] ?? 0) * 1000, // kalau dari Unix detik + ).toLocal(), ); } } diff --git a/packages/monitoring_repository/lib/src/monitoring_repo.dart b/packages/monitoring_repository/lib/src/monitoring_repo.dart index 9d0afb5..555796a 100644 --- a/packages/monitoring_repository/lib/src/monitoring_repo.dart +++ b/packages/monitoring_repository/lib/src/monitoring_repo.dart @@ -1,22 +1,21 @@ -/// file monitoring_repo.dart di "C:\App_project\klimatologi\packages\monitoring_repository\lib\src\monitoring_repo.dart" - -import 'models/has_timestamp.dart'; +// import 'models/models.dart'; abstract class MonitoringRepository { + // fungsi untuk ambil data berkali/streaming Stream getSensorStream( String path, T Function(Map json) mapper, ); + // fungsi untuk ambil sensor sekali Future getSensorSnapshot( String path, T Function(Map json) mapper, ); - Future> getSensorHistory( + // Fungsi untuk ambil riwayat (List) + Future> getSensorHistory( String path, - T Function(Map json) mapper, { - String? orderByChild, - int limit = 500, - }); + T Function(Map json) mapper, + ); } diff --git a/packages/monitoring_repository/lib/src/utils/timestamp_parser.dart b/packages/monitoring_repository/lib/src/utils/timestamp_parser.dart deleted file mode 100644 index dd8c978..0000000 --- a/packages/monitoring_repository/lib/src/utils/timestamp_parser.dart +++ /dev/null @@ -1,46 +0,0 @@ -/// Mengubah berbagai format waktu Firebase → DateTime. -/// -/// Format yang didukung: -/// int Unix detik 1777807041 (anemometer) -/// int Unix milidetik 1777950497446 (sensor/atmospheric) -/// String "HH:MM:SS" "11:09:00" (Monitoring/evaporasi, intraday only) -/// String ISO 8601 "2025-03-05T14:26" (untuk masa depan) -class TimestampParser { - // Angka > ini dianggap milidetik (threshold: ~Nov 2001 dalam detik) - static const int _msThreshold = 10000000000; - - static DateTime parse(dynamic raw) { - if (raw == null) return DateTime.now(); - - if (raw is int) { - if (raw <= 0) return DateTime.now(); - if (raw > _msThreshold) { - return DateTime.fromMillisecondsSinceEpoch(raw).toLocal(); - } - return DateTime.fromMillisecondsSinceEpoch(raw * 1000).toLocal(); - } - - if (raw is double) return parse(raw.toInt()); - - if (raw is String) { - // "HH:MM:SS" → pakai tanggal hari ini (intraday only) - final m = RegExp(r'^(\d{1,2}):(\d{2}):(\d{2})$').firstMatch(raw); - if (m != null) { - final now = DateTime.now(); - return DateTime( - now.year, - now.month, - now.day, - int.parse(m.group(1)!), - int.parse(m.group(2)!), - int.parse(m.group(3)!), - ); - } - try { - return DateTime.parse(raw).toLocal(); - } catch (_) {} - } - - return DateTime.now(); - } -}