feat: Introduce AppRouter for centralized application routing and deep link handling, and create push_navigation_handler for push notification navigation.

This commit is contained in:
mso7nueh
2026-03-17 16:33:54 +03:00
parent decc8c63e6
commit 3d7755a9a2
2 changed files with 76 additions and 8 deletions

View File

@@ -15,7 +15,7 @@ class AppRouter {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// Домены для deep links (в т.ч. ссылки из пушей dev2.eatvio.com)
static const List<String> _deepLinkHosts = ['eatvio.ru', 'www.eatvio.ru'];
static const List<String> deepLinkHosts = ['eatvio.ru', 'eatvio.com'];
static GoRouter appRouter = GoRouter(
initialLocation: RouteConstants.updateScreen,
@@ -34,7 +34,8 @@ class AppRouter {
final host = uri.host.toLowerCase();
final segments = uri.pathSegments;
final isDeepLink = _deepLinkHosts.contains(host);
// Проверяем, что хост заканчивается на один из разрешенных доменов
final isDeepLink = deepLinkHosts.any((allowedHost) => host == allowedHost || host.endsWith('.$allowedHost'));
if (!isDeepLink) {
return null;
@@ -190,7 +191,7 @@ class AppRouter {
if (uri.host.isEmpty && uri.pathSegments.isNotEmpty) {
uri = Uri(
scheme: 'https',
host: _deepLinkHosts.first,
host: deepLinkHosts.first,
pathSegments: uri.pathSegments,
queryParameters: uri.queryParameters.isEmpty ? null : uri.queryParameters,
);

View File

@@ -1,6 +1,7 @@
import 'dart:developer' as developer;
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
@@ -70,22 +71,88 @@ class PushNavigationHandler {
static void _handleMessageInternal(RemoteMessage message, {required String source}) {
final data = message.data;
final actionUrl = data['action_url'] as String?;
// Пытаемся взять URL из action_url или action (бэкенд шлёт и то и другое)
String? rawUrl = data['action_url'] as String?;
if (rawUrl == null || rawUrl.isEmpty) {
rawUrl = data['action'] as String?;
}
if (actionUrl == null || actionUrl.isEmpty) {
if (rawUrl != null && rawUrl.isNotEmpty) {
developer.log(
'PushNavigationHandler: no action_url in message data, source=$source, data=$data',
'PushNavigationHandler: navigating by url=$rawUrl, source=$source',
name: 'PushNavigationHandler',
);
// Если это URL, но без схемы, добавляем https
String finalUrl = rawUrl;
if (!finalUrl.startsWith('http') && finalUrl.contains('/')) {
finalUrl = 'https://${AppRouter.deepLinkHosts.first}/$finalUrl'.replaceAll('//', '/').replaceFirst('https:/', 'https://');
}
AppRouter.navigateByDeepLinkUrl(finalUrl);
return;
}
// Fallback: навигация по типу уведомления, если URL отсутствует.
final target = _resolveTargetByType(data);
if (target != null) {
developer.log(
'PushNavigationHandler: fallback navigation by type, target=$target, source=$source, data=$data',
name: 'PushNavigationHandler',
);
AppRouter.appRouter.go(target);
return;
}
developer.log(
'PushNavigationHandler: navigating by action_url=$actionUrl, source=$source',
'PushNavigationHandler: no action_url/action and no known type, source=$source, data=$data',
name: 'PushNavigationHandler',
);
}
AppRouter.navigateByDeepLinkUrl(actionUrl);
/// Определяет маршрут по полям `type` / `action` из data, если URL отсутствует.
static String? _resolveTargetByType(Map<String, dynamic> data) {
final type = data['type'] as String? ?? '';
final action = data['action'] as String? ?? '';
final databaseType = data['database_type'] as String? ?? '';
// Чат
if (type == 'chat_message' || databaseType == 'chat-message') {
return RouteConstants.chatSelectScreen;
}
// Дневник
if (type == 'diary_reminder' || action == 'open_diary' || databaseType == 'diary-reminder') {
return RouteConstants.diaryScreen;
}
// Комментарий консультанта к дневнику
if (type == 'consultant_diary_comment' || databaseType == 'consultant-left-diary-comment') {
return RouteConstants.diaryScreen;
}
// Консультации (plan-related)
if (type.startsWith('plan_') || type.startsWith('chosen_plan_') || databaseType.contains('plan')) {
return RouteConstants.consultationsScreen;
}
// Диета
if (type == 'diet_started' || type == 'diet_cancelled' || databaseType.startsWith('diet-')) {
return RouteConstants.feedScreen;
}
// Бейдж
if (type == 'badge_awarded' || databaseType == 'badge-awarded') {
return RouteConstants.myProfileScreen;
}
// Мотивация
if (type == 'motivation') {
return RouteConstants.feedScreen;
}
return null;
}
}