new project stable version
This commit is contained in:
209
lib/presentation/viewmodel/active_ride_bloc.dart
Normal file
209
lib/presentation/viewmodel/active_ride_bloc.dart
Normal file
@@ -0,0 +1,209 @@
|
||||
import 'dart:async';
|
||||
import 'package:be_happy/domain/entities/active_scooter_order.dart';
|
||||
import 'package:be_happy/domain/usecase/update_scooter_order_data_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/result.dart';
|
||||
import '../../../domain/entities/scooter_order.dart';
|
||||
import '../../../domain/usecase/finish_ride_usecase.dart';
|
||||
import '../../../domain/usecase/pause_ride_usecase.dart';
|
||||
import '../../../domain/usecase/resume_ride_usecase.dart';
|
||||
import '../../../domain/usecase/get_scooter_order_by_id_usecase.dart';
|
||||
import '../event/active_ride_event.dart';
|
||||
import '../state/active_ride_state.dart';
|
||||
|
||||
class ActiveRideBloc extends Bloc<ActiveRideEvent, ActiveRideState> {
|
||||
final FinishRideUsecase _finishRideUsecase;
|
||||
final PauseRideUsecase _pauseRideUsecase;
|
||||
final ResumeRideUsecase _resumeRideUsecase;
|
||||
final GetScooterOrderByIdUsecase _getScooterOrderByIdUsecase;
|
||||
final UpdateScooterOrderDataUsecase _updateScooterOrderDataUsecase;
|
||||
Timer? _syncTimer;
|
||||
|
||||
ActiveRideBloc(
|
||||
this._finishRideUsecase,
|
||||
this._pauseRideUsecase,
|
||||
this._resumeRideUsecase,
|
||||
this._getScooterOrderByIdUsecase,
|
||||
this._updateScooterOrderDataUsecase,
|
||||
) : super(const ActiveRideState()) {
|
||||
on<LoadScooterOrder>(_onLoadScooterOrder);
|
||||
on<PauseRide>(_onPauseRide);
|
||||
on<ResumeRide>(_onResumeRide);
|
||||
on<FinishRide>(_onFinishRide);
|
||||
on<SyncScooterOrder>(_onSyncScooterOrder);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_syncTimer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _onLoadScooterOrder(
|
||||
LoadScooterOrder event,
|
||||
Emitter<ActiveRideState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ActiveRideStatus.loading));
|
||||
|
||||
final results = await Future.wait([
|
||||
_getScooterOrderByIdUsecase(event.orderId),
|
||||
_updateScooterOrderDataUsecase(orderId: event.orderId),
|
||||
]);
|
||||
|
||||
final orderResult = results[0];
|
||||
final activeOrderData = results[1];
|
||||
|
||||
if (orderResult is Success<ScooterOrder>) {
|
||||
final order = orderResult.data;
|
||||
|
||||
|
||||
// Пытаемся достать доп. данные, если они Success
|
||||
final orderData = activeOrderData is Success<ActiveScooterOrder>
|
||||
? activeOrderData.data
|
||||
: null;
|
||||
|
||||
final startTime = order?.startAt ?? order?.createdAt;
|
||||
final elapsedTime = DateTime.now().difference(startTime!);
|
||||
final isPaused = order?.status.toLowerCase() == 'pause';
|
||||
|
||||
print("ORDER DATA2: $orderData");
|
||||
|
||||
emit(state.copyWith(
|
||||
status: ActiveRideStatus.success,
|
||||
order: order,
|
||||
elapsedTime: elapsedTime,
|
||||
speed: orderData?.speed ?? 0.0,
|
||||
distance: orderData?.mileage ?? 0.0,
|
||||
cost: orderData?.price ?? 0.0,
|
||||
isPaused: isPaused,
|
||||
inZone: orderData?.zone,
|
||||
));
|
||||
|
||||
//synchronize
|
||||
_syncTimer?.cancel();
|
||||
_syncTimer = Timer.periodic(const Duration(seconds: 5), (timer) {
|
||||
add(SyncScooterOrder(event.orderId));
|
||||
});
|
||||
} else if (orderResult is Failure) {
|
||||
emit(state.copyWith(
|
||||
status: ActiveRideStatus.failure,
|
||||
errorMessage: 'Не удалось загрузить информацию о поездке',
|
||||
));
|
||||
}
|
||||
print("CURRENT STATE $state");
|
||||
|
||||
}
|
||||
|
||||
Future<void> _onPauseRide(
|
||||
PauseRide event,
|
||||
Emitter<ActiveRideState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ActiveRideStatus.loading));
|
||||
|
||||
final result = await _pauseRideUsecase(event.orderId);
|
||||
|
||||
if (result is Success<ScooterOrder>) {
|
||||
emit(state.copyWith(
|
||||
status: ActiveRideStatus.success,
|
||||
order: result.data,
|
||||
isPaused: true,
|
||||
));
|
||||
} else if (result is Failure) {
|
||||
emit(state.copyWith(
|
||||
status: ActiveRideStatus.failure,
|
||||
errorMessage: 'Не удалось поставить поездку на паузу',
|
||||
));
|
||||
}
|
||||
print("CURRENT STATE $state");
|
||||
}
|
||||
|
||||
Future<void> _onResumeRide(
|
||||
ResumeRide event,
|
||||
Emitter<ActiveRideState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ActiveRideStatus.loading));
|
||||
|
||||
final result = await _resumeRideUsecase(event.orderId);
|
||||
|
||||
if (result is Success<ScooterOrder>) {
|
||||
emit(state.copyWith(
|
||||
status: ActiveRideStatus.success,
|
||||
order: result.data,
|
||||
isPaused: false,
|
||||
));
|
||||
} else if (result is Failure) {
|
||||
emit(state.copyWith(
|
||||
status: ActiveRideStatus.failure,
|
||||
errorMessage: 'Не удалось возобновить поездку',
|
||||
));
|
||||
}
|
||||
print("CURRENT STATE $state");
|
||||
}
|
||||
|
||||
Future<void> _onFinishRide(
|
||||
FinishRide event,
|
||||
Emitter<ActiveRideState> emit,
|
||||
) async {
|
||||
// emit(state.copyWith(status: ActiveRideStatus.loading));
|
||||
|
||||
/*final result = await _finishRideUsecase(event.orderId);
|
||||
|
||||
|
||||
|
||||
if (result is Success<ScooterOrder>) {
|
||||
_syncTimer?.cancel();
|
||||
_syncTimer = null;
|
||||
|
||||
emit(state.copyWith(
|
||||
status: ActiveRideStatus.success,
|
||||
order: result.data,
|
||||
));
|
||||
} else if (result is Failure) {
|
||||
emit(state.copyWith(
|
||||
status: ActiveRideStatus.failure,
|
||||
errorMessage: 'Не удалось завершить поездку',
|
||||
));
|
||||
}*/
|
||||
}
|
||||
|
||||
Future<void> _onSyncScooterOrder(
|
||||
SyncScooterOrder event,
|
||||
Emitter<ActiveRideState> emit,
|
||||
) async {
|
||||
final results = await Future.wait([
|
||||
_getScooterOrderByIdUsecase(event.orderId),
|
||||
_updateScooterOrderDataUsecase(orderId: event.orderId),
|
||||
]);
|
||||
|
||||
final orderResult = results[0];
|
||||
final activeOrderData = results[1];
|
||||
|
||||
if (orderResult is Success<ScooterOrder>) {
|
||||
final order = orderResult.data;
|
||||
|
||||
// Пытаемся достать доп. данные, если они Success
|
||||
final orderData = activeOrderData is Success<ActiveScooterOrder>
|
||||
? activeOrderData.data
|
||||
: null;
|
||||
|
||||
final startTime = order?.startAt ?? order?.createdAt;
|
||||
final elapsedTime = DateTime.now().difference(startTime!);
|
||||
final isPaused = order?.status.toLowerCase() == 'pause';
|
||||
|
||||
emit(state.copyWith(
|
||||
order: order,
|
||||
elapsedTime: elapsedTime,
|
||||
speed: orderData?.speed ?? 0.0,
|
||||
distance: orderData?.mileage ?? 0.0,
|
||||
cost: orderData?.price ?? state.cost,
|
||||
isPaused: isPaused,
|
||||
inZone: orderData?.zone,
|
||||
));
|
||||
}
|
||||
print("CURRENT STATE $state");
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
112
lib/presentation/viewmodel/add_card_bloc.dart
Normal file
112
lib/presentation/viewmodel/add_card_bloc.dart
Normal file
@@ -0,0 +1,112 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../domain/usecase/add_payment_card_usecase.dart';
|
||||
import '../event/add_card_event.dart';
|
||||
import '../state/add_card_state.dart';
|
||||
|
||||
// 🔹 Импорты для Result и Failure
|
||||
import '../../core/result.dart';
|
||||
import '../../core/failures.dart';
|
||||
|
||||
class AddCardBloc extends Bloc<AddCardEvent, AddCardState> {
|
||||
final AddPaymentCardUsecase addPaymentCardUsecase;
|
||||
|
||||
AddCardBloc(this.addPaymentCardUsecase) : super(const AddCardState()) {
|
||||
on<CardNumberChanged>(_onCardNumberChanged);
|
||||
on<ExpiryDateChanged>(_onExpiryDateChanged);
|
||||
on<CvvChanged>(_onCvvChanged);
|
||||
on<CardHolderChanged>(_onCardHolderChanged);
|
||||
on<AddCardSubmitted>(_onAddCardSubmitted);
|
||||
}
|
||||
|
||||
void _onCardNumberChanged(CardNumberChanged event, Emitter<AddCardState> emit) {
|
||||
final formatted = _formatCardNumber(event.cardNumber);
|
||||
emit(state.copyWith(cardNumber: formatted));
|
||||
}
|
||||
|
||||
void _onExpiryDateChanged(ExpiryDateChanged event, Emitter<AddCardState> emit) {
|
||||
final formatted = _formatExpiryDate(event.expiryDate);
|
||||
emit(state.copyWith(expiryDate: formatted));
|
||||
}
|
||||
|
||||
void _onCvvChanged(CvvChanged event, Emitter<AddCardState> emit) {
|
||||
final formatted = event.cvv.replaceAll(RegExp(r'\D'), '').substring(0, 3);
|
||||
emit(state.copyWith(cvv: formatted));
|
||||
}
|
||||
|
||||
void _onCardHolderChanged(CardHolderChanged event, Emitter<AddCardState> emit) {
|
||||
emit(state.copyWith(cardHolder: event.cardHolder));
|
||||
}
|
||||
|
||||
Future<void> _onAddCardSubmitted(AddCardSubmitted event, Emitter<AddCardState> emit) async {
|
||||
if (state.cardNumber.isEmpty || state.expiryDate.isEmpty) return;
|
||||
|
||||
emit(state.copyWith(status: AddCardStatus.loading));
|
||||
|
||||
final expiryParts = state.expiryDate.split('/'); // Используем state
|
||||
if (expiryParts.length != 2) {
|
||||
emit(state.copyWith(
|
||||
status: AddCardStatus.failure,
|
||||
errorMessage: 'Неверный формат даты',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await addPaymentCardUsecase(
|
||||
cardNumber: state.cardNumber.replaceAll(' ', ''),
|
||||
expiryMonth: expiryParts[0],
|
||||
expiryYear: expiryParts[1],
|
||||
cardHolder: state.cardHolder.trim(),
|
||||
cvv: state.cvv,
|
||||
);
|
||||
|
||||
if (result is Success) {
|
||||
emit(state.copyWith(status: AddCardStatus.success));
|
||||
} else if (result is Failure) {
|
||||
final failure = result.failure;
|
||||
|
||||
String errorMessage = 'Ошибка добавления карты';
|
||||
|
||||
if (failure is AuthFailure) {
|
||||
errorMessage = 'Неверные данные карты';
|
||||
} else if (failure is AuthBlockFailure) {
|
||||
errorMessage = 'Доступ заблокирован';
|
||||
} else if (failure is UnknownFailure) {
|
||||
errorMessage = failure.message ?? 'Неизвестная ошибка';
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
status: AddCardStatus.failure,
|
||||
errorMessage: errorMessage,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
String _formatCardNumber(String value) {
|
||||
final cleaned = value.replaceAll(RegExp(r'\D'), '');
|
||||
final limited = cleaned.substring(0, cleaned.length > 16 ? 16 : cleaned.length);
|
||||
|
||||
String formatted = '';
|
||||
for (int i = 0; i < limited.length; i++) {
|
||||
if (i > 0 && i % 4 == 0) {
|
||||
formatted += ' ';
|
||||
}
|
||||
formatted += limited[i];
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
String _formatExpiryDate(String value) {
|
||||
final cleaned = value.replaceAll(RegExp(r'\D'), '');
|
||||
if (cleaned.isEmpty) return '';
|
||||
|
||||
if (cleaned.length <= 2) {
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
final month = cleaned.substring(0, 2);
|
||||
final year = cleaned.substring(2, cleaned.length > 4 ? 4 : cleaned.length);
|
||||
|
||||
return '$month/$year';
|
||||
}
|
||||
}
|
||||
51
lib/presentation/viewmodel/auth_bloc.dart
Normal file
51
lib/presentation/viewmodel/auth_bloc.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../domain/usecase/login_usecase.dart';
|
||||
import '../event/auth_event.dart';
|
||||
import '../state/auth_state.dart';
|
||||
|
||||
class PhoneAuthBloc extends Bloc<PhoneAuthEvent, PhoneAuthState> {
|
||||
final LoginUseCase _loginUseCase;
|
||||
|
||||
PhoneAuthBloc(this._loginUseCase) : super(PhoneAuthState.initial()) {
|
||||
on<PhoneChanged>((event, emit) {
|
||||
emit(state.copyWith(phone: event.phone));
|
||||
});
|
||||
|
||||
on<IsAdultChanged>((event, emit) {
|
||||
emit(state.copyWith(isAdult: event.isAdult));
|
||||
});
|
||||
|
||||
on<PrivacyAcceptedChanged>((event, emit) {
|
||||
emit(state.copyWith(privacyAccepted: event.accepted));
|
||||
});
|
||||
|
||||
on<SubmitPhonePressed>(_onSubmitPhonePressed);
|
||||
}
|
||||
|
||||
Future<void> _onSubmitPhonePressed(
|
||||
SubmitPhonePressed event,
|
||||
Emitter<PhoneAuthState> emit,
|
||||
) async {
|
||||
if (state.isSubmitting) return;
|
||||
|
||||
final phone = state.phone;
|
||||
final isAdult = state.isAdult;
|
||||
final privacyAccepted = state.privacyAccepted;
|
||||
|
||||
if (phone.isEmpty || !isAdult || !privacyAccepted) {
|
||||
emit(state.copyWith(error: "Пожалуйста, заполните все поля."));
|
||||
return;
|
||||
}
|
||||
|
||||
emit(state.copyWith(isSubmitting: true, error: null));
|
||||
|
||||
//готовы отправить данные, вызываем юзкейс
|
||||
try {
|
||||
final tempToken = await _loginUseCase.execute('+375$phone');
|
||||
print("TOKEN ON PRESENTATION LAYER: $tempToken");
|
||||
emit(state.copyWith(isSubmitting: false, isSuccess: true));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(isSubmitting: false, error: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
40
lib/presentation/viewmodel/current_rides_bloc.dart
Normal file
40
lib/presentation/viewmodel/current_rides_bloc.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:be_happy/domain/usecase/get_profile_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../core/result.dart';
|
||||
import '../../domain/entities/scooter_order.dart';
|
||||
import '../../domain/usecase/get_client_orders_usecase.dart';
|
||||
import '../event/current_rides_event.dart';
|
||||
import '../state/current_rides_state.dart';
|
||||
|
||||
class CurrentRidesBloc extends Bloc<CurrentRidesEvent, CurrentRidesState> {
|
||||
final GetClientOrdersUsecase _getClientOrdersUsecase;
|
||||
|
||||
CurrentRidesBloc(this._getClientOrdersUsecase) : super(const CurrentRidesState()) {
|
||||
on<LoadClientOrders>(_onLoadClientOrders);
|
||||
}
|
||||
|
||||
Future<void> _onLoadClientOrders(
|
||||
LoadClientOrders event,
|
||||
Emitter<CurrentRidesState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: CurrentRidesStatus.loading));
|
||||
|
||||
final result = await _getClientOrdersUsecase();
|
||||
|
||||
if (result is Success<List<ScooterOrder>>) {
|
||||
|
||||
print("RESULT: ${result.data}");
|
||||
|
||||
emit(state.copyWith(
|
||||
status: CurrentRidesStatus.success,
|
||||
orders: result.data ?? [],
|
||||
));
|
||||
} else if (result is Failure) {
|
||||
emit(state.copyWith(
|
||||
status: CurrentRidesStatus.failure,
|
||||
errorMessage: "Не удалось загрузить заказы",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
59
lib/presentation/viewmodel/edit_profile_bloc.dart
Normal file
59
lib/presentation/viewmodel/edit_profile_bloc.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:be_happy/domain/entities/user_profile.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../domain/usecase/get_profile_usecase.dart';
|
||||
import '../../domain/usecase/update_profile_usecase.dart';
|
||||
import '../event/edit_profile_event.dart';
|
||||
import '../event/profile_event.dart';
|
||||
import '../state/edit_profile_state.dart';
|
||||
import '../state/profile_state.dart';
|
||||
|
||||
class EditProfileBloc
|
||||
extends Bloc<EditProfileEvent, EditProfileState> {
|
||||
final UpdateProfileUseCase updateProfileUseCase;
|
||||
final GetProfileUseCase getProfileUseCase;
|
||||
|
||||
EditProfileBloc(this.updateProfileUseCase, this.getProfileUseCase)
|
||||
: super(EditProfileState.initial()) {
|
||||
|
||||
on<EditProfileStarted>(_onStarted);
|
||||
|
||||
on<EditProfileSubmitted>(_onSubmitted);
|
||||
}
|
||||
|
||||
Future<void> _onSubmitted(
|
||||
EditProfileSubmitted event,
|
||||
Emitter<EditProfileState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isSaving: true));
|
||||
|
||||
try {
|
||||
final result = await updateProfileUseCase(event.profile);
|
||||
|
||||
if (result != null) {
|
||||
emit(state.copyWith(isSaving: false, isSuccess: true));
|
||||
} else {
|
||||
emit(state.copyWith(isSaving: false, error: "Не удалось обновить профиль"));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(state.copyWith(isSaving: false, error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onStarted(
|
||||
EditProfileStarted event,
|
||||
Emitter<EditProfileState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isSaving: true));
|
||||
|
||||
print("EDIT BLOC STARTED");
|
||||
try {
|
||||
final profile = await getProfileUseCase();
|
||||
|
||||
emit(state.copyWith(profile: profile, isSaving: false, isSuccess: false));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(isSaving: false, error: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
283
lib/presentation/viewmodel/map_bloc.dart
Normal file
283
lib/presentation/viewmodel/map_bloc.dart
Normal file
@@ -0,0 +1,283 @@
|
||||
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()}",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
90
lib/presentation/viewmodel/map_settings_modal_bloc.dart
Normal file
90
lib/presentation/viewmodel/map_settings_modal_bloc.dart
Normal file
@@ -0,0 +1,90 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:be_happy/domain/entities/map_settings.dart';
|
||||
import 'package:be_happy/domain/usecase/get_map_settings_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/get_pedestrian_routes_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/get_scooter_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/save_map_settings_usecase.dart';
|
||||
import 'package:be_happy/presentation/event/map_settings_modal_event.dart';
|
||||
import 'package:be_happy/presentation/state/map_settings_modal_state.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:yandex_mapkit/yandex_mapkit.dart';
|
||||
|
||||
class MapSettingsModalBloc extends Bloc<MapSettingsModalEvent, MapSettingsModalState> {
|
||||
final GetMapSettingsUsecase getMapSettingsUsecase;
|
||||
final SaveMapSettingsUsecase saveMapSettingsUsecase;
|
||||
|
||||
MapSettingsModalBloc(this.getMapSettingsUsecase, this.saveMapSettingsUsecase)
|
||||
: super(MapSettingsModalState(
|
||||
isAllGeomarksActive: true,
|
||||
isAllGeozonesActive: true,
|
||||
isParkingZoneActive: true,
|
||||
isRestrictedParkingZoneActive: true,
|
||||
isRestrictedDrivingZoneActive: true,
|
||||
)) {
|
||||
on<AllGeozonesToggled>(_onAllGeozonesToggled);
|
||||
on<AllGeomarksToggled>(_onAllGeomarksToggled);
|
||||
on<ParkingZonesToggled>(_onParkingZonesToggled);
|
||||
on<RestrictedParkingZonesToggled>(_onRestrictedParkingZonesToggled);
|
||||
on<RestrictedDrivingZonesToggled>(_onRestrictedDrivingZonesToggled);
|
||||
on<ApllyButtonClick>(_onApplyClick);
|
||||
on<MapSettingsModalStarted>(_onModalStarted);
|
||||
}
|
||||
|
||||
FutureOr<void> _onAllGeozonesToggled(AllGeozonesToggled event, Emitter<MapSettingsModalState> emit) {
|
||||
emit(state.copyWith(
|
||||
isAllGeozonesActive: event.value,
|
||||
isRestrictedParkingZoneActive: event.value,
|
||||
isParkingZoneActive: event.value,
|
||||
isRestrictedDrivingZoneActive: event.value,
|
||||
));
|
||||
}
|
||||
|
||||
FutureOr<void> _onParkingZonesToggled(ParkingZonesToggled event, Emitter<MapSettingsModalState> emit) {
|
||||
final newState = state.copyWith(isParkingZoneActive: event.value);
|
||||
emit(_calculateParentState(newState));
|
||||
}
|
||||
|
||||
FutureOr<void> _onRestrictedParkingZonesToggled(RestrictedParkingZonesToggled event, Emitter<MapSettingsModalState> emit) {
|
||||
final newState = state.copyWith(isRestrictedParkingZoneActive: event.value);
|
||||
emit(_calculateParentState(newState));
|
||||
}
|
||||
|
||||
FutureOr<void> _onRestrictedDrivingZonesToggled(RestrictedDrivingZonesToggled event, Emitter<MapSettingsModalState> emit) {
|
||||
final newState = state.copyWith(isRestrictedDrivingZoneActive: event.value);
|
||||
emit(_calculateParentState(newState));
|
||||
}
|
||||
|
||||
MapSettingsModalState _calculateParentState(MapSettingsModalState currentState) {
|
||||
final bool anyChildActive = currentState.isParkingZoneActive ||
|
||||
currentState.isRestrictedParkingZoneActive ||
|
||||
currentState.isRestrictedDrivingZoneActive;
|
||||
|
||||
return currentState.copyWith(isAllGeozonesActive: anyChildActive);
|
||||
}
|
||||
|
||||
FutureOr<void> _onAllGeomarksToggled(AllGeomarksToggled event, Emitter<MapSettingsModalState> emit) {
|
||||
emit(state.copyWith(isGeomarksActive: event.value));
|
||||
}
|
||||
|
||||
FutureOr<void> _onApplyClick(ApllyButtonClick event, Emitter<MapSettingsModalState> emit) async {
|
||||
MapSettings settings = MapSettings(
|
||||
all_placemarks: state.isAllGeomarksActive,
|
||||
all_zones: state.isAllGeozonesActive,
|
||||
parking_zones: state.isParkingZoneActive,
|
||||
restricted_parking_zones: state.isRestrictedParkingZoneActive,
|
||||
restricted_driving_zones: state.isRestrictedDrivingZoneActive);
|
||||
await saveMapSettingsUsecase(settings);
|
||||
}
|
||||
|
||||
FutureOr<void> _onModalStarted(MapSettingsModalStarted event, Emitter<MapSettingsModalState> emit) async {
|
||||
final settings = await getMapSettingsUsecase();
|
||||
emit(state.copyWith(
|
||||
isGeomarksActive: settings.all_placemarks,
|
||||
isAllGeozonesActive: settings.all_zones,
|
||||
isParkingZoneActive: settings.parking_zones,
|
||||
isRestrictedParkingZoneActive: settings.restricted_parking_zones,
|
||||
isRestrictedDrivingZoneActive: settings.restricted_driving_zones,
|
||||
));
|
||||
}
|
||||
}
|
||||
40
lib/presentation/viewmodel/news_bloc.dart
Normal file
40
lib/presentation/viewmodel/news_bloc.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'dart:developer' as dev;
|
||||
import '../../../domain/repositories/news_repository.dart';
|
||||
import '../event/news_event.dart';
|
||||
import '../state/news_state.dart';
|
||||
|
||||
class NewsBloc extends Bloc<NewsEvent, NewsState> {
|
||||
final NewsRepository _newsRepository;
|
||||
|
||||
NewsBloc(this._newsRepository) : super(const NewsState(status: NewsStatus.initial)) {
|
||||
on<NewsFetchRequested>(_onFetchRequested);
|
||||
}
|
||||
|
||||
Future<void> _onFetchRequested(
|
||||
NewsFetchRequested event,
|
||||
Emitter<NewsState> emit,
|
||||
) async {
|
||||
dev.log('NewsBloc: Получено событие NewsFetchRequested');
|
||||
|
||||
emit(state.copyWith(status: NewsStatus.loading));
|
||||
|
||||
try {
|
||||
final news = await _newsRepository.getNews();
|
||||
|
||||
dev.log('NewsBloc: Успешно загружено ${news.length} новостей');
|
||||
|
||||
emit(state.copyWith(
|
||||
status: NewsStatus.success,
|
||||
news: news,
|
||||
));
|
||||
} catch (e, stackTrace) {
|
||||
dev.log('NewsBloc: Ошибка: $e', stackTrace: stackTrace);
|
||||
|
||||
emit(state.copyWith(
|
||||
status: NewsStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
107
lib/presentation/viewmodel/order_history_bloc.dart
Normal file
107
lib/presentation/viewmodel/order_history_bloc.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:be_happy/domain/entities/scooter_order.dart';
|
||||
import 'package:be_happy/domain/usecase/get_scooter_order_history_usecase.dart';
|
||||
import 'package:be_happy/core/result.dart';
|
||||
|
||||
// 🔹 EVENTS
|
||||
abstract class OrderHistoryEvent {}
|
||||
|
||||
class OrderHistoryFetchRequested extends OrderHistoryEvent {
|
||||
final int page;
|
||||
OrderHistoryFetchRequested({this.page = 1});
|
||||
}
|
||||
|
||||
class OrderHistoryRefreshRequested extends OrderHistoryEvent {}
|
||||
|
||||
// 🔹 STATES
|
||||
enum OrderHistoryStatus { initial, loading, success, failure, empty }
|
||||
|
||||
class OrderHistoryState {
|
||||
final OrderHistoryStatus status;
|
||||
final List<ScooterOrder> orders;
|
||||
final String? errorMessage;
|
||||
final int currentPage;
|
||||
final bool hasMore;
|
||||
|
||||
OrderHistoryState({
|
||||
required this.status,
|
||||
this.orders = const [],
|
||||
this.errorMessage,
|
||||
this.currentPage = 1,
|
||||
this.hasMore = true,
|
||||
});
|
||||
|
||||
OrderHistoryState copyWith({
|
||||
OrderHistoryStatus? status,
|
||||
List<ScooterOrder>? orders,
|
||||
String? errorMessage,
|
||||
int? currentPage,
|
||||
bool? hasMore,
|
||||
}) {
|
||||
return OrderHistoryState(
|
||||
status: status ?? this.status,
|
||||
orders: orders ?? this.orders,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
currentPage: currentPage ?? this.currentPage,
|
||||
hasMore: hasMore ?? this.hasMore,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OrderHistoryBloc extends Bloc<OrderHistoryEvent, OrderHistoryState> {
|
||||
final GetScooterOrderHistoryUsecase _usecase;
|
||||
|
||||
OrderHistoryBloc(this._usecase) : super(OrderHistoryState(status: OrderHistoryStatus.initial)) {
|
||||
on<OrderHistoryFetchRequested>(_onFetchRequested);
|
||||
on<OrderHistoryRefreshRequested>(_onRefreshRequested);
|
||||
}
|
||||
|
||||
Future<void> _onFetchRequested(
|
||||
OrderHistoryFetchRequested event,
|
||||
Emitter<OrderHistoryState> emit,
|
||||
) async {
|
||||
if (event.page == 1) {
|
||||
emit(state.copyWith(status: OrderHistoryStatus.loading));
|
||||
}
|
||||
|
||||
final result = await _usecase.call(page: event.page);
|
||||
|
||||
// ✅ Явная проверка с правильным типом
|
||||
if (result is Success<List<ScooterOrder>>) {
|
||||
final orders = result.data;
|
||||
|
||||
if (orders == null || orders.isEmpty) {
|
||||
emit(state.copyWith(
|
||||
status: OrderHistoryStatus.empty,
|
||||
orders: [],
|
||||
));
|
||||
} else {
|
||||
final newOrders = event.page == 1
|
||||
? orders
|
||||
: [...state.orders, ...orders];
|
||||
|
||||
emit(state.copyWith(
|
||||
status: OrderHistoryStatus.success,
|
||||
orders: newOrders,
|
||||
currentPage: event.page,
|
||||
hasMore: orders.length == 20,
|
||||
));
|
||||
}
|
||||
}
|
||||
// ✅ Явно указываем тип Failure
|
||||
else if (result is Failure<List<ScooterOrder>>) {
|
||||
emit(state.copyWith(
|
||||
status: OrderHistoryStatus.failure,
|
||||
errorMessage: result.failure.message ?? 'Неизвестная ошибка',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onRefreshRequested(
|
||||
OrderHistoryRefreshRequested event,
|
||||
Emitter<OrderHistoryState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: OrderHistoryStatus.loading));
|
||||
add(OrderHistoryFetchRequested(page: 1));
|
||||
}
|
||||
}
|
||||
124
lib/presentation/viewmodel/payment_confirm_bloc.dart
Normal file
124
lib/presentation/viewmodel/payment_confirm_bloc.dart
Normal file
@@ -0,0 +1,124 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:be_happy/domain/usecase/get_payment_cards_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/get_profile_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/get_scooter_order_by_id_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/result.dart';
|
||||
import '../../../domain/entities/scooter_order.dart';
|
||||
import '../../../domain/usecase/pay_ride_usecase.dart';
|
||||
import '../../domain/entities/payment_card.dart';
|
||||
import '../event/payment_confirm_event.dart';
|
||||
import '../state/payment_confirm_state.dart';
|
||||
|
||||
class PaymentConfirmBloc
|
||||
extends Bloc<PaymentConfirmEvent, PaymentConfirmState> {
|
||||
final PayRideUsecase _payRideUsecase;
|
||||
final GetScooterOrderByIdUsecase _getScooterOrderByIdUsecase;
|
||||
final GetPaymentCardsUsecase _getPaymentCardsUsecase;
|
||||
final GetProfileUseCase _getProfileUseCase;
|
||||
|
||||
PaymentConfirmBloc(
|
||||
this._payRideUsecase,
|
||||
this._getScooterOrderByIdUsecase,
|
||||
this._getPaymentCardsUsecase,
|
||||
this._getProfileUseCase,
|
||||
) : super(const PaymentConfirmState()) {
|
||||
on<PayRide>(_onPayRide);
|
||||
on<PaymentConfirmStarted>(_onStarted);
|
||||
on<PaymentCardChanged>(_onCardChanged);
|
||||
on<SelectBalancePressed>(_onSelectBalancePressed);
|
||||
}
|
||||
|
||||
Future<void> _onStarted(
|
||||
PaymentConfirmStarted event,
|
||||
Emitter<PaymentConfirmState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: PaymentConfirmStatus.loading));
|
||||
|
||||
PaymentCard? mainCard;
|
||||
final result = await _getScooterOrderByIdUsecase(event.orderId);
|
||||
final cards_result = await _getPaymentCardsUsecase();
|
||||
|
||||
if (cards_result is Success<List<PaymentCard>>) {
|
||||
mainCard = cards_result.data?.firstWhere(
|
||||
(card) => card.isMain,
|
||||
orElse: () => cards_result.data!.first,
|
||||
);
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case Success<ScooterOrder>(data: final order):
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: PaymentConfirmStatus.initial,
|
||||
order: order,
|
||||
selectedCard: mainCard,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case Failure<ScooterOrder>():
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: PaymentConfirmStatus.failure,
|
||||
errorMessage: 'Не удалось загрузить данные поездки',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onCardChanged(
|
||||
PaymentCardChanged event,
|
||||
Emitter<PaymentConfirmState> emit,
|
||||
) {
|
||||
emit(state.copyWith(selectedCard: event.card, useBalance: false));
|
||||
}
|
||||
|
||||
Future<void> _onPayRide(
|
||||
PayRide event,
|
||||
Emitter<PaymentConfirmState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: PaymentConfirmStatus.loading));
|
||||
|
||||
final photoIds = event.photoIds.isEmpty ? [1] : event.photoIds;
|
||||
|
||||
final result = await _payRideUsecase(
|
||||
event.orderId,
|
||||
event.cardId,
|
||||
event.isBalance,
|
||||
);
|
||||
|
||||
if (result is Success<ScooterOrder>) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: PaymentConfirmStatus.success,
|
||||
paymentCompleted: true,
|
||||
|
||||
),
|
||||
);
|
||||
} else if (result is Failure) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: PaymentConfirmStatus.failure,
|
||||
errorMessage: 'Не удалось оплатить поездку',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onSelectBalancePressed(
|
||||
SelectBalancePressed event,
|
||||
Emitter<PaymentConfirmState> emit,
|
||||
) async {
|
||||
final profile = await _getProfileUseCase();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
useBalance: true,
|
||||
userBalance: profile.balance,
|
||||
selectedCard: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
44
lib/presentation/viewmodel/payment_method_sheet_bloc.dart
Normal file
44
lib/presentation/viewmodel/payment_method_sheet_bloc.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../core/result.dart';
|
||||
import '../../domain/entities/payment_card.dart';
|
||||
import '../../domain/usecase/get_payment_cards_usecase.dart';
|
||||
import '../event/payment_method_sheet_event.dart';
|
||||
import '../state/payment_method_sheet_state.dart';
|
||||
|
||||
class PaymentMethodSheetBloc extends Bloc<PaymentMethodSheetEvent, PaymentMethodSheetState> {
|
||||
final GetPaymentCardsUsecase _getPaymentCardsUsecase;
|
||||
|
||||
PaymentMethodSheetBloc(this._getPaymentCardsUsecase)
|
||||
: super(PaymentMethodSheetState(status: PaymentMethodSheetStatus.initial)) {
|
||||
on<PaymentMethodSheetStarted>(_onStarted);
|
||||
}
|
||||
|
||||
Future<void> _onStarted(
|
||||
PaymentMethodSheetStarted event,
|
||||
Emitter<PaymentMethodSheetState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: PaymentMethodSheetStatus.loading));
|
||||
|
||||
try {
|
||||
final result = await _getPaymentCardsUsecase();
|
||||
|
||||
if (result is Success<List<PaymentCard>>) {
|
||||
emit(state.copyWith(
|
||||
status: PaymentMethodSheetStatus.success,
|
||||
cards: result.data ?? [],
|
||||
));
|
||||
} else {
|
||||
emit(state.copyWith(
|
||||
status: PaymentMethodSheetStatus.failure,
|
||||
errorMessage: 'Failed to load payment cards',
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: PaymentMethodSheetStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
136
lib/presentation/viewmodel/payment_methods_bloc.dart
Normal file
136
lib/presentation/viewmodel/payment_methods_bloc.dart
Normal file
@@ -0,0 +1,136 @@
|
||||
import 'package:be_happy/domain/usecase/get_profile_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../core/result.dart';
|
||||
import '../../core/failures.dart';
|
||||
import '../../domain/entities/payment_card.dart';
|
||||
import '../../domain/usecase/get_payment_cards_usecase.dart';
|
||||
import '../../domain/usecase/remove_payment_card_usecase.dart';
|
||||
import '../../domain/usecase/set_main_payment_card_usecase.dart';
|
||||
import '../event/payment_methods_event.dart';
|
||||
import '../state/payment_methods_state.dart';
|
||||
|
||||
class PaymentMethodsBloc extends Bloc<PaymentMethodsEvent, PaymentMethodsState> {
|
||||
final GetPaymentCardsUsecase _getPaymentCardsUsecase;
|
||||
final RemovePaymentCardUsecase _removePaymentCardUsecase;
|
||||
final SetMainPaymentCardUsecase _setMainPaymentCardUsecase;
|
||||
final GetProfileUseCase _getProfileUseCase;
|
||||
|
||||
PaymentMethodsBloc(
|
||||
this._getPaymentCardsUsecase,
|
||||
this._removePaymentCardUsecase,
|
||||
this._setMainPaymentCardUsecase,
|
||||
this._getProfileUseCase,
|
||||
) : super(PaymentMethodsState(status: PaymentMethodsStatus.initial)) {
|
||||
on<PaymentMethodsStarted>(_onStarted);
|
||||
on<PaymentMethodsDeleteCard>(_onDeleteCard);
|
||||
on<PaymentMethodsSetMainCard>(_onSetMainCard);
|
||||
}
|
||||
|
||||
Future<void> _onStarted(
|
||||
PaymentMethodsStarted event,
|
||||
Emitter<PaymentMethodsState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: PaymentMethodsStatus.loading));
|
||||
|
||||
try {
|
||||
final result = await _getPaymentCardsUsecase();
|
||||
final profile = await _getProfileUseCase();
|
||||
|
||||
|
||||
if (result is Success<List<PaymentCard>>) {
|
||||
emit(state.copyWith(
|
||||
status: PaymentMethodsStatus.success,
|
||||
cards: result.data ?? [],
|
||||
balance: profile.balance,
|
||||
));
|
||||
} else if (result is Failure) {
|
||||
String errorMessage = 'Не удалось загрузить карты';
|
||||
|
||||
/*if (result.failure is AuthFailure) {
|
||||
errorMessage = 'Ошибка авторизации';
|
||||
} else if (result.failure is UnknownFailure) {
|
||||
errorMessage = result.failure.message ?? 'Неизвестная ошибка';
|
||||
}*/
|
||||
|
||||
emit(state.copyWith(
|
||||
status: PaymentMethodsStatus.failure,
|
||||
errorMessage: errorMessage,
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: PaymentMethodsStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onDeleteCard(
|
||||
PaymentMethodsDeleteCard event,
|
||||
Emitter<PaymentMethodsState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isDeleting: true));
|
||||
|
||||
try {
|
||||
final result = await _removePaymentCardUsecase(event.cardId);
|
||||
|
||||
if (result is Success) {
|
||||
emit(state.copyWith(isDeleting: false));
|
||||
add(PaymentMethodsStarted());
|
||||
} else if (result is Failure) {
|
||||
String errorMessage = 'Не удалось удалить карту';
|
||||
|
||||
if (result.failure is AuthFailure) {
|
||||
errorMessage = 'Ошибка авторизации';
|
||||
} else if (result.failure is UnknownFailure) {
|
||||
errorMessage = result.failure.message ?? 'Неизвестная ошибка';
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
isDeleting: false,
|
||||
errorMessage: errorMessage,
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
isDeleting: false,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSetMainCard(
|
||||
PaymentMethodsSetMainCard event,
|
||||
Emitter<PaymentMethodsState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isSettingMain: true));
|
||||
|
||||
try {
|
||||
final result = await _setMainPaymentCardUsecase(event.cardId);
|
||||
|
||||
if (result is Success) {
|
||||
emit(state.copyWith(isSettingMain: false));
|
||||
add(PaymentMethodsStarted());
|
||||
} else if (result is Failure) {
|
||||
String errorMessage = 'Не удалось установить основную карту';
|
||||
|
||||
if (result.failure is AuthFailure) {
|
||||
errorMessage = 'Ошибка авторизации';
|
||||
} else if (result.failure is UnknownFailure) {
|
||||
errorMessage = result.failure.message ?? 'Неизвестная ошибка';
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
isSettingMain: false,
|
||||
errorMessage: errorMessage,
|
||||
));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
isSettingMain: false,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
84
lib/presentation/viewmodel/pin_bloc.dart
Normal file
84
lib/presentation/viewmodel/pin_bloc.dart
Normal file
@@ -0,0 +1,84 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:be_happy/domain/usecase/verify_pin_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../domain/usecase/is_pin_set_usecase.dart';
|
||||
import '../event/pin_event.dart';
|
||||
import '../state/pin_state.dart';
|
||||
import '../../domain/usecase/create_pin_usecase.dart';
|
||||
|
||||
class PinBloc extends Bloc<PinEvent, PinState> {
|
||||
final CreatePinUseCase createPinUseCase;
|
||||
final VerifyPinUseCase verifyPinUsecase;
|
||||
final IsPinSetUsecase isPinSetUsecase;
|
||||
|
||||
PinBloc({
|
||||
required this.createPinUseCase,
|
||||
required this.verifyPinUsecase,
|
||||
required this.isPinSetUsecase,
|
||||
bool isRegistration = true,
|
||||
}) : super(const PinLoading()) {
|
||||
on<PinDigitChanged>(_onPinChanged);
|
||||
on<PinSubmitted>(_onPinSubmitted);
|
||||
on<PinScreenStarted>(_onPinScreenStarted);
|
||||
}
|
||||
|
||||
void _onPinChanged(
|
||||
PinDigitChanged event,
|
||||
Emitter<PinState> emit,
|
||||
) {
|
||||
if (state is PinCreateInProgress) {
|
||||
emit(PinCreateInProgress(pin: event.pin));
|
||||
} else if (state is PinLoginInProgress) {
|
||||
emit(PinLoginInProgress(pin: event.pin));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onPinSubmitted(
|
||||
PinSubmitted event,
|
||||
Emitter<PinState> emit,
|
||||
) async {
|
||||
final currentPin = event.pin;
|
||||
final bool isCreateMode = state is PinCreateInProgress;
|
||||
|
||||
emit(const PinLoading());
|
||||
|
||||
try {
|
||||
if (isCreateMode) {
|
||||
await createPinUseCase(currentPin);
|
||||
emit(const PinSuccess());
|
||||
} else {
|
||||
final isValid = await verifyPinUsecase(currentPin);
|
||||
if (isValid) {
|
||||
emit(const PinSuccess());
|
||||
} else {
|
||||
throw Exception("Неверный ПИН-код");
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (isCreateMode) {
|
||||
emit(PinCreateInProgress(pin: '', error: e.toString()));
|
||||
} else {
|
||||
emit(PinLoginInProgress(pin: '', error: e.toString().contains("Неверный")
|
||||
? "Неверный ПИН-код"
|
||||
: "Ошибка при проверке"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onPinScreenStarted(PinScreenStarted event, Emitter<PinState> emit) async {
|
||||
try {
|
||||
final bool hasPin = await isPinSetUsecase();
|
||||
|
||||
if (hasPin) {
|
||||
// Пин ЕСТЬ в базе — значит, нужно его ВВЕСТИ (Login)
|
||||
emit(const PinLoginInProgress(pin: ''));
|
||||
} else {
|
||||
// Пина НЕТ в базе — значит, нужно его СОЗДАТЬ (Create)
|
||||
emit(const PinCreateInProgress(pin: ''));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(PinLoginInProgress(pin: '', error: "Ошибка доступа к хранилищу"));
|
||||
}
|
||||
}
|
||||
}
|
||||
75
lib/presentation/viewmodel/profile_bloc.dart
Normal file
75
lib/presentation/viewmodel/profile_bloc.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'package:be_happy/domain/usecase/upload_profile_photo_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../domain/entities/user_profile.dart';
|
||||
import '../../domain/usecase/get_profile_usecase.dart';
|
||||
import '../../domain/usecase/update_profile_usecase.dart';
|
||||
import '../event/profile_event.dart';
|
||||
import '../state/profile_state.dart';
|
||||
|
||||
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
final GetProfileUseCase getProfileUseCase;
|
||||
final UploadProfilePhotoUsecase uploadProfilePhotoUsecase;
|
||||
final UpdateProfileUseCase updateProfileUseCase;
|
||||
|
||||
|
||||
ProfileBloc(this.getProfileUseCase,
|
||||
this.uploadProfilePhotoUsecase,
|
||||
this.updateProfileUseCase)
|
||||
: super(ProfileState.initial()) {
|
||||
on<ProfileStarted>(_onStarted);
|
||||
on<ProfileUpdated>(_onUpdated);
|
||||
on<ProfilePhotoUpdated>(_onPhotoUploaded);
|
||||
}
|
||||
|
||||
Future<void> _onStarted(
|
||||
ProfileStarted event,
|
||||
Emitter<ProfileState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
print("_onStarted method was start");
|
||||
try {
|
||||
final profile = await getProfileUseCase();
|
||||
print(profile.name);
|
||||
|
||||
emit(state.copyWith(isLoading: false, profile: profile));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(isLoading: false, error: "STARTED: ${e.toString()}"));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onUpdated(
|
||||
ProfileUpdated event,
|
||||
Emitter<ProfileState> emit,
|
||||
) async {
|
||||
final profile = await getProfileUseCase();
|
||||
emit(state.copyWith(profile: profile));
|
||||
}
|
||||
|
||||
Future<void> _onPhotoUploaded(
|
||||
ProfilePhotoUpdated event,
|
||||
Emitter<ProfileState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
try {
|
||||
final avatarId = await uploadProfilePhotoUsecase(event.imageFile);
|
||||
|
||||
if (avatarId != null) {
|
||||
// Отправляем PATCH запрос с новым avatarId
|
||||
await updateProfileUseCase(UserProfile(
|
||||
name: '',
|
||||
birthDate: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
avatarId: avatarId,
|
||||
));
|
||||
}
|
||||
|
||||
final updatedProfile = await getProfileUseCase();
|
||||
|
||||
emit(state.copyWith(isLoading: false, profile: updatedProfile, error: null));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(isLoading: false, error: "Ошибка загрузки: ${e.toString()}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
63
lib/presentation/viewmodel/reserved_ride_bloc.dart
Normal file
63
lib/presentation/viewmodel/reserved_ride_bloc.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/result.dart';
|
||||
import '../../../domain/entities/scooter_order.dart';
|
||||
import '../../../domain/usecase/cancel_ride_usecase.dart';
|
||||
import '../../../domain/usecase/start_ride_usecase.dart';
|
||||
import '../event/reserved_ride_event.dart';
|
||||
import '../state/reserved_ride_state.dart';
|
||||
|
||||
class ReservedRideBloc extends Bloc<ReservedRideEvent, ReservedRideState> {
|
||||
final StartRideUsecase _startRideUsecase;
|
||||
final CancelRideUsecase _cancelRideUsecase;
|
||||
|
||||
ReservedRideBloc(
|
||||
this._startRideUsecase,
|
||||
this._cancelRideUsecase,
|
||||
) : super(const ReservedRideState()) {
|
||||
on<StartRide>(_onStartRide);
|
||||
on<CancelRide>(_onCancelRide);
|
||||
}
|
||||
|
||||
Future<void> _onStartRide(
|
||||
StartRide event,
|
||||
Emitter<ReservedRideState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ReservedRideStatus.loading));
|
||||
|
||||
final result = await _startRideUsecase(event.orderId);
|
||||
|
||||
if (result is Success<ScooterOrder>) {
|
||||
emit(state.copyWith(
|
||||
status: ReservedRideStatus.success,
|
||||
rideStarted: true,
|
||||
));
|
||||
} else if (result is Failure) {
|
||||
emit(state.copyWith(
|
||||
status: ReservedRideStatus.failure,
|
||||
errorMessage: 'Не удалось начать поездку',
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onCancelRide(
|
||||
CancelRide event,
|
||||
Emitter<ReservedRideState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ReservedRideStatus.loading));
|
||||
|
||||
final result = await _cancelRideUsecase(event.orderId);
|
||||
|
||||
if (result is Success<ScooterOrder>) {
|
||||
emit(state.copyWith(
|
||||
status: ReservedRideStatus.success,
|
||||
rideCancelled: true,
|
||||
));
|
||||
} else if (result is Failure) {
|
||||
emit(state.copyWith(
|
||||
status: ReservedRideStatus.failure,
|
||||
errorMessage: 'Не удалось отменить бронирование',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
31
lib/presentation/viewmodel/route_bloc.dart
Normal file
31
lib/presentation/viewmodel/route_bloc.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:be_happy/core/result.dart';
|
||||
import 'package:be_happy/domain/entities/point.dart';
|
||||
import 'package:be_happy/domain/usecase/get_scooter_order_route_history_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../event/route_event.dart';
|
||||
import '../state/route_state.dart';
|
||||
|
||||
class RouteBloc extends Bloc<RouteEvent, RouteState> {
|
||||
final GetScooterOrderRouteHistoryUsecase getRouteUseCase;
|
||||
|
||||
RouteBloc({required this.getRouteUseCase}) : super(RouteInitial()) {
|
||||
on<FetchRouteEvent>((event, emit) async {
|
||||
emit(RouteLoading());
|
||||
try {
|
||||
final result = await getRouteUseCase(event.orderId);
|
||||
|
||||
if (result is Success<List<Point>>) {
|
||||
|
||||
final List<Point> points = result.data ?? [];
|
||||
|
||||
emit(RouteLoaded(points));
|
||||
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
emit(RouteError("Ошибка загрузки пути"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
27
lib/presentation/viewmodel/scooter_code_bloc.dart
Normal file
27
lib/presentation/viewmodel/scooter_code_bloc.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:be_happy/domain/entities/scooter.dart';
|
||||
import 'package:be_happy/domain/usecase/get_scooter_by_title_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../event/scooter_code_event.dart';
|
||||
import '../state/scooter_code_state.dart';
|
||||
|
||||
class ScooterCodeBloc extends Bloc<ScooterCodeEvent, ScooterCodeState> {
|
||||
final GetScooterByTitleUsecase getScooterByTitleUsecase;
|
||||
|
||||
ScooterCodeBloc({required this.getScooterByTitleUsecase}) : super(const ScooterCodeInitial()) {
|
||||
on<ScooterCodeChanged>((event, emit) {
|
||||
emit(ScooterCodeInitial(code: event.code));
|
||||
});
|
||||
|
||||
on<ScooterCodeSubmitted>((event, emit) async {
|
||||
emit(ScooterCodeLoading(code: event.code));
|
||||
|
||||
final result = await getScooterByTitleUsecase(event.code);
|
||||
|
||||
/*result.fold(
|
||||
(error) => emit(ScooterCodeFailure(error.message ?? "Ошибка", code: event.code)),
|
||||
(scooter) => emit(ScooterCodeSuccess(scooter, code: event.code)),
|
||||
);*/
|
||||
});
|
||||
}
|
||||
}
|
||||
37
lib/presentation/viewmodel/scooter_detail_bloc.dart
Normal file
37
lib/presentation/viewmodel/scooter_detail_bloc.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../core/result.dart';
|
||||
import '../../domain/entities/scooter.dart';
|
||||
import '../../domain/usecase/get_scooter_usecase.dart';
|
||||
import '../event/scooter_detail_event.dart';
|
||||
import '../state/scooter_detail_state.dart';
|
||||
|
||||
class ScooterDetailBloc extends Bloc<ScooterDetailEvent, ScooterDetailState> {
|
||||
final GetScooterUsecase _getScooterUsecase;
|
||||
|
||||
ScooterDetailBloc(this._getScooterUsecase) : super(const ScooterDetailState()) {
|
||||
on<LoadScooterDetails>(_onLoadScooterDetails);
|
||||
}
|
||||
|
||||
Future<void> _onLoadScooterDetails(
|
||||
LoadScooterDetails event,
|
||||
Emitter<ScooterDetailState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ScooterStatus.loading));
|
||||
|
||||
final result = await _getScooterUsecase(event.scooterId);
|
||||
|
||||
if (result is Success<Scooter?>) {
|
||||
print("SCOOTER: ${result.data}");
|
||||
emit(state.copyWith(
|
||||
status: ScooterStatus.success,
|
||||
scooter: result.data,
|
||||
));
|
||||
} else if (result is Failure) {
|
||||
emit(state.copyWith(
|
||||
status: ScooterStatus.failure,
|
||||
errorMessage: "Не удалось загрузить данные",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
91
lib/presentation/viewmodel/scooter_detail_modal_bloc.dart
Normal file
91
lib/presentation/viewmodel/scooter_detail_modal_bloc.dart
Normal file
@@ -0,0 +1,91 @@
|
||||
import 'package:be_happy/domain/usecase/get_pedestrian_routes_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/get_scooter_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:yandex_mapkit/yandex_mapkit.dart';
|
||||
|
||||
import '../../core/result.dart';
|
||||
import '../../domain/entities/scooter.dart';
|
||||
import '../../domain/usecase/get_address_by_point_usecase.dart';
|
||||
import '../event/scooter_detail_event.dart';
|
||||
import '../event/scooter_detail_modal_event.dart';
|
||||
import '../state/map_state.dart';
|
||||
import '../state/scooter_detail_modal_state.dart';
|
||||
|
||||
class ScooterDetailModalBloc
|
||||
extends Bloc<ScooterDetailModalEvent, ScooterDetailModalState> {
|
||||
final GetAddressByPointUsecase _getAddressUsecase;
|
||||
final GetPedestrianRoutesUsecase _getPedestrianRoutesUsecase;
|
||||
final GetScooterUsecase _getScooterUsecase;
|
||||
final Map<int, String> _addressCache = {};
|
||||
final List<Scooter> fetch_scooters = [];
|
||||
double? distance = 0.0;
|
||||
|
||||
ScooterDetailModalBloc(
|
||||
this._getAddressUsecase,
|
||||
this._getScooterUsecase,
|
||||
this._getPedestrianRoutesUsecase,
|
||||
) : super(
|
||||
ScooterDetailModalState(
|
||||
status: ScooterDetailModalStatus.initial,
|
||||
),
|
||||
) {
|
||||
on<ScooterDetailModalStarted>(_onStarted);
|
||||
}
|
||||
|
||||
Future<void> _onStarted(
|
||||
ScooterDetailModalStarted event,
|
||||
Emitter<ScooterDetailModalState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: ScooterDetailModalStatus.loading));
|
||||
|
||||
final List<Scooter> updatedScooters = [];
|
||||
String? firstAddress;
|
||||
|
||||
try {
|
||||
for (var scooter in event.scooters) {
|
||||
final result = await _getScooterUsecase(scooter.id);
|
||||
|
||||
String? currentAddress = _addressCache[scooter.id];
|
||||
if (currentAddress == null) {
|
||||
currentAddress = await _getAddressUsecase(scooter.longitude, scooter.latitude);
|
||||
_addressCache[scooter.id] = currentAddress;
|
||||
}
|
||||
|
||||
firstAddress ??= currentAddress;
|
||||
|
||||
final routes = await _getPedestrianRoutesUsecase(
|
||||
Point(latitude: event.userLatitude, longitude: event.userLongitude),
|
||||
Point(latitude: scooter.latitude, longitude: scooter.longitude),
|
||||
);
|
||||
|
||||
final distance = routes?.firstOrNull?.metadata.weight.walkingDistance.value;
|
||||
final time = routes?.firstOrNull?.metadata.weight.time.value;
|
||||
|
||||
if (result is Success<Scooter>) {
|
||||
final data = result.data!;
|
||||
data.distance = distance;
|
||||
data.timeToTravel = time;
|
||||
updatedScooters.add(data);
|
||||
} else {
|
||||
updatedScooters.add(scooter);
|
||||
}
|
||||
}
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: ScooterDetailModalStatus.success,
|
||||
scooters: updatedScooters,
|
||||
address: firstAddress ?? "Unknown address",
|
||||
distance: updatedScooters.firstOrNull?.distance,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
print('Error in Bloc: $e');
|
||||
emit(state.copyWith(
|
||||
status: ScooterDetailModalStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
78
lib/presentation/viewmodel/send_photo_bloc.dart
Normal file
78
lib/presentation/viewmodel/send_photo_bloc.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'dart:io';
|
||||
import 'package:be_happy/domain/entities/scooter_order.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../domain/usecase/finish_ride_usecase.dart';
|
||||
import '../../domain/usecase/upload_scooter_photos_usecase.dart';
|
||||
import '../event/send_photo_event.dart';
|
||||
import '../state/send_photo_state.dart';
|
||||
import '../../core/result.dart';
|
||||
import '../../core/failures.dart';
|
||||
|
||||
class SendPhotoBloc extends Bloc<SendPhotoEvent, SendPhotoState> {
|
||||
final UploadScooterPhotosUsecase uploadScooterPhotosUsecase;
|
||||
final FinishRideUsecase _finishRideUsecase;
|
||||
|
||||
|
||||
SendPhotoBloc(this.uploadScooterPhotosUsecase, this._finishRideUsecase) : super(const SendPhotoState()) {
|
||||
on<PhotoSelected>(_onPhotoSelected);
|
||||
on<PhotoUploadSubmitted>(_onPhotoUploadSubmitted);
|
||||
}
|
||||
|
||||
void _onPhotoSelected(PhotoSelected event, Emitter<SendPhotoState> emit) {
|
||||
emit(state.copyWith(selectedImages: event.imagePaths));
|
||||
}
|
||||
|
||||
Future<void> _onPhotoUploadSubmitted(
|
||||
PhotoUploadSubmitted event, Emitter<SendPhotoState> emit) async {
|
||||
if (state.selectedImages.isEmpty) {
|
||||
emit(state.copyWith(
|
||||
status: SendPhotoStatus.failure,
|
||||
errorMessage: 'Выберите хотя бы одно фото',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
emit(state.copyWith(status: SendPhotoStatus.loading));
|
||||
|
||||
final imageFiles = state.selectedImages.map((path) => File(path)).toList();
|
||||
|
||||
final result = await uploadScooterPhotosUsecase(imageFiles);
|
||||
|
||||
switch (result) {
|
||||
case Success<List<int>>():
|
||||
// ✅ Защита: если data null или пустой — не вызываем finish
|
||||
if (result.data == null || result.data!.isEmpty) {
|
||||
emit(state.copyWith(
|
||||
status: SendPhotoStatus.failure,
|
||||
errorMessage: 'Не удалось получить ID загруженных фото',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Передаём два параметра в UseCase
|
||||
final finishResult = await _finishRideUsecase(
|
||||
event.orderId,
|
||||
result.data! // ✅ ! т.к. проверили выше
|
||||
);
|
||||
|
||||
switch (finishResult) {
|
||||
case Success<ScooterOrder>():
|
||||
emit(state.copyWith(
|
||||
status: SendPhotoStatus.success,
|
||||
recievedPhotoIds: result.data,
|
||||
));
|
||||
case Failure():
|
||||
print("❌ FINISH ERROR: ${finishResult.failure.message}");
|
||||
emit(state.copyWith(
|
||||
status: SendPhotoStatus.failure,
|
||||
errorMessage: 'Ошибка завершения поездки',
|
||||
));
|
||||
}
|
||||
case Failure():
|
||||
emit(state.copyWith(
|
||||
status: SendPhotoStatus.failure,
|
||||
errorMessage: 'Ошибка загрузки фото: ${result.failure.message}',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
53
lib/presentation/viewmodel/splash_bloc.dart
Normal file
53
lib/presentation/viewmodel/splash_bloc.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:be_happy/domain/usecase/refresh_token_usecase.dart';
|
||||
import 'package:be_happy/presentation/event/spalsh_event.dart';
|
||||
import 'package:be_happy/presentation/state/splash_state.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SplashBloc extends Bloc<SplashEvent, SplashState> {
|
||||
final RefreshTokenUseCase _refreshTokenUseCase;
|
||||
|
||||
SplashBloc(this._refreshTokenUseCase):
|
||||
super(AuthInitial()) {
|
||||
on<AuthCheckRequested>(_onAuthCheckRequested);
|
||||
on<AuthStarted>(_onAuthStarted);
|
||||
on<PinVerificationSuccess>((event, emit) {
|
||||
emit(AuthPinVerified());
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onAuthCheckRequested(
|
||||
AuthCheckRequested event,
|
||||
Emitter<SplashState> emit,
|
||||
) async {
|
||||
print('AuthBloc: Получено событие AuthCheckRequested.');
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final bool isFirstLaunch = prefs.getBool('is_first_launch') ?? true;
|
||||
|
||||
if (isFirstLaunch) {
|
||||
await prefs.setBool('is_first_launch', false);
|
||||
emit(AuthFirstLaunch());
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Если не первый запуск, пытаемся обновить токен
|
||||
try {
|
||||
print('AuthBloc: Пытаюсь обновить токен...');
|
||||
// Здесь вызывается ваш метод refreshToken()
|
||||
await _refreshTokenUseCase.execute();
|
||||
print('AuthBloc: Токен успешно обновлен. Отправляю AuthAuthenticated.');
|
||||
// Если метод выполнился без исключений, значит токен успешно обновлен
|
||||
emit(AuthAuthenticated());
|
||||
} catch (e) {
|
||||
print('AuthBloc: Ошибка при обновлении токена: $e');
|
||||
// Если refreshToken() бросил исключение, значит что-то пошло не так
|
||||
emit(AuthUnauthenticated());
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onAuthStarted(AuthStarted event, Emitter<SplashState> emit) {
|
||||
emit(AuthInProgress());
|
||||
}
|
||||
}
|
||||
42
lib/presentation/viewmodel/subscription_list_bloc.dart
Normal file
42
lib/presentation/viewmodel/subscription_list_bloc.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'package:be_happy/core/result.dart';
|
||||
import 'package:be_happy/domain/entities/subscription.dart';
|
||||
import 'package:be_happy/domain/usecase/get_available_subscriptions_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/get_client_subscriptions_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../event/subscription_list_event.dart';
|
||||
import '../state/subscription_list_state.dart';
|
||||
|
||||
class SubscriptionListBloc extends Bloc<SubscriptionEvent, SubscriptionState> {
|
||||
final GetAvailableSubscriptionsUsecase getAvailableSubscriptionsUsecase;
|
||||
final GetClientSubscriptionsUsecase getClientSubscriptionsUsecase;
|
||||
|
||||
SubscriptionListBloc({
|
||||
required this.getAvailableSubscriptionsUsecase,
|
||||
required this.getClientSubscriptionsUsecase,
|
||||
}) : super(SubscriptionsLoading()) {
|
||||
on<LoadSubscriptionsEvent>((event, emit) async {
|
||||
emit(SubscriptionsLoading());
|
||||
try {
|
||||
final results = await Future.wait([
|
||||
getAvailableSubscriptionsUsecase(),
|
||||
getClientSubscriptionsUsecase(),
|
||||
]);
|
||||
|
||||
final allResult = results[0];
|
||||
final activeResult = results[1] ;
|
||||
|
||||
if (allResult is Success<List<Subscription>> && activeResult is Success<List<Subscription>>) {
|
||||
emit(SubscriptionsLoaded(
|
||||
subscriptions: allResult.data ?? [],
|
||||
activeSubscriptions: activeResult.data ?? [],
|
||||
));
|
||||
} else {
|
||||
emit(SubscriptionsError("Не удалось загрузить данные из API"));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(SubscriptionsError(e.toString()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
66
lib/presentation/viewmodel/susbcription_details_bloc.dart
Normal file
66
lib/presentation/viewmodel/susbcription_details_bloc.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'package:be_happy/core/result.dart';
|
||||
import 'package:be_happy/domain/entities/subscription.dart';
|
||||
import 'package:be_happy/domain/usecase/activate_subscription_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/get_subscription_by_id_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../event/subscription_details_event.dart';
|
||||
import '../state/susbcription_details_state.dart';
|
||||
|
||||
class SubscriptionDetailsBloc extends Bloc<SubscriptionDetailsEvent, SubscriptionDetailsState> {
|
||||
final GetSubscriptionByIdUsecase getSubscriptionByIdUsecase;
|
||||
final ActivateSubscriptionUsecase activateSubscriptionUsecase;
|
||||
|
||||
SubscriptionDetailsBloc(this.getSubscriptionByIdUsecase,
|
||||
this.activateSubscriptionUsecase) : super(DetailsLoading()) {
|
||||
on<LoadDetailsEvent>((event, emit) async {
|
||||
emit(DetailsLoading());
|
||||
try {
|
||||
|
||||
final result = await getSubscriptionByIdUsecase(event.subscriptionId);
|
||||
|
||||
switch (result) {
|
||||
|
||||
case Success<Subscription>():
|
||||
final sub = result.data;
|
||||
|
||||
if (sub == null) return;
|
||||
|
||||
emit(DetailsContentState(
|
||||
subscription: sub,
|
||||
selectedPeriod: sub.options.first,
|
||||
));
|
||||
case Failure<Subscription>():
|
||||
emit(DetailsError("Ошибка при запросе данных"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
} catch (e) {
|
||||
emit(DetailsError("Не удалось загрузить данные"));
|
||||
}
|
||||
});
|
||||
|
||||
on<SelectPeriodEvent>((event, emit) {
|
||||
if (state is DetailsContentState) {
|
||||
emit((state as DetailsContentState).copyWith(selectedPeriod: event.period));
|
||||
}
|
||||
});
|
||||
|
||||
on<ToggleAgreementEvent>((event, emit) {
|
||||
if (state is DetailsContentState) {
|
||||
emit((state as DetailsContentState).copyWith(isAgreed: event.value));
|
||||
}
|
||||
});
|
||||
|
||||
on<ActivateSubscriptionPressed>((event, emit) {
|
||||
switch(state) {
|
||||
case DetailsContentState contentState:
|
||||
activateSubscriptionUsecase(contentState.selectedPeriod.id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
128
lib/presentation/viewmodel/tariff_sheet_bloc.dart
Normal file
128
lib/presentation/viewmodel/tariff_sheet_bloc.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
import 'dart:async';
|
||||
import 'package:be_happy/domain/usecase/get_profile_usecase.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:be_happy/domain/entities/payment_card.dart';
|
||||
import 'package:be_happy/domain/usecase/book_scooter_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/get_payment_cards_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../core/result.dart';
|
||||
import '../../domain/entities/tariff.dart';
|
||||
import '../../domain/usecase/get_available_tariffs_usecase.dart';
|
||||
import '../event/tariff_sheet_event.dart';
|
||||
import '../state/tariff_sheet_state.dart';
|
||||
|
||||
class TariffSheetBloc extends Bloc<TariffSheetEvent, TariffSheetState> {
|
||||
final GetAvailableTariffsUsecase _getAvailableTariffsUsecase;
|
||||
final GetProfileUseCase _getProfileUseCase;
|
||||
final GetPaymentCardsUsecase _getPaymentCardsUsecase;
|
||||
final BookScooterUsecase _bookScooterUsecase;
|
||||
|
||||
TariffSheetBloc(
|
||||
this._getAvailableTariffsUsecase,
|
||||
this._getProfileUseCase,
|
||||
this._getPaymentCardsUsecase,
|
||||
this._bookScooterUsecase,
|
||||
) : super(TariffSheetState(status: TariffSheetStatus.initial)) {
|
||||
on<TariffSheetStarted>(_onStarted);
|
||||
on<PaymentCardChanged>(_onPaymentCardChanged);
|
||||
on<BookScooterPressed>(_onBookScooterPressed);
|
||||
on<SelectBalancePressed>(_onSelectBalancePressed);
|
||||
}
|
||||
|
||||
Future<void> _onStarted(
|
||||
TariffSheetStarted event,
|
||||
Emitter<TariffSheetState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: TariffSheetStatus.loading));
|
||||
|
||||
try {
|
||||
final result = await _getAvailableTariffsUsecase(event.scooterId);
|
||||
final cards_result = await _getPaymentCardsUsecase();
|
||||
|
||||
if (result is Success<List<Tariff>> &&
|
||||
cards_result is Success<List<PaymentCard>>) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: TariffSheetStatus.success,
|
||||
tariffs: result.data ?? [],
|
||||
selectedCard: cards_result.data?.firstWhereOrNull(
|
||||
(element) => element.isMain,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: TariffSheetStatus.failure,
|
||||
errorMessage: 'Failed to load tariffs',
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: TariffSheetStatus.failure,
|
||||
errorMessage: "ERROR: ${e.toString()} \n $stackTrace",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onPaymentCardChanged(
|
||||
PaymentCardChanged event,
|
||||
Emitter<TariffSheetState> emit,
|
||||
) {
|
||||
try {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: TariffSheetStatus.success,
|
||||
selectedCard: event.card,
|
||||
useBalance: false,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: TariffSheetStatus.failure,
|
||||
errorMessage: 'Failed to change card',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onBookScooterPressed(
|
||||
BookScooterPressed event,
|
||||
Emitter<TariffSheetState> emit,
|
||||
) {
|
||||
try {
|
||||
_bookScooterUsecase(
|
||||
scooterId: event.scooterId,
|
||||
planId: event.planId,
|
||||
cardId: event.cardId,
|
||||
subscriptionId: event.subscriptionId,
|
||||
isBalance: event.isBalance,
|
||||
isInsurance: event.isInsurance,
|
||||
);
|
||||
Future.delayed(const Duration(milliseconds: 300));
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: TariffSheetStatus.failure,
|
||||
errorMessage: 'Failed to book scooter',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onSelectBalancePressed(SelectBalancePressed event, Emitter<TariffSheetState> emit) async {
|
||||
final profile = await _getProfileUseCase();
|
||||
|
||||
emit(state.copyWith(
|
||||
useBalance: true,
|
||||
userBalance: profile.balance,
|
||||
selectedCard: null,
|
||||
));
|
||||
}
|
||||
}
|
||||
81
lib/presentation/viewmodel/top_up_bloc.dart
Normal file
81
lib/presentation/viewmodel/top_up_bloc.dart
Normal file
@@ -0,0 +1,81 @@
|
||||
import 'package:be_happy/domain/usecase/get_certificates_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/get_payment_cards_usecase.dart';
|
||||
import 'package:be_happy/domain/usecase/purchase_certificate_usecase.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../core/result.dart';
|
||||
import '../../domain/entities/certificate.dart';
|
||||
import '../../domain/entities/payment_card.dart';
|
||||
import '../event/top_up_event.dart';
|
||||
import '../state/top_up_state.dart';
|
||||
|
||||
class TopUpBloc extends Bloc<TopUpEvent, TopUpState> {
|
||||
final GetCertificatesUsecase getCertificatesUsecase;
|
||||
final PurchaseCertificateUsecase purchaseCertificateUsecase;
|
||||
final GetPaymentCardsUsecase getUserCards;
|
||||
|
||||
TopUpBloc({
|
||||
required this.getCertificatesUsecase,
|
||||
required this.purchaseCertificateUsecase,
|
||||
required this.getUserCards,
|
||||
}) : super(TopUpState(isLoading: true)) {
|
||||
on<LoadTopUpData>((event, emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
|
||||
// Запускаем оба запроса параллельно
|
||||
final results = await Future.wait([
|
||||
getCertificatesUsecase(),
|
||||
getUserCards(),
|
||||
]);
|
||||
|
||||
final certResult = results[0] as Result<List<Certificate>>;
|
||||
final cardResult = results[1] as Result<List<PaymentCard>>;
|
||||
|
||||
switch ((certResult, cardResult)) {
|
||||
case (Success(data: final certs), Success(data: final cards)):
|
||||
emit(
|
||||
state.copyWith(
|
||||
certificates: certs,
|
||||
cards: cards,
|
||||
selectedTariff: certs!.length > 1
|
||||
? certs[1]
|
||||
: (certs.isNotEmpty ? certs.first : null),
|
||||
|
||||
selectedCard: cards!.isEmpty
|
||||
? null
|
||||
: cards.firstWhere(
|
||||
(c) => c.isMain,
|
||||
orElse: () => cards.first,
|
||||
),
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
|
||||
case (
|
||||
Success<List<Certificate>>(data: null),
|
||||
Failure<List<PaymentCard>>(),
|
||||
):
|
||||
// TODO: Handle this case.
|
||||
throw UnimplementedError();
|
||||
case (
|
||||
Success<List<Certificate>>(data: [...]),
|
||||
Failure<List<PaymentCard>>(),
|
||||
):
|
||||
// TODO: Handle this case.
|
||||
throw UnimplementedError();
|
||||
case (Failure<List<Certificate>>(), _):
|
||||
// TODO: Handle this case.
|
||||
throw UnimplementedError();
|
||||
}
|
||||
});
|
||||
on<SelectCertificate>(
|
||||
(event, emit) => emit(state.copyWith(selectedTariff: event.certificate)),
|
||||
);
|
||||
on<SelectCard>(
|
||||
(event, emit) => emit(state.copyWith(selectedCard: event.card)),
|
||||
);
|
||||
on<ToggleAgreement>(
|
||||
(event, emit) => emit(state.copyWith(isAgreed: event.value)),
|
||||
);
|
||||
}
|
||||
}
|
||||
92
lib/presentation/viewmodel/verify_code_bloc.dart
Normal file
92
lib/presentation/viewmodel/verify_code_bloc.dart
Normal file
@@ -0,0 +1,92 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:be_happy/core/failures.dart';
|
||||
import 'package:be_happy/core/result.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../domain/usecase/verify_code_usecase.dart';
|
||||
import '../event/verify_code_event.dart';
|
||||
import '../state/verify_code_state.dart';
|
||||
|
||||
class VerifyCodeBloc extends Bloc<VerifyCodeEvent, VerifyCodeState> {
|
||||
final VerifyCodeUseCase _verifyCodeUseCase;
|
||||
Timer? _timer;
|
||||
|
||||
VerifyCodeBloc(this._verifyCodeUseCase)
|
||||
: super(VerifyCodeState.initial()) {
|
||||
on<VerifyCodeStarted>((event, emit) {
|
||||
emit(state.copyWith(
|
||||
phoneNumber: event.phoneNumber,
|
||||
tempToken: event.tempToken,
|
||||
secondsLeft: 60,
|
||||
));
|
||||
_startTimer();
|
||||
});
|
||||
|
||||
on<CodeChanged>((event, emit) {
|
||||
emit(state.copyWith(code: event.code));
|
||||
});
|
||||
|
||||
on<ResendCodePressed>((event, emit) {
|
||||
if (state.secondsLeft == 0) {
|
||||
emit(state.copyWith(secondsLeft: 60));
|
||||
_startTimer();
|
||||
}
|
||||
});
|
||||
|
||||
on<VerifyCodeSubmitted>(_onVerifyCodeSubmitted);
|
||||
}
|
||||
|
||||
void _startTimer() {
|
||||
_timer?.cancel();
|
||||
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (state.secondsLeft <= 1) {
|
||||
timer.cancel();
|
||||
emit(state.copyWith(secondsLeft: 0));
|
||||
} else {
|
||||
emit(state.copyWith(secondsLeft: state.secondsLeft - 1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _onVerifyCodeSubmitted(VerifyCodeSubmitted event,
|
||||
Emitter<VerifyCodeState> emit,) async {
|
||||
if (state.isSubmitting || state.code.length != 6) return;
|
||||
|
||||
emit(state.copyWith(isSubmitting: true, error: null));
|
||||
|
||||
final result = await _verifyCodeUseCase.execute(state.code, state.tempToken);
|
||||
|
||||
final newState = switch (result) {
|
||||
Success() => state.copyWith(
|
||||
isSubmitting: false,
|
||||
isSuccess: true,
|
||||
),
|
||||
Failure(failure: final f) => switch (f) {
|
||||
AuthFailure(attemptsLeft: final count) => state.copyWith(
|
||||
isSubmitting: false,
|
||||
attemptsLeft: count,
|
||||
error: 'Неверный код. Осталось попыток: $count',
|
||||
),
|
||||
AuthBlockFailure() => state.copyWith(
|
||||
isSubmitting: false,
|
||||
isBlocked: true,
|
||||
error: 'Вы заблокированы за слишком частые попытки',
|
||||
),
|
||||
_ => state.copyWith(
|
||||
isSubmitting: false,
|
||||
error: 'Произошла ошибка. Попробуйте позже',
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
emit(newState);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user