Files
2026-05-12 12:02:40 +03:00

284 lines
8.1 KiB
Dart

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<ScooterEvent, ScooterState> {
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<ClientNotification>? _notificationSubscription;
MapBloc(
this.getAvailableZonesUsecase,
this.getScootersUsecase,
this.getMapSettingsUsecase,
this.getNotificationsStreamUseCase,
this.getProfileUseCase,
this.checkUserUseCase,
this.logoutUseCase,
this.splashBloc,
) : super(ScooterState(isGeomarksShowed: true)) {
on<FetchScooters>(_onFetchScooters);
on<UpdateMap>(_onUpdateMap);
on<UpdateUserLocation>(_onUpdateUserLocation);
on<NotificationReceived>(_onNotificationReceived);
on<FetchProfileData>(_onFetchProfileData);
on<CheckUser>(_onCheckUser);
on<LogoutPressed>(_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<void> _onFetchScooters(
FetchScooters event,
Emitter<ScooterState> 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<Scooter>;
final zones = results[1] as List<Zone>;
final settings = results[2] as MapSettings;
zones.forEach(print);
List<Zone> 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<void> _onUpdateMap(UpdateMap event, Emitter<ScooterState> 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<Scooter>;
final zones = results[1] as List<Zone>;
final settings = results[2] as MapSettings;
List<Zone> 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<void> _onUpdateUserLocation(
UpdateUserLocation event,
Emitter<ScooterState> 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<void> _onNotificationReceived(
NotificationReceived event,
Emitter<ScooterState> emit,
) {
print("NOTIFICATION RECEIVED: ${event.notification.content}");
emit(state.copyWith(lastNotification: event.notification));
}
bool checkUserInZone(Point userPos, List<Point> zonePoints) {
List<mt.LatLng> 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<void> _onFetchProfileData(
FetchProfileData event,
Emitter<ScooterState> 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<void> _onLogoutPressed(
LogoutPressed event,
Emitter<ScooterState> emit,
) {
logoutUseCase();
splashBloc.add(AuthCheckRequested());
}
FutureOr<void> _onCheckUser(
CheckUser event,
Emitter<ScooterState> 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()}",
),
);
}
}
}