This commit is contained in:
kleponijo 2026-06-29 13:42:06 +07:00
parent a6e74d4da0
commit 898c784359
4 changed files with 69 additions and 23 deletions

View File

@ -19,6 +19,7 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
final MonitoringRepository _repository; final MonitoringRepository _repository;
final NotificationBloc _notificationBloc; final NotificationBloc _notificationBloc;
StreamSubscription<MyWindSpeed>? _subscription; StreamSubscription<MyWindSpeed>? _subscription;
StreamSubscription<Map<String, MyWindSpeed>>? _historySubscription;
WindSpeedBloc({ WindSpeedBloc({
required MonitoringRepository repository, required MonitoringRepository repository,
@ -35,6 +36,7 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
on<WindSpeedDeleteByDateRequested>(_onDeleteByDate); on<WindSpeedDeleteByDateRequested>(_onDeleteByDate);
on<WindSpeedDeleteByDateRangeRequested>(_onDeleteByDateRange); on<WindSpeedDeleteByDateRangeRequested>(_onDeleteByDateRange);
on<WindSpeedDeleteByHourRangeRequested>(_onDeleteByHourRange); on<WindSpeedDeleteByHourRangeRequested>(_onDeleteByHourRange);
on<_WindSpeedHistoryUpdated>(_onHistoryUpdated);
} }
Future<String> _getDeviceId() async { Future<String> _getDeviceId() async {
@ -49,42 +51,55 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
WatchWindSpeedStarted event, WatchWindSpeedStarted event,
Emitter<WindSpeedState> emit, Emitter<WindSpeedState> emit,
) async { ) async {
// Cancel stream lama dulu sebelum apapun
await _subscription?.cancel(); await _subscription?.cancel();
await _historySubscription?.cancel();
_subscription = null; _subscription = null;
_historySubscription = null;
emit(state.copyWith(isLoading: true, currentSpeed: 0)); emit(state.copyWith(isLoading: true, currentSpeed: 0));
final deviceId = await _getDeviceId(); final deviceId = await _getDeviceId();
final historyMap = await _repository.getSensorHistoryWithKeys( // History stream (realtime, otomatis update)
'anemometer/$deviceId/history', _historySubscription = _repository
(json) => MyWindSpeed.fromJson(json), .getSensorHistoryStream(
); 'anemometer/$deviceId/history',
(json) => MyWindSpeed.fromJson(json),
)
.listen((newMap) => add(_WindSpeedHistoryUpdated(newMap)));
final history = _sortedList(historyMap); // Realtime stream
final graphs = _buildGraphs(history);
if (history.isNotEmpty) _emitWindAlert(history.last.speed);
emit(state.copyWith(
historyMap: historyMap,
history: history,
filteredHistory: history,
dailySpeeds: graphs['daily']!,
weeklySpeeds: graphs['weekly']!,
monthlySpeeds: graphs['monthly']!,
isLoading: false,
alertLevel:
history.isNotEmpty ? _getAlertLevel(history.last.speed) : 'Normal',
));
// Subscribe stream baru
_subscription = _repository _subscription = _repository
.getSensorStream( .getSensorStream(
'anemometer/$deviceId/realtime', 'anemometer/$deviceId/realtime',
(json) => MyWindSpeed.fromJson(json), (json) => MyWindSpeed.fromJson(json),
) )
.listen((data) => add(_WindSpeedRealtimeUpdated(data))); .listen((data) => add(_WindSpeedRealtimeUpdated(data)));
// Loading selesai, state awal kosong dulu
// _onHistoryUpdated akan otomatis mengisi data begitu stream pertama emit
emit(state.copyWith(isLoading: false));
}
void _onHistoryUpdated(
_WindSpeedHistoryUpdated event,
Emitter<WindSpeedState> emit,
) {
final history = _sortedList(event.historyMap);
final graphs = _buildGraphs(history);
final filtered = state.selectedDate != null
? history
.where((e) => _isSameDate(e.timestamp, state.selectedDate!))
.toList()
: history;
emit(state.copyWith(
historyMap: event.historyMap,
history: history,
filteredHistory: filtered,
dailySpeeds: graphs['daily'],
weeklySpeeds: graphs['weekly'],
monthlySpeeds: graphs['monthly'],
));
} }
// //
@ -429,6 +444,7 @@ class WindSpeedBloc extends Bloc<WindSpeedEvent, WindSpeedState> {
@override @override
Future<void> close() async { Future<void> close() async {
await _subscription?.cancel(); await _subscription?.cancel();
await _historySubscription?.cancel();
return super.close(); return super.close();
} }
} }

View File

@ -76,3 +76,10 @@ class WindSpeedDeleteByHourRangeRequested extends WindSpeedEvent {
@override @override
List<Object?> get props => [date, startHour, endHour]; List<Object?> get props => [date, startHour, endHour];
} }
class _WindSpeedHistoryUpdated extends WindSpeedEvent {
final Map<String, MyWindSpeed> historyMap;
const _WindSpeedHistoryUpdated(this.historyMap);
@override
List<Object?> get props => [historyMap];
}

View File

@ -200,4 +200,21 @@ class FirebaseMonitoringRepo implements MonitoringRepository {
Future<void> sendRemoteRestart(String deviceId) async { Future<void> sendRemoteRestart(String deviceId) async {
await _db.ref('anemometer/$deviceId/command/restart').set(true); await _db.ref('anemometer/$deviceId/command/restart').set(true);
} }
@override
Stream<Map<String, T>> getSensorHistoryStream<T>(
String path,
T Function(Map<dynamic, dynamic> json) mapper,
) {
return _db.ref(path).onValue.map((event) {
if (!event.snapshot.exists || event.snapshot.value is! Map) return {};
final data = event.snapshot.value as Map<dynamic, dynamic>;
return {
for (final entry in data.entries)
entry.key.toString(): mapper(
entry.value is Map ? entry.value as Map<dynamic, dynamic> : {},
),
};
});
}
} }

View File

@ -41,4 +41,10 @@ abstract class MonitoringRepository {
); );
Future<void> deleteHistoryByKeys(String path, List<String> keys); Future<void> deleteHistoryByKeys(String path, List<String> keys);
Future<void> deleteAllHistory(String path); Future<void> deleteAllHistory(String path);
// Tambah di abstract class MonitoringRepository:
Stream<Map<String, T>> getSensorHistoryStream<T>(
String path,
T Function(Map<dynamic, dynamic> json) mapper,
);
} }