2023-11-01 20:36:16 +03:30
|
|
|
import 'package:flutter/foundation.dart';
|
2023-07-06 17:18:41 +03:30
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:go_router/go_router.dart';
|
2023-12-01 12:56:24 +03:30
|
|
|
import 'package:hiddify/core/preferences/general_preferences.dart';
|
2023-11-01 20:36:16 +03:30
|
|
|
import 'package:hiddify/core/router/routes.dart';
|
2024-01-05 15:21:10 +03:30
|
|
|
import 'package:hiddify/features/deep_link/notifier/deep_link_notifier.dart';
|
2023-09-30 11:15:32 +03:30
|
|
|
import 'package:hiddify/utils/utils.dart';
|
2023-07-06 17:18:41 +03:30
|
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
2023-09-18 14:01:44 +03:30
|
|
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
2023-07-06 17:18:41 +03:30
|
|
|
|
|
|
|
|
part 'app_router.g.dart';
|
|
|
|
|
|
2023-11-01 20:36:16 +03:30
|
|
|
bool _debugMobileRouter = false;
|
|
|
|
|
|
|
|
|
|
final useMobileRouter =
|
|
|
|
|
!PlatformUtils.isDesktop || (kDebugMode && _debugMobileRouter);
|
|
|
|
|
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
|
|
|
|
|
|
2023-07-06 17:18:41 +03:30
|
|
|
// TODO: test and improve handling of deep link
|
|
|
|
|
@riverpod
|
|
|
|
|
GoRouter router(RouterRef ref) {
|
2023-09-30 11:15:32 +03:30
|
|
|
final notifier = ref.watch(routerListenableProvider.notifier);
|
2023-07-06 17:18:41 +03:30
|
|
|
final deepLink = ref.listen(
|
2024-01-05 15:21:10 +03:30
|
|
|
deepLinkNotifierProvider,
|
2023-07-06 17:18:41 +03:30
|
|
|
(_, next) async {
|
|
|
|
|
if (next case AsyncData(value: final link?)) {
|
2023-07-26 14:17:11 +03:30
|
|
|
await ref.state.push(AddProfileRoute(url: link.url).location);
|
2023-07-06 17:18:41 +03:30
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
final initialLink = deepLink.read();
|
2023-07-28 00:03:01 +03:30
|
|
|
String initialLocation = const HomeRoute().location;
|
2023-07-06 17:18:41 +03:30
|
|
|
if (initialLink case AsyncData(value: final link?)) {
|
2023-07-26 14:17:11 +03:30
|
|
|
initialLocation = AddProfileRoute(url: link.url).location;
|
2023-07-06 17:18:41 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GoRouter(
|
|
|
|
|
navigatorKey: rootNavigatorKey,
|
|
|
|
|
initialLocation: initialLocation,
|
|
|
|
|
debugLogDiagnostics: true,
|
2023-11-26 21:59:57 +03:30
|
|
|
routes: [
|
|
|
|
|
if (useMobileRouter) $mobileWrapperRoute else $desktopWrapperRoute,
|
2023-12-09 15:32:06 +03:30
|
|
|
$introRoute,
|
2023-11-26 21:59:57 +03:30
|
|
|
],
|
2023-09-30 11:15:32 +03:30
|
|
|
refreshListenable: notifier,
|
|
|
|
|
redirect: notifier.redirect,
|
2023-09-18 14:01:44 +03:30
|
|
|
observers: [
|
|
|
|
|
SentryNavigatorObserver(),
|
|
|
|
|
],
|
2023-07-06 17:18:41 +03:30
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-11 13:47:08 +03:30
|
|
|
final tabLocations = [
|
|
|
|
|
const HomeRoute().location,
|
|
|
|
|
const ProxiesRoute().location,
|
|
|
|
|
const ConfigOptionsRoute().location,
|
|
|
|
|
const SettingsRoute().location,
|
|
|
|
|
const LogsOverviewRoute().location,
|
|
|
|
|
const AboutRoute().location,
|
|
|
|
|
];
|
|
|
|
|
|
2023-07-06 17:18:41 +03:30
|
|
|
int getCurrentIndex(BuildContext context) {
|
2023-08-20 15:34:19 +03:30
|
|
|
final String location = GoRouterState.of(context).uri.path;
|
2023-08-19 22:27:23 +03:30
|
|
|
if (location == const HomeRoute().location) return 0;
|
2024-02-11 13:47:08 +03:30
|
|
|
var index = 0;
|
|
|
|
|
for (final tab in tabLocations.sublist(1)) {
|
|
|
|
|
index++;
|
|
|
|
|
if (location.startsWith(tab)) return index;
|
|
|
|
|
}
|
2023-07-06 17:18:41 +03:30
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void switchTab(int index, BuildContext context) {
|
2024-02-11 13:47:08 +03:30
|
|
|
assert(index >= 0 && index < tabLocations.length);
|
|
|
|
|
final location = tabLocations[index];
|
|
|
|
|
return context.go(location);
|
2023-07-06 17:18:41 +03:30
|
|
|
}
|
2023-09-30 11:15:32 +03:30
|
|
|
|
|
|
|
|
@riverpod
|
|
|
|
|
class RouterListenable extends _$RouterListenable
|
|
|
|
|
with AppLogger
|
|
|
|
|
implements Listenable {
|
|
|
|
|
VoidCallback? _routerListener;
|
|
|
|
|
bool _introCompleted = false;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Future<void> build() async {
|
2024-03-02 22:53:14 +03:30
|
|
|
_introCompleted = ref.watch(Preferences.introCompleted);
|
2023-09-30 11:15:32 +03:30
|
|
|
|
|
|
|
|
ref.listenSelf((_, __) {
|
|
|
|
|
if (state.isLoading) return;
|
|
|
|
|
loggy.debug("triggering listener");
|
|
|
|
|
_routerListener?.call();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-01 12:56:24 +03:30
|
|
|
// ignore: avoid_build_context_in_providers
|
2023-09-30 11:15:32 +03:30
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|