diff --git a/analyze_log.txt b/analyze_log.txt new file mode 100644 index 0000000..fe29a4a Binary files /dev/null and b/analyze_log.txt differ diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html new file mode 100644 index 0000000..9ff7a95 --- /dev/null +++ b/android/build/reports/problems/problems-report.html @@ -0,0 +1,663 @@ + + + + + + + + + + + + + Gradle Configuration Cache + + + +
+ +
+ Loading... +
+ + + + + + diff --git a/build_log.txt b/build_log.txt new file mode 100644 index 0000000..1b849d5 Binary files /dev/null and b/build_log.txt differ diff --git a/build_log2.txt b/build_log2.txt new file mode 100644 index 0000000..8da8340 Binary files /dev/null and b/build_log2.txt differ diff --git a/lib/core/utils/time_series_mapper.dart b/lib/core/utils/time_series_mapper.dart index 0fa54cb..1300b84 100644 --- a/lib/core/utils/time_series_mapper.dart +++ b/lib/core/utils/time_series_mapper.dart @@ -1,3 +1,5 @@ +import 'package:monitoring_repository/monitoring_repository.dart'; + class TimeSeriesMapper { /// ========================= /// 📅 DAILY (24 JAM) @@ -142,4 +144,30 @@ 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 9a995a7..05f0616 100644 --- a/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart +++ b/lib/screens/monitoring/wind_speed/blocs/wind_speed_bloc.dart @@ -48,32 +48,16 @@ class WindSpeedBloc extends Bloc { final history = await _repository.getSensorHistory( 'anemometer/history', (json) => MyWindSpeed.fromJson(json), + orderByChild: 'timestamp', + limit: 500, ); - // ✅ 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, - ), - ); + final dailyGraph = + TimeSeriesMapper.dailyFrom(history, (e) => e.speed); + final weekly = + TimeSeriesMapper.weeklyFrom(history, (e) => e.speed); + final monthly = + TimeSeriesMapper.monthlyFrom(history, (e) => e.speed); // Cek kondisi awal dari history if (history.isNotEmpty) { @@ -141,48 +125,11 @@ class WindSpeedBloc extends Bloc { /// ========================= /// 📊 CHANGE PERIOD /// ========================= - Future _onPeriodChanged( + void _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 e5ff71e..b69e0b2 100644 --- a/packages/monitoring_repository/lib/monitoring_repository.dart +++ b/packages/monitoring_repository/lib/monitoring_repository.dart @@ -3,3 +3,4 @@ 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 f7cf78c..a8f54eb 100644 --- a/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart +++ b/packages/monitoring_repository/lib/src/firebase_monitoring_repo.dart @@ -1,72 +1,61 @@ -// 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'; +import 'monitoring_repo.dart'; +import 'models/has_timestamp.dart'; +import 'models/models.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 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({}); - } + final value = event.snapshot.value; + return mapper(value is Map ? value : {}); }); } @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(); - final data = snapshot.value as Map? ?? {}; - return mapper(data); + return mapper(snapshot.value as Map? ?? {}); } @override - Future> getSensorHistory( + Future> getSensorHistory( String path, - T Function(Map json) mapper, - ) async { + T Function(Map json) mapper, { + String? orderByChild, + int limit = 500, + }) async { try { - // Ambil data dari path history - final snapshot = await _db.ref(path).get(); + Query query = _db.ref(path); - 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; + if (orderByChild != null) { + query = query.orderByChild(orderByChild).limitToLast(limit); + } else { + query = query.limitToLast(limit); } - return []; - } catch (e) { + + 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 []; } } diff --git a/packages/monitoring_repository/lib/src/models/evaporasi.dart b/packages/monitoring_repository/lib/src/models/evaporasi.dart index 907cbc5..caec82e 100644 --- a/packages/monitoring_repository/lib/src/models/evaporasi.dart +++ b/packages/monitoring_repository/lib/src/models/evaporasi.dart @@ -1,7 +1,13 @@ -class Evaporasi { +import 'has_timestamp.dart'; +import '../utils/timestamp_parser.dart'; + +class Evaporasi implements HasTimestamp { final double evaporasi; final double suhu; final double tinggiAir; + final String status; + + @override final DateTime timestamp; Evaporasi({ @@ -9,6 +15,7 @@ class Evaporasi { required this.suhu, required this.tinggiAir, required this.timestamp, + this.status = '', }); static final empty = Evaporasi( @@ -19,27 +26,22 @@ class Evaporasi { ); factory Evaporasi.fromJson(Map json) { - final now = DateTime.now(); - DateTime timestamp = now; + // ✅ 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 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); - } - } + // ✅ Prioritas: timestamp (Unix, kalau firmware sudah diupdate) + // → fallback ke waktu ("HH:MM:SS") + final rawTime = json['timestamp'] ?? json['waktu']; return Evaporasi( evaporasi: (json['evaporasi'] ?? 0).toDouble(), - suhu: (json['suhu_air'] ?? 0).toDouble(), - tinggiAir: (json['tinggi_air'] ?? 0).toDouble(), - - /// 🔥 bikin timestamp dari jam & menit - timestamp: timestamp, + suhu: suhu, + tinggiAir: tinggi, + status: json['status'] ?? '', + timestamp: TimestampParser.parse(rawTime), // ✅ pakai TimestampParser ); } } diff --git a/packages/monitoring_repository/lib/src/models/has_timestamp.dart b/packages/monitoring_repository/lib/src/models/has_timestamp.dart new file mode 100644 index 0000000..46a4e63 --- /dev/null +++ b/packages/monitoring_repository/lib/src/models/has_timestamp.dart @@ -0,0 +1,5 @@ +/// 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 ae80da6..c51ddde 100644 --- a/packages/monitoring_repository/lib/src/models/models.dart +++ b/packages/monitoring_repository/lib/src/models/models.dart @@ -1,3 +1,4 @@ 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 b35d05a..90ab814 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 @@ -class MyWindSpeed { - double speed; +import 'has_timestamp.dart'; +import '../utils/timestamp_parser.dart'; - DateTime timestamp; +class MyWindSpeed implements HasTimestamp { + final double speed; + + @override + final 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: DateTime.fromMillisecondsSinceEpoch( - (json['timestamp'] ?? 0) * 1000, // kalau dari Unix detik - ).toLocal(), + timestamp: TimestampParser.parse(json['timestamp']), ); } } diff --git a/packages/monitoring_repository/lib/src/monitoring_repo.dart b/packages/monitoring_repository/lib/src/monitoring_repo.dart index 555796a..9d0afb5 100644 --- a/packages/monitoring_repository/lib/src/monitoring_repo.dart +++ b/packages/monitoring_repository/lib/src/monitoring_repo.dart @@ -1,21 +1,22 @@ -// import 'models/models.dart'; +/// file monitoring_repo.dart di "C:\App_project\klimatologi\packages\monitoring_repository\lib\src\monitoring_repo.dart" + +import 'models/has_timestamp.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, ); - // Fungsi untuk ambil riwayat (List) - Future> getSensorHistory( + Future> getSensorHistory( String path, - T Function(Map json) mapper, - ); + T Function(Map json) mapper, { + String? orderByChild, + int limit = 500, + }); } diff --git a/packages/monitoring_repository/lib/src/utils/timestamp_parser.dart b/packages/monitoring_repository/lib/src/utils/timestamp_parser.dart new file mode 100644 index 0000000..dd8c978 --- /dev/null +++ b/packages/monitoring_repository/lib/src/utils/timestamp_parser.dart @@ -0,0 +1,46 @@ +/// 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(); + } +}