import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hiddify/core/prefs/prefs.dart'; import 'package:hiddify/core/router/routes.dart'; import 'package:hiddify/services/deep_link_service.dart'; import 'package:hiddify/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( deepLinkServiceProvider, (_, 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, ], refreshListenable: notifier, redirect: notifier.redirect, observers: [ SentryNavigatorObserver(), ], ); } int getCurrentIndex(BuildContext context) { final String location = GoRouterState.of(context).uri.path; if (location == const HomeRoute().location) return 0; if (location.startsWith(const ProxiesRoute().location)) return 1; if (location.startsWith(const LogsOverviewRoute().location)) return 2; if (location.startsWith(const SettingsRoute().location)) return 3; if (location.startsWith(const AboutRoute().location)) return 4; return 0; } void switchTab(int index, BuildContext context) { switch (index) { case 0: const HomeRoute().go(context); case 1: const ProxiesRoute().go(context); case 2: const LogsOverviewRoute().go(context); case 3: const SettingsRoute().go(context); case 4: const AboutRoute().go(context); } } @riverpod class RouterListenable extends _$RouterListenable with AppLogger implements Listenable { VoidCallback? _routerListener; bool _introCompleted = false; @override Future build() async { _introCompleted = ref.watch(introCompletedProvider); ref.listenSelf((_, __) { if (state.isLoading) return; loggy.debug("triggering listener"); _routerListener?.call(); }); } 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; } }