forked from eatvio/eatvio_app
265 lines
10 KiB
Dart
265 lines
10 KiB
Dart
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<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||
|
||
static const List<String> deepLinkHosts = ['eatvio.ru', 'eatvio.com', 'dev2.eatvio.com'];
|
||
static const List<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> effectiveSegments) {
|
||
return RouteConstants.myProfileScreen;
|
||
}
|
||
|
||
static String? _resolveDiaryDeepLink(List<String> effectiveSegments) {
|
||
developer.log('Deep link: diary', name: 'AppRouter');
|
||
return RouteConstants.diaryScreen;
|
||
}
|
||
|
||
static String? _resolveConsultantDiaryCommentDeepLink(List<String> 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<String> 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<String> 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});
|
||
}
|
||
}
|