Files
be_happy_public/lib/presentation/navigation/app_router.dart
2026-05-29 11:40:55 +03:00

568 lines
20 KiB
Dart

import 'dart:async';
import 'package:bot_toast/bot_toast.dart';
import 'package:be_happy/core/app_colors.dart';
import 'package:be_happy/di/service_locator.dart';
import 'package:be_happy/domain/entities/user_profile.dart';
import 'package:be_happy/domain/usecase/get_available_subscriptions_usecase.dart';
import 'package:be_happy/domain/usecase/get_certificates_usecase.dart';
import 'package:be_happy/domain/usecase/get_client_subscriptions_usecase.dart';
import 'package:be_happy/domain/usecase/get_profile_usecase.dart';
import 'package:be_happy/domain/usecase/get_scooter_by_title_usecase.dart';
import 'package:be_happy/domain/usecase/get_subscription_by_id_usecase.dart';
import 'package:be_happy/domain/usecase/is_pin_set_usecase.dart';
import 'package:be_happy/domain/usecase/purchase_certificate_usecase.dart';
import 'package:be_happy/presentation/event/payment_confirm_event.dart';
import 'package:be_happy/presentation/event/pin_event.dart';
import 'package:be_happy/presentation/event/subscription_list_event.dart';
import 'package:be_happy/presentation/screens/block_screen.dart';
import 'package:be_happy/presentation/screens/documents_screen.dart';
import 'package:be_happy/presentation/screens/edit_profile_screen.dart';
import 'package:be_happy/presentation/screens/map_screen.dart';
import 'package:be_happy/presentation/screens/news_screen.dart';
import 'package:be_happy/presentation/screens/onboarding_screen.dart';
import 'package:be_happy/presentation/screens/order_history_detail_screen.dart';
import 'package:be_happy/presentation/screens/payment_confirm_screen.dart';
import 'package:be_happy/presentation/screens/profile_screen.dart';
import 'package:be_happy/presentation/screens/promo_code_screen.dart';
import 'package:be_happy/presentation/screens/qr_scan_info_screen.dart';
import 'package:be_happy/presentation/screens/scooter_code_input_screen.dart';
import 'package:be_happy/presentation/screens/scooter_detail_screen.dart';
import 'package:be_happy/presentation/screens/send_photo_screen.dart';
import 'package:be_happy/presentation/screens/subscription_list_screen.dart';
import 'package:be_happy/presentation/screens/support_screen.dart';
import 'package:be_happy/presentation/screens/top_up_screen.dart';
import 'package:be_happy/presentation/viewmodel/splash_bloc.dart';
import 'package:be_happy/presentation/viewmodel/susbcription_details_bloc.dart';
import 'package:be_happy/presentation/viewmodel/top_up_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:geolocator/geolocator.dart';
import 'package:go_router/go_router.dart';
import 'package:path/path.dart';
import '../../domain/entities/scooter.dart';
import '../../domain/usecase/activate_subscription_usecase.dart';
import '../../domain/usecase/book_scooter_usecase.dart';
import '../../domain/usecase/create_pin_usecase.dart';
import '../../domain/usecase/get_address_by_point_usecase.dart';
import '../../domain/usecase/get_available_tariffs_usecase.dart';
import '../../domain/usecase/get_client_orders_usecase.dart';
import '../../domain/usecase/get_map_settings_usecase.dart';
import '../../domain/usecase/get_payment_cards_usecase.dart';
import '../../domain/usecase/get_pedestrian_routes_usecase.dart';
import '../../domain/usecase/get_scooter_order_by_id_usecase.dart';
import '../../domain/usecase/get_scooter_usecase.dart';
import '../../domain/usecase/pay_ride_usecase.dart';
import '../../domain/usecase/remove_payment_card_usecase.dart';
import '../../domain/usecase/save_map_settings_usecase.dart';
import '../../domain/usecase/set_main_payment_card_usecase.dart';
import '../../domain/usecase/verify_pin_usecase.dart';
import '../components/scooter_bottom_sheet.dart';
import '../components/sheet/current_rides_sheet.dart';
import '../components/sheet/map_settings_sheet.dart';
import '../components/sheet/payment_method_sheet.dart';
import '../components/sheet/reserved_ride_sheet.dart';
import '../components/sheet/tariff_sheet.dart';
import '../event/current_rides_event.dart';
import '../event/edit_profile_event.dart';
import '../event/map_settings_modal_event.dart';
import '../event/payment_methods_event.dart';
import '../event/profile_event.dart';
import '../event/scooter_detail_modal_event.dart';
import '../event/subscription_details_event.dart';
import '../event/tariff_sheet_event.dart';
import '../event/top_up_event.dart';
import '../screens/add_card_screen.dart'; // ← новый импорт
import '../screens/license_agreement_screen.dart';
import '../screens/notifications_screen.dart';
import '../screens/order_history_screen.dart';
import '../screens/payment_methods_screen.dart';
import '../screens/phone_login_screen.dart';
import '../screens/phone_screen.dart';
import '../screens/pin_login_screen.dart';
import '../screens/privacy_policy_screen.dart';
import '../screens/qr_scan_screen.dart';
import '../screens/splash_screen.dart';
import '../screens/subscription_details_screen.dart';
import '../state/splash_state.dart';
import '../viewmodel/add_card_bloc.dart';
import '../viewmodel/current_rides_bloc.dart';
import '../viewmodel/edit_profile_bloc.dart';
import '../viewmodel/map_settings_modal_bloc.dart';
import '../viewmodel/payment_confirm_bloc.dart';
import '../viewmodel/payment_method_sheet_bloc.dart';
import '../viewmodel/payment_methods_bloc.dart';
import '../viewmodel/pin_bloc.dart';
import '../viewmodel/profile_bloc.dart';
import '../viewmodel/scooter_code_bloc.dart';
import '../viewmodel/scooter_detail_bloc.dart';
import '../viewmodel/scooter_detail_modal_bloc.dart';
import '../viewmodel/subscription_list_bloc.dart';
import '../viewmodel/tariff_sheet_bloc.dart'; // ← новый импорт
class AppRouter {
final SplashBloc splashBloc;
AppRouter(this.splashBloc);
late final GoRouter router = GoRouter(
debugLogDiagnostics: true,
initialLocation: '/splash',
routes: [
GoRoute(
path: '/splash',
builder: (context, state) => const SplashScreen(),
),
GoRoute(path: '/login', builder: (context, state) => const PhoneScreen()),
GoRoute(
path: '/verify',
builder: (context, state) {
final phone = state.uri.queryParameters['phone'];
if (phone != null) {
return PhoneLoginScreen(phoneNumber: phone, tempToken: '');
}
throw Exception("Incorrect phone");
},
),
GoRoute(
path: '/block',
builder: (context, state) => const BlockedScreen(),
),
GoRoute(
path: '/pin',
builder: (context, state) =>
BlocProvider(
create: (context) =>
PinBloc(
createPinUseCase: getIt<CreatePinUseCase>(),
verifyPinUsecase: getIt<VerifyPinUseCase>(),
isPinSetUsecase: getIt<IsPinSetUsecase>(),
)..add(PinScreenStarted()),
child: const PinLoginScreen(),
)
),
GoRoute(
path: '/privacy-policy',
builder: (context, state) => const PrivacyPolicyScreen(),
),
GoRoute(
path: '/license-agreement',
builder: (context, state) => const LicenseAgreementScreen(),
),
GoRoute(
path: '/home',
builder: (context, state) => const MapScreen(),
routes: [
//Modal Bottom Sheets
GoRoute(
path: 'scooter-sheet',
pageBuilder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final scooters = data['scooters'] as List<Scooter>;
final location = data['currentLocation'] as Position;
return modalPage(
state: state,
child: Material(
type: MaterialType.transparency,
child: BlocProvider(
create: (context) =>
ScooterDetailModalBloc(
getIt<GetAddressByPointUsecase>(),
getIt<GetScooterUsecase>(),
getIt<GetPedestrianRoutesUsecase>(),
)
..add(
ScooterDetailModalStarted(
scooters,
location.latitude,
location.longitude,
),
),
child: ScooterBottomSheet(),
),
),
);
},
),
GoRoute(
path: 'tarif-sheet',
pageBuilder: (context, state) {
final scooter = state.extra as Scooter;
return modalPage(
state: state,
child: Material(
type: MaterialType.transparency,
child: BlocProvider(
create: (context) =>
TariffSheetBloc(
getIt<GetAvailableTariffsUsecase>(),
getIt<GetProfileUseCase>(),
getIt<GetPaymentCardsUsecase>(),
getIt<BookScooterUsecase>(),
),
child: TariffSheet(scooter: scooter),
),
),
);
},
),
GoRoute(
path: 'current-rides-sheet',
pageBuilder: (context, state) {
return modalPage(
state: state,
child: Material(
type: MaterialType.transparency,
child: BlocProvider(
create: (context) =>
CurrentRidesBloc(getIt<GetClientOrdersUsecase>())
..add(LoadClientOrders()),
child: CurrentRidesSheet(),
),
),
);
},
),
GoRoute(
path: 'payment-method-sheet',
pageBuilder: (context, state) {
return modalPage(
state: state,
child: Material(
type: MaterialType.transparency,
child: BlocProvider(
create: (context) =>
PaymentMethodSheetBloc(getIt<GetPaymentCardsUsecase>()),
child: PaymentMethodSheet(),
),
),
);
},
),
GoRoute(
path: 'map-settings-sheet',
pageBuilder: (context, state) {
return modalPage(
state: state,
child: Material(
type: MaterialType.transparency,
child: BlocProvider(
create: (context) =>
MapSettingsModalBloc(
getIt<GetMapSettingsUsecase>(),
getIt<SaveMapSettingsUsecase>(),
)
..add(MapSettingsModalStarted()),
child: MapSettingsSheet(),
),
),
);
},
),
//Sub screens
GoRoute(
path: 'profile',
builder: (context, state) =>
BlocProvider(
create: (context) =>
getIt<ProfileBloc>()
..add(ProfileStarted()),
child: ProfileScreen(),
),
routes: [
GoRoute(
path: 'edit',
builder: (context, state) =>
BlocProvider(
create: (context) =>
getIt<EditProfileBloc>()
..add(EditProfileStarted()),
child: EditProfileScreen(
profile: UserProfile(
name: '',
birthDate: '',
phone: '',
balance: 23,
email: '',
),
),
),
),
],
),
GoRoute(
path: 'scooter/:id',
builder: (context, state) =>
BlocProvider(
create: (context) => getIt<ScooterDetailBloc>(),
child: ScooterDetailScreen(),
),
),
GoRoute(
path: 'order-photos/:orderId',
builder: (context, state) {
int orderId = int.parse(state.pathParameters['orderId']!);
return SendPhotoScreen(orderId: orderId);
},
),
GoRoute(
path: 'checkout/:orderId',
builder: (context, state) {
int orderId = int.parse(state.pathParameters['orderId']!);
List<int> photoIds = [];
if (state.extra != null) {
photoIds = state.extra as List<int>;
}
return BlocProvider(
create: (context) =>
PaymentConfirmBloc(
getIt<PayRideUsecase>(),
getIt<GetScooterOrderByIdUsecase>(),
getIt<GetPaymentCardsUsecase>(),
getIt<GetProfileUseCase>(),
)
..add(PaymentConfirmStarted(orderId)),
child: PaymentConfirmScreen(
orderId: orderId,
photoIds: photoIds,
),
);
},
),
GoRoute(
path: 'support',
builder: (context, state) => const SupportScreen(),
),
GoRoute(
path: 'documents',
builder: (context, state) => const DocumentsScreen(),
),
GoRoute(
path: 'promo',
builder: (context, state) => const PromoCodeScreen(),
),
GoRoute(
path: 'subscriptions',
builder: (context, state) {
return BlocProvider(
create: (context) =>
SubscriptionListBloc(
getAvailableSubscriptionsUsecase:
getIt<GetAvailableSubscriptionsUsecase>(),
getClientSubscriptionsUsecase:
getIt<GetClientSubscriptionsUsecase>(),
)
..add(LoadSubscriptionsEvent()),
child: SubscriptionsListScreen(),
);
},
routes: [
GoRoute(
path: ':id',
builder: (context, state) {
return BlocProvider(
create: (context) =>
SubscriptionDetailsBloc(
getIt<GetSubscriptionByIdUsecase>(),
getIt<ActivateSubscriptionUsecase>(),
getIt<GetClientSubscriptionsUsecase>(),
)
..add(
LoadDetailsEvent(
int.parse(state.pathParameters['id']!),
),
),
child: SubscriptionDetailsScreen(subscriptionId: 1),
);
},
),
],
),
GoRoute(
path: 'rules',
builder: (context, state) => const OnboardingScreen(),
),
GoRoute(
path: 'news',
builder: (context, state) => const NewsScreen(),
),
GoRoute(
path: 'qr-info',
builder: (context, state) => const QRScanInfoScreen(),
routes: [
GoRoute(
path: 'qr-scan',
builder: (context, state) => const QrScanScreen(),
),
GoRoute(
path: 'qr-input',
builder: (context, state) => BlocProvider(
create: (context) =>
ScooterCodeBloc(
getScooterByTitleUsecase: getIt<GetScooterByTitleUsecase>(),
),
child: ScooterCodeInputScreen(),
),
),
]
),
GoRoute(
path: 'payment-methods',
builder: (context, state) =>
BlocProvider(
create: (context) =>
PaymentMethodsBloc(
getIt<GetPaymentCardsUsecase>(),
getIt<RemovePaymentCardUsecase>(),
getIt<SetMainPaymentCardUsecase>(),
getIt<GetProfileUseCase>(),
)
..add(PaymentMethodsStarted()),
child: PaymentMethodsScreen(),
),
routes: [
GoRoute(
path: 'add-card',
builder: (context, state) =>
BlocProvider(
create: (context) => getIt<AddCardBloc>(),
child: const AddCardScreen(),
),
),
GoRoute(
path: 'top-up',
builder: (context, state) =>
BlocProvider(
create: (context) =>
TopUpBloc(
getCertificatesUsecase: getIt<GetCertificatesUsecase>(),
purchaseCertificateUsecase:
getIt<PurchaseCertificateUsecase>(),
getUserCards: getIt<GetPaymentCardsUsecase>(),
)
..add(LoadTopUpData()),
child: TopUpScreen(),
),
),
],
),
GoRoute(
path: 'order-history',
builder: (context, state) => const OrderHistoryScreen(),
routes: []
),
GoRoute(
path: 'notifications',
builder: (context, state) => const NotificationsScreen(),
),
],
),
],
observers: [BotToastNavigatorObserver()],
redirect: (BuildContext context, GoRouterState state) {
final authState = splashBloc.state;
final currentLocation = state.uri.toString();
print("inside redirect");
print(splashBloc.state);
print(state.uri.toString());
if (authState is AuthInitial && currentLocation != '/splash') {
print("splash");
return '/splash';
}
if (authState is AuthFirstLaunch &&
currentLocation != '/login' &&
currentLocation != '/privacy-policy' &&
currentLocation != '/license-agreement') {
print("login");
return '/login';
}
if (authState is AuthUnauthenticated &&
currentLocation != '/login' &&
currentLocation != '/privacy-policy' &&
currentLocation != '/license-agreement') {
print("login2");
return '/login';
}
if (authState is AuthAuthenticated) {
final isComingFromStart =
currentLocation == '/splash' ||
currentLocation == '/login' ||
currentLocation == '/phone';
if (isComingFromStart) {
print("redirecting to pin check");
return '/pin';
}
}
if (authState is AuthPinVerified) {
if (currentLocation == '/splash' ||
currentLocation == '/login' ||
currentLocation == '/pin') {
return '/home';
}
}
return null;
},
refreshListenable: GoRouterRefreshStream(splashBloc.stream),
);
CustomTransitionPage modalPage({
required GoRouterState state,
required Widget child,
}) {
return CustomTransitionPage(
key: state.pageKey,
opaque: false,
barrierDismissible: true,
barrierColor: Colors.black54,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return SlideTransition(
position: animation.drive(
Tween(
begin: const Offset(0, 1),
end: Offset.zero,
).chain(CurveTween(curve: Curves.easeOutCubic)),
),
child: child,
);
},
child: child,
);
}
}
class GoRouterRefreshStream extends ChangeNotifier {
late final StreamSubscription<dynamic> _subscription;
GoRouterRefreshStream(Stream<dynamic> stream) {
notifyListeners();
_subscription = stream.asBroadcastStream().listen((dynamic _) {
print("Stream updated");
notifyListeners();
});
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
}