import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:umbrix/core/preferences/general_preferences.dart'; import 'package:umbrix/core/router/routes.dart'; import 'package:umbrix/features/deep_link/notifier/deep_link_notifier.dart'; import 'package:umbrix/utils/utils.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; part 'app_router.g.dart'; bool _debugMobileRouter = false; final useMobileRouter = !PlatformUtils.isDesktop || (kDebugMode && _debugMobileRouter); final GlobalKey rootNavigatorKey = GlobalKey(); // TODO: test and improve handling of deep link @riverpod GoRouter router(RouterRef ref) { final notifier = ref.watch(routerListenableProvider.notifier); final deepLink = ref.listen( deepLinkNotifierProvider, (_, next) async { if (next case AsyncData(value: final link?)) { await ref.state.push(AddProfileRoute(url: link.url).location); } }, ); final initialLink = deepLink.read(); String initialLocation = const HomeRoute().location; if (initialLink case AsyncData(value: final link?)) { initialLocation = AddProfileRoute(url: link.url).location; } return GoRouter( navigatorKey: rootNavigatorKey, initialLocation: initialLocation, debugLogDiagnostics: true, routes: [ if (useMobileRouter) $mobileWrapperRoute else $desktopWrapperRoute, $introRoute, ], refreshListenable: notifier, redirect: notifier.redirect, observers: [ SentryNavigatorObserver(), ], ); } final tabLocations = [ const HomeRoute().location, // 0: Главная const ProxiesRoute().location, // 1: Локации const PerAppProxyRoute().location, // 2: Исключения const SettingsRoute().location, // 3: Настройки const AboutRoute().location, // 4: О программе ]; int getCurrentIndex(BuildContext context) { final String location = GoRouterState.of(context).uri.path; // Проверяем точное совпадение для главной if (location == const HomeRoute().location) return 0; // Проверяем остальные маршруты по порядку // ВАЖНО: более длинные пути проверяем раньше! if (location.startsWith(const PerAppProxyRoute().location)) return 2; // /settings/per-app-proxy if (location.startsWith(const ProxiesRoute().location)) return 1; // /proxies if (location.startsWith(const SettingsRoute().location)) return 3; // /settings if (location.startsWith(const AboutRoute().location)) return 4; // /about return 0; } void switchTab(int index, BuildContext context) { assert(index >= 0 && index < tabLocations.length); final location = tabLocations[index]; return context.go(location); } @riverpod class RouterListenable extends _$RouterListenable with AppLogger implements Listenable { VoidCallback? _routerListener; bool _introCompleted = false; @override Future build() async { _introCompleted = ref.watch(Preferences.introCompleted); ref.listenSelf((_, __) { if (state.isLoading) return; loggy.debug("triggering listener"); _routerListener?.call(); }); } // ignore: avoid_build_context_in_providers String? redirect(BuildContext context, GoRouterState state) { // if (this.state.isLoading || this.state.hasError) return null; final isIntro = state.uri.path == const IntroRoute().location; if (!_introCompleted) { return const IntroRoute().location; } else if (isIntro) { return const HomeRoute().location; } return null; } @override void addListener(VoidCallback listener) { _routerListener = listener; } @override void removeListener(VoidCallback listener) { _routerListener = null; } }