import 'dart:async'; import 'package:be_happy/core/result.dart'; import 'package:be_happy/domain/entities/client_notification.dart'; import 'package:be_happy/domain/entities/map_settings.dart'; import 'package:be_happy/domain/usecase/check_user_usecase.dart'; import 'package:be_happy/domain/usecase/get_available_zones_usecase.dart'; import 'package:be_happy/domain/usecase/get_map_settings_usecase.dart'; import 'package:be_happy/domain/usecase/get_notifications_stream_usecase.dart'; import 'package:be_happy/domain/usecase/logout_usecase.dart'; import 'package:be_happy/presentation/viewmodel/splash_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../domain/entities/point.dart'; import '../../domain/entities/scooter.dart'; import '../../domain/entities/zone.dart'; import '../../domain/usecase/get_available_scooters_usecase.dart'; import '../../domain/usecase/get_profile_usecase.dart'; import '../event/map_event.dart'; import '../event/spalsh_event.dart'; import '../state/map_state.dart'; import 'package:maps_toolkit/maps_toolkit.dart' as mt; class MapBloc extends Bloc { final GetAvailableScootersUsecase getScootersUsecase; final GetAvailableZonesUsecase getAvailableZonesUsecase; final GetMapSettingsUsecase getMapSettingsUsecase; final GetNotificationsStreamUseCase getNotificationsStreamUseCase; final GetProfileUseCase getProfileUseCase; final CheckUserUseCase checkUserUseCase; final LogoutUseCase logoutUseCase; final SplashBloc splashBloc; StreamSubscription? _notificationSubscription; MapBloc( this.getAvailableZonesUsecase, this.getScootersUsecase, this.getMapSettingsUsecase, this.getNotificationsStreamUseCase, this.getProfileUseCase, this.checkUserUseCase, this.logoutUseCase, this.splashBloc, ) : super(ScooterState(isGeomarksShowed: true)) { on(_onFetchScooters); on(_onUpdateMap); on(_onUpdateUserLocation); on(_onNotificationReceived); on(_onFetchProfileData); on(_onCheckUser); on(_onLogoutPressed); } void startNotificationStream() { _notificationSubscription?.cancel(); _notificationSubscription = getNotificationsStreamUseCase().listen( (notification) { add(NotificationReceived(notification)); }, onError: (error) { print(" SSE BLOC ERROR: $error"); _handleReconnect(); }, onDone: () { print(" SSE Stream closed by server (onDone)"); _handleReconnect(); }, cancelOnError: true, ); } void _handleReconnect() { if (isClosed) return; Future.delayed(const Duration(seconds: 5), () { if (!isClosed) { print(" Attempting to reconnect to SSE..."); startNotificationStream(); } }); } void stopNotificationStream() { _notificationSubscription?.cancel(); _notificationSubscription = null; } Future _onFetchScooters( FetchScooters event, Emitter emit, ) async { emit(state.copyWith(status: ScooterStatus.loading)); try { final results = await Future.wait([ getScootersUsecase(event.areaScooters, 0, 100), getAvailableZonesUsecase(event.area, 0, 100), getMapSettingsUsecase(), ]); final scooters = results[0] as List; final zones = results[1] as List; final settings = results[2] as MapSettings; zones.forEach(print); List filteredZones = []; if (settings.all_zones) { if (settings.parking_zones) { filteredZones.addAll(zones.where((el) => el.type == "Finish")); } if (settings.restricted_parking_zones) { filteredZones.addAll(zones.where((el) => el.type == "Drive")); } if (settings.restricted_driving_zones) { filteredZones.addAll(zones.where((el) => el.type == "NotDrive")); } } print( " FETCH: filteredZones.length = ${filteredZones.length}, all_zones=${settings.all_zones}", ); emit( state.copyWith( status: ScooterStatus.success, scooters: scooters, zones: filteredZones, area: event.area, areaScooters: event.areaScooters, isGeomarksShowed: settings.all_placemarks, ), ); } catch (e) { print("FETCH ERROR: $e"); emit( state.copyWith( status: ScooterStatus.failure, errorMessage: e.toString(), ), ); } } Future _onUpdateMap(UpdateMap event, Emitter emit) async { emit(state.copyWith(status: ScooterStatus.loading)); try { final results = await Future.wait([ getScootersUsecase(state.areaScooters, 0, 100), getAvailableZonesUsecase(state.area, 0, 100), getMapSettingsUsecase(), ]); final scooters = results[0] as List; final zones = results[1] as List; final settings = results[2] as MapSettings; List filteredZones = []; if (settings.all_zones) { if (settings.parking_zones) { filteredZones.addAll(zones.where((el) => el.type == "Finish")); } if (settings.restricted_parking_zones) { filteredZones.addAll(zones.where((el) => el.type == "Drive")); } if (settings.restricted_driving_zones) { filteredZones.addAll(zones.where((el) => el.type == "NotDrive")); } } print( " UPDATE MAP: filteredZones.length = ${filteredZones.length}, all_zones=${settings.all_zones}", ); final zonesToEmit = filteredZones.isNotEmpty ? filteredZones : state.zones; emit( state.copyWith( status: ScooterStatus.success, scooters: scooters, zones: zonesToEmit, isGeomarksShowed: settings.all_placemarks, ), ); } catch (e) { print("UPDATE ERROR: $e"); emit( state.copyWith( status: ScooterStatus.failure, errorMessage: e.toString(), ), ); } } FutureOr _onUpdateUserLocation( UpdateUserLocation event, Emitter emit, ) { print("USER LOCATION UPDATED EVENT start"); state.zones.forEach((z) { if (checkUserInZone(Point(event.latitude, event.longitude), z.points)) { print("USER IN ZONE - ${z.type}"); } }); } FutureOr _onNotificationReceived( NotificationReceived event, Emitter emit, ) { print("NOTIFICATION RECEIVED: ${event.notification.content}"); emit(state.copyWith(lastNotification: event.notification)); } bool checkUserInZone(Point userPos, List zonePoints) { List polygon = zonePoints .map((p) => mt.LatLng(p.latitude, p.longitude)) .toList(); mt.LatLng userLatLng = mt.LatLng(userPos.latitude, userPos.longitude); return mt.PolygonUtil.containsLocation(userLatLng, polygon, true); } FutureOr _onFetchProfileData( FetchProfileData event, Emitter emit, ) async { try { final profile = await getProfileUseCase(); emit(state.copyWith(phoneNumber: profile.phone, balance: profile.balance)); } catch (e) { emit( state.copyWith( status: ScooterStatus.failure, errorMessage: "FetchProfileData for SideMenu: ${e.toString()}", ), ); } } FutureOr _onLogoutPressed( LogoutPressed event, Emitter emit, ) { logoutUseCase(); splashBloc.add(AuthCheckRequested()); } FutureOr _onCheckUser( CheckUser event, Emitter emit, ) async { try { final flags = await checkUserUseCase(); print("flags: $flags"); if (flags == null) { return; } print("check user success"); emit(state.copyWith(flags: flags)); } catch (e) { emit( state.copyWith( status: ScooterStatus.failure, errorMessage: "CheckUser: ${e.toString()}", ), ); } } }