import 'dart:developer' as developer; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/routing/auth_routes.dart'; import 'package:eatvio_app/core/routing/content_routes.dart'; import 'package:eatvio_app/core/routing/main_navigation_routes.dart'; import 'package:eatvio_app/core/routing/other_routes.dart'; import 'package:eatvio_app/core/routing/route_constants.dart'; import 'package:eatvio_app/core/routing/settings_routes.dart'; import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart'; import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:go_router/go_router.dart'; /// Главный роутер приложения class AppRouter { static GlobalKey navigatorKey = GlobalKey(); static const List deepLinkHosts = ['eatvio.ru', 'eatvio.com', 'dev2.eatvio.com']; static const List metaSegments = ['ru', 'en', 'api', 'v2']; static GoRouter appRouter = GoRouter( initialLocation: RouteConstants.updateScreen, navigatorKey: navigatorKey, redirect: _handleRedirect, routes: [ ...AuthRoutes.createRoutes(), ...MainNavigationRoutes.createRoutes(), ...ContentRoutes.createRoutes(), ...SettingsRoutes.createRoutes(), ...OtherRoutes.createRoutes(), ], ); static String? _resolveRecipeDeepLink(List effectiveSegments) { if (effectiveSegments.length != 2) return null; final contentIdentifier = effectiveSegments[1]; developer.log('Deep link: recipe/$contentIdentifier', name: 'AppRouter'); return '/about-recipes-screen?slug=$contentIdentifier'; } static String? _resolveBlogPostDeepLink(List effectiveSegments) { if (effectiveSegments.length != 2) return null; final contentIdentifier = effectiveSegments[1]; developer.log('Deep link: blog_post/$contentIdentifier', name: 'AppRouter'); return '/blog-post-screen?id=$contentIdentifier'; } static String? _resolvePlanDeepLink(List effectiveSegments) { if (effectiveSegments.length != 2) return null; final contentIdentifier = effectiveSegments[1]; final planId = int.tryParse(contentIdentifier); if (planId != null) { developer.log('Deep link: diary', name: 'AppRouter'); return RouteConstants.diaryScreen; } developer.log('Deep link: plan/$contentIdentifier', name: 'AppRouter'); return '/about-plan-screen?slug=$contentIdentifier'; } static String? _resolveDietDeepLink(List effectiveSegments) { if (effectiveSegments.length == 2) { final contentIdentifier = effectiveSegments[1]; final dietId = int.tryParse(contentIdentifier); if (dietId != null) { developer.log('Deep link: diet/id=$dietId', name: 'AppRouter'); return '/about-diet-screen?id=$dietId'; } developer.log('Deep link: diet/slug=$contentIdentifier', name: 'AppRouter'); return '/about-diet-screen?slug=$contentIdentifier'; } const dietTabIndexWithHiddenPlansTab = 3; const dietTabIndexWithoutHiddenPlansTab = 4; final initialTabIndex = hidePlans ? dietTabIndexWithHiddenPlansTab : dietTabIndexWithoutHiddenPlansTab; return '${RouteConstants.feedScreen}?initialTabIndex=$initialTabIndex'; } static String? _resolveChatDeepLink(List effectiveSegments) { if (effectiveSegments.length == 2) { final contentIdentifier = effectiveSegments[1]; developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter'); return '${RouteConstants.chatScreen}?uuid=$contentIdentifier'; } else if (effectiveSegments.length == 3 && effectiveSegments[1] == 'show') { final contentIdentifier = effectiveSegments[2]; developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter'); return '${RouteConstants.chatScreen}?uuid=$contentIdentifier'; } developer.log('Deep link: chat', name: 'AppRouter'); return RouteConstants.chatSelectScreen; } static String? _resolveProfileDeepLink(List effectiveSegments) { if (effectiveSegments.length == 2) { final contentIdentifier = effectiveSegments[1]; developer.log('Deep link: profile/$contentIdentifier', name: 'AppRouter'); return '/profile-screen?slug=$contentIdentifier'; } return RouteConstants.myProfileScreen; } static String? _resolveBadgeDeepLink(List effectiveSegments) { return RouteConstants.myProfileScreen; } static String? _resolveDiaryDeepLink(List effectiveSegments) { developer.log('Deep link: diary', name: 'AppRouter'); return RouteConstants.diaryScreen; } static String? _resolveConsultantDiaryCommentDeepLink(List effectiveSegments) { if (effectiveSegments.length != 2) return null; final contentIdentifier = effectiveSegments[1]; developer.log('Deep link: consultant_diary_comment/$contentIdentifier → diary', name: 'AppRouter'); return RouteConstants.diaryScreen; } static String? _resolveConsultationsDeepLink(List effectiveSegments) { if (effectiveSegments.length != 3 || effectiveSegments[1] != 'status') return RouteConstants.consultationsScreen; final consultationId = effectiveSegments[2]; developer.log('Deep link: consultations/status/$consultationId', name: 'AppRouter'); return '${InfoByOrderScreen.path}?planId=$consultationId'; } /// Deep link после оплаты Alfa: `finance/alfa/return/{orderId}` → чат с query `orderId`. static String? _resolveFinanceAlfaReturnDeepLink(List effectiveSegments) { if (effectiveSegments.length != 4 || effectiveSegments[1] != 'alfa' || effectiveSegments[2] != 'return') { return null; } final orderId = effectiveSegments[3]; if (orderId.isEmpty) return null; developer.log('Deep link: finance/alfa/return/$orderId', name: 'AppRouter'); return '${RouteConstants.chatScreen}?orderId=$orderId'; } /// Разбирает deep link URI в внутренний route target (или null, если host не наш). static String? resolveDeepLinkTarget(Uri uri) { final host = uri.host.toLowerCase(); final segments = uri.pathSegments; final isDeepLink = deepLinkHosts.any((allowedHost) => host == allowedHost || host.endsWith('.$allowedHost')); if (!isDeepLink) { return null; } developer.log('Deep link detected: host=$host, path=${uri.path}, segments=$segments', name: 'AppRouter'); if (segments.isEmpty) { developer.log('Empty segments in deep link, redirecting to feed', name: 'AppRouter'); return RouteConstants.feedScreen; } final effectiveSegments = segments.where((segment) => !metaSegments.contains(segment)).toList(); if (effectiveSegments.isEmpty) { developer.log('No effective segments after language check, redirecting to feed', name: 'AppRouter'); return RouteConstants.feedScreen; } final type = effectiveSegments[0]; switch (type) { case 'recipe': final target = _resolveRecipeDeepLink(effectiveSegments); if (target != null) return target; break; case 'blog_post': final target = _resolveBlogPostDeepLink(effectiveSegments); if (target != null) return target; break; case 'plan': final target = _resolvePlanDeepLink(effectiveSegments); if (target != null) return target; break; case 'diet': final target = _resolveDietDeepLink(effectiveSegments); if (target != null) return target; break; case 'chat': final target = _resolveChatDeepLink(effectiveSegments); if (target != null) return target; break; case 'profile': final target = _resolveProfileDeepLink(effectiveSegments); if (target != null) return target; break; case 'badge': final target = _resolveBadgeDeepLink(effectiveSegments); if (target != null) return target; break; case 'diary': final target = _resolveDiaryDeepLink(effectiveSegments); if (target != null) return target; break; case 'consultant_diary_comment': final target = _resolveConsultantDiaryCommentDeepLink(effectiveSegments); if (target != null) return target; break; case 'consultations': final target = _resolveConsultationsDeepLink(effectiveSegments); if (target != null) return target; break; case 'finance': final target = _resolveFinanceAlfaReturnDeepLink(effectiveSegments); if (target != null) return target; break; } developer.log('Unknown deep link format: ${effectiveSegments.length} effective segments, type=$type, redirecting to feed', name: 'AppRouter'); return RouteConstants.feedScreen; } static String? _handleRedirect(BuildContext context, GoRouterState state) { return resolveDeepLinkTarget(state.uri); } static void navigateByDeepLinkUrl(String url) { if (url.isEmpty) { developer.log('navigateByDeepLinkUrl: empty url, redirecting to feed', name: 'AppRouter'); appRouter.go(RouteConstants.feedScreen); return; } Uri? uri; try { uri = Uri.tryParse(url); } catch (e) { developer.log('navigateByDeepLinkUrl: failed to parse url=$url, error=$e, redirecting to feed', name: 'AppRouter'); appRouter.go(RouteConstants.feedScreen); return; } if (uri == null) { developer.log('navigateByDeepLinkUrl: uri is null for url=$url, redirecting to feed', name: 'AppRouter'); appRouter.go(RouteConstants.feedScreen); return; } if (uri.host.isEmpty && uri.pathSegments.isNotEmpty) { uri = Uri( scheme: 'https', host: deepLinkHosts.first, pathSegments: uri.pathSegments, queryParameters: uri.queryParameters.isEmpty ? null : uri.queryParameters, ); } final target = resolveDeepLinkTarget(uri); FlutterNativeSplash.remove(); if (target != null) { appRouter.go(target); return; } developer.log('navigateByDeepLinkUrl: unresolved url=$url (normalized=${uri.toString()}), redirecting to feed', name: 'AppRouter'); appRouter.go(RouteConstants.feedScreen); } static void navigateToRegisterViaSocial({required AuthResultEntity authResult, required String provider}) { appRouter.pushNamed('registerViaSocialScreen', extra: {'authResult': authResult, 'provider': provider}); } }