567 lines
20 KiB
Dart
567 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/map_settings_sheet.dart';
|
|
import '../components/scooter_bottom_sheet.dart';
|
|
import '../components/sheet/current_rides_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>(),
|
|
)
|
|
..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();
|
|
}
|
|
}
|