nav.dart 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_spinkit/flutter_spinkit.dart';
  4. import 'package:go_router/go_router.dart';
  5. import 'package:page_transition/page_transition.dart';
  6. import 'package:provider/provider.dart';
  7. import '/backend/schema/structs/index.dart';
  8. import '/auth/custom_auth/custom_auth_user_provider.dart';
  9. import '/main.dart';
  10. import '/flutter_flow/flutter_flow_theme.dart';
  11. import '/flutter_flow/lat_lng.dart';
  12. import '/flutter_flow/place.dart';
  13. import '/flutter_flow/flutter_flow_util.dart';
  14. import 'serialization_util.dart';
  15. import '/index.dart';
  16. export 'package:go_router/go_router.dart';
  17. export 'serialization_util.dart';
  18. const kTransitionInfoKey = '__transition_info__';
  19. GlobalKey<NavigatorState> appNavigatorKey = GlobalKey<NavigatorState>();
  20. const debugRouteLinkMap = {
  21. '/login':
  22. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=login',
  23. '/horecagelegenheidCurrent':
  24. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=horecagelegenheidCurrent',
  25. '/home':
  26. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=home',
  27. '/selectprovinciegemeente':
  28. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=selectprovinciegemeente',
  29. '/pUitgaanPage':
  30. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=PUitgaanPage',
  31. '/event':
  32. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=Event',
  33. '/eventCurrent':
  34. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=EventCurrent',
  35. '/horecagelegenhedenOverzicht':
  36. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=HorecagelegenhedenOverzicht',
  37. '/horecagelegenhedenOverzichtPageDataType':
  38. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=HorecagelegenhedenOverzichtPageDataType',
  39. '/horecagelegenhedenOverzichtSortPage':
  40. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=HorecagelegenhedenOverzichtSortPage',
  41. '/wachtwoordVergeten':
  42. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=wachtwoordVergeten',
  43. '/favorieten':
  44. 'https://app.flutterflow.io/project/uitgaanskrant-1qhvtd?tab=uiBuilder&page=favorieten'
  45. };
  46. class AppStateNotifier extends ChangeNotifier {
  47. AppStateNotifier._();
  48. static AppStateNotifier? _instance;
  49. static AppStateNotifier get instance => _instance ??= AppStateNotifier._();
  50. UitgaanskrantAuthUser? initialUser;
  51. UitgaanskrantAuthUser? user;
  52. bool showSplashImage = true;
  53. String? _redirectLocation;
  54. /// Determines whether the app will refresh and build again when a sign
  55. /// in or sign out happens. This is useful when the app is launched or
  56. /// on an unexpected logout. However, this must be turned off when we
  57. /// intend to sign in/out and then navigate or perform any actions after.
  58. /// Otherwise, this will trigger a refresh and interrupt the action(s).
  59. bool notifyOnAuthChange = true;
  60. bool get loading => user == null || showSplashImage;
  61. bool get loggedIn => user?.loggedIn ?? false;
  62. bool get initiallyLoggedIn => initialUser?.loggedIn ?? false;
  63. bool get shouldRedirect => loggedIn && _redirectLocation != null;
  64. String getRedirectLocation() => _redirectLocation!;
  65. bool hasRedirect() => _redirectLocation != null;
  66. void setRedirectLocationIfUnset(String loc) => _redirectLocation ??= loc;
  67. void clearRedirectLocation() => _redirectLocation = null;
  68. /// Mark as not needing to notify on a sign in / out when we intend
  69. /// to perform subsequent actions (such as navigation) afterwards.
  70. void updateNotifyOnAuthChange(bool notify) => notifyOnAuthChange = notify;
  71. void update(UitgaanskrantAuthUser newUser) {
  72. final shouldUpdate =
  73. user?.uid == null || newUser.uid == null || user?.uid != newUser.uid;
  74. initialUser ??= newUser;
  75. user = newUser;
  76. // Refresh the app on auth change unless explicitly marked otherwise.
  77. // No need to update unless the user has changed.
  78. if (notifyOnAuthChange && shouldUpdate) {
  79. notifyListeners();
  80. }
  81. // Once again mark the notifier as needing to update on auth change
  82. // (in order to catch sign in / out events).
  83. updateNotifyOnAuthChange(true);
  84. }
  85. void stopShowingSplashImage() {
  86. showSplashImage = false;
  87. notifyListeners();
  88. }
  89. }
  90. GoRouter createRouter(AppStateNotifier appStateNotifier) => GoRouter(
  91. initialLocation: '/',
  92. debugLogDiagnostics: true,
  93. refreshListenable: appStateNotifier,
  94. navigatorKey: appNavigatorKey,
  95. errorBuilder: (context, state) => appStateNotifier.loggedIn
  96. ? SelectprovinciegemeenteWidget()
  97. : HomeWidget(),
  98. routes: [
  99. FFRoute(
  100. name: '_initialize',
  101. path: '/',
  102. builder: (context, _) => appStateNotifier.loggedIn
  103. ? SelectprovinciegemeenteWidget()
  104. : HomeWidget(),
  105. ),
  106. FFRoute(
  107. name: LoginWidget.routeName,
  108. path: LoginWidget.routePath,
  109. builder: (context, params) => LoginWidget(),
  110. ),
  111. FFRoute(
  112. name: HorecagelegenheidCurrentWidget.routeName,
  113. path: HorecagelegenheidCurrentWidget.routePath,
  114. builder: (context, params) => HorecagelegenheidCurrentWidget(
  115. nid: params.getParam(
  116. 'nid',
  117. ParamType.String,
  118. ),
  119. ),
  120. ),
  121. FFRoute(
  122. name: HomeWidget.routeName,
  123. path: HomeWidget.routePath,
  124. builder: (context, params) => HomeWidget(),
  125. ),
  126. FFRoute(
  127. name: SelectprovinciegemeenteWidget.routeName,
  128. path: SelectprovinciegemeenteWidget.routePath,
  129. builder: (context, params) => SelectprovinciegemeenteWidget(),
  130. ),
  131. FFRoute(
  132. name: PUitgaanPageWidget.routeName,
  133. path: PUitgaanPageWidget.routePath,
  134. builder: (context, params) => PUitgaanPageWidget(
  135. plaats: params.getParam(
  136. 'plaats',
  137. ParamType.String,
  138. ),
  139. services: params.getParam(
  140. 'services',
  141. ParamType.String,
  142. ),
  143. ),
  144. ),
  145. FFRoute(
  146. name: EventWidget.routeName,
  147. path: EventWidget.routePath,
  148. builder: (context, params) => EventWidget(
  149. nid: params.getParam(
  150. 'nid',
  151. ParamType.String,
  152. ),
  153. ),
  154. ),
  155. FFRoute(
  156. name: EventCurrentWidget.routeName,
  157. path: EventCurrentWidget.routePath,
  158. builder: (context, params) => EventCurrentWidget(
  159. nid: params.getParam(
  160. 'nid',
  161. ParamType.String,
  162. ),
  163. horecaid: params.getParam(
  164. 'horecaid',
  165. ParamType.String,
  166. ),
  167. ),
  168. ),
  169. FFRoute(
  170. name: HorecagelegenhedenOverzichtWidget.routeName,
  171. path: HorecagelegenhedenOverzichtWidget.routePath,
  172. builder: (context, params) => HorecagelegenhedenOverzichtWidget(
  173. plaats: params.getParam(
  174. 'plaats',
  175. ParamType.String,
  176. ),
  177. ),
  178. ),
  179. FFRoute(
  180. name: HorecagelegenhedenOverzichtPageDataTypeWidget.routeName,
  181. path: HorecagelegenhedenOverzichtPageDataTypeWidget.routePath,
  182. builder: (context, params) =>
  183. HorecagelegenhedenOverzichtPageDataTypeWidget(
  184. plaats: params.getParam(
  185. 'plaats',
  186. ParamType.String,
  187. ),
  188. ),
  189. ),
  190. FFRoute(
  191. name: HorecagelegenhedenOverzichtSortPageWidget.routeName,
  192. path: HorecagelegenhedenOverzichtSortPageWidget.routePath,
  193. builder: (context, params) =>
  194. HorecagelegenhedenOverzichtSortPageWidget(
  195. plaats: params.getParam(
  196. 'plaats',
  197. ParamType.String,
  198. ),
  199. ),
  200. ),
  201. FFRoute(
  202. name: WachtwoordVergetenWidget.routeName,
  203. path: WachtwoordVergetenWidget.routePath,
  204. builder: (context, params) => WachtwoordVergetenWidget(),
  205. ),
  206. FFRoute(
  207. name: FavorietenWidget.routeName,
  208. path: FavorietenWidget.routePath,
  209. builder: (context, params) => FavorietenWidget(),
  210. )
  211. ].map((r) => r.toRoute(appStateNotifier)).toList(),
  212. observers: [routeObserver],
  213. );
  214. extension NavParamExtensions on Map<String, String?> {
  215. Map<String, String> get withoutNulls => Map.fromEntries(
  216. entries
  217. .where((e) => e.value != null)
  218. .map((e) => MapEntry(e.key, e.value!)),
  219. );
  220. }
  221. extension NavigationExtensions on BuildContext {
  222. void goNamedAuth(
  223. String name,
  224. bool mounted, {
  225. Map<String, String> pathParameters = const <String, String>{},
  226. Map<String, String> queryParameters = const <String, String>{},
  227. Object? extra,
  228. bool ignoreRedirect = false,
  229. }) =>
  230. !mounted || GoRouter.of(this).shouldRedirect(ignoreRedirect)
  231. ? null
  232. : goNamed(
  233. name,
  234. pathParameters: pathParameters,
  235. queryParameters: queryParameters,
  236. extra: extra,
  237. );
  238. void pushNamedAuth(
  239. String name,
  240. bool mounted, {
  241. Map<String, String> pathParameters = const <String, String>{},
  242. Map<String, String> queryParameters = const <String, String>{},
  243. Object? extra,
  244. bool ignoreRedirect = false,
  245. }) =>
  246. !mounted || GoRouter.of(this).shouldRedirect(ignoreRedirect)
  247. ? null
  248. : pushNamed(
  249. name,
  250. pathParameters: pathParameters,
  251. queryParameters: queryParameters,
  252. extra: extra,
  253. );
  254. void safePop() {
  255. // If there is only one route on the stack, navigate to the initial
  256. // page instead of popping.
  257. if (canPop()) {
  258. pop();
  259. } else {
  260. go('/');
  261. }
  262. }
  263. }
  264. extension GoRouterExtensions on GoRouter {
  265. AppStateNotifier get appState => AppStateNotifier.instance;
  266. void prepareAuthEvent([bool ignoreRedirect = false]) =>
  267. appState.hasRedirect() && !ignoreRedirect
  268. ? null
  269. : appState.updateNotifyOnAuthChange(false);
  270. bool shouldRedirect(bool ignoreRedirect) =>
  271. !ignoreRedirect && appState.hasRedirect();
  272. void clearRedirectLocation() => appState.clearRedirectLocation();
  273. void setRedirectLocationIfUnset(String location) =>
  274. appState.updateNotifyOnAuthChange(false);
  275. }
  276. extension _GoRouterStateExtensions on GoRouterState {
  277. Map<String, dynamic> get extraMap =>
  278. extra != null ? extra as Map<String, dynamic> : {};
  279. Map<String, dynamic> get allParams => <String, dynamic>{}
  280. ..addAll(pathParameters)
  281. ..addAll(uri.queryParameters)
  282. ..addAll(extraMap);
  283. TransitionInfo get transitionInfo => extraMap.containsKey(kTransitionInfoKey)
  284. ? extraMap[kTransitionInfoKey] as TransitionInfo
  285. : TransitionInfo.appDefault();
  286. }
  287. class FFParameters {
  288. FFParameters(this.state, [this.asyncParams = const {}]);
  289. final GoRouterState state;
  290. final Map<String, Future<dynamic> Function(String)> asyncParams;
  291. Map<String, dynamic> futureParamValues = {};
  292. // Parameters are empty if the params map is empty or if the only parameter
  293. // present is the special extra parameter reserved for the transition info.
  294. bool get isEmpty =>
  295. state.allParams.isEmpty ||
  296. (state.allParams.length == 1 &&
  297. state.extraMap.containsKey(kTransitionInfoKey));
  298. bool isAsyncParam(MapEntry<String, dynamic> param) =>
  299. asyncParams.containsKey(param.key) && param.value is String;
  300. bool get hasFutures => state.allParams.entries.any(isAsyncParam);
  301. Future<bool> completeFutures() => Future.wait(
  302. state.allParams.entries.where(isAsyncParam).map(
  303. (param) async {
  304. final doc = await asyncParams[param.key]!(param.value)
  305. .onError((_, __) => null);
  306. if (doc != null) {
  307. futureParamValues[param.key] = doc;
  308. return true;
  309. }
  310. return false;
  311. },
  312. ),
  313. ).onError((_, __) => [false]).then((v) => v.every((e) => e));
  314. dynamic getParam<T>(
  315. String paramName,
  316. ParamType type, {
  317. bool isList = false,
  318. StructBuilder<T>? structBuilder,
  319. }) {
  320. if (futureParamValues.containsKey(paramName)) {
  321. return futureParamValues[paramName];
  322. }
  323. if (!state.allParams.containsKey(paramName)) {
  324. return null;
  325. }
  326. final param = state.allParams[paramName];
  327. // Got parameter from `extras`, so just directly return it.
  328. if (param is! String) {
  329. return param;
  330. }
  331. // Return serialized value.
  332. return deserializeParam<T>(
  333. param,
  334. type,
  335. isList,
  336. structBuilder: structBuilder,
  337. );
  338. }
  339. }
  340. class FFRoute {
  341. const FFRoute({
  342. required this.name,
  343. required this.path,
  344. required this.builder,
  345. this.requireAuth = false,
  346. this.asyncParams = const {},
  347. this.routes = const [],
  348. });
  349. final String name;
  350. final String path;
  351. final bool requireAuth;
  352. final Map<String, Future<dynamic> Function(String)> asyncParams;
  353. final Widget Function(BuildContext, FFParameters) builder;
  354. final List<GoRoute> routes;
  355. GoRoute toRoute(AppStateNotifier appStateNotifier) => GoRoute(
  356. name: name,
  357. path: path,
  358. redirect: (context, state) {
  359. if (appStateNotifier.shouldRedirect) {
  360. final redirectLocation = appStateNotifier.getRedirectLocation();
  361. appStateNotifier.clearRedirectLocation();
  362. return redirectLocation;
  363. }
  364. if (requireAuth && !appStateNotifier.loggedIn) {
  365. appStateNotifier.setRedirectLocationIfUnset(state.uri.toString());
  366. return '/home';
  367. }
  368. return null;
  369. },
  370. pageBuilder: (context, state) {
  371. fixStatusBarOniOS16AndBelow(context);
  372. final ffParams = FFParameters(state, asyncParams);
  373. final page = ffParams.hasFutures
  374. ? FutureBuilder(
  375. future: ffParams.completeFutures(),
  376. builder: (context, _) => builder(context, ffParams),
  377. )
  378. : builder(context, ffParams);
  379. final child = appStateNotifier.loading
  380. ? Center(
  381. child: SizedBox(
  382. width: 80.0,
  383. height: 80.0,
  384. child: SpinKitFadingCircle(
  385. color: Color(0xFFB1061E),
  386. size: 80.0,
  387. ),
  388. ),
  389. )
  390. : page;
  391. final transitionInfo = state.transitionInfo;
  392. return transitionInfo.hasTransition
  393. ? CustomTransitionPage(
  394. key: state.pageKey,
  395. name: state.name,
  396. child: child,
  397. transitionDuration: transitionInfo.duration,
  398. transitionsBuilder:
  399. (context, animation, secondaryAnimation, child) =>
  400. PageTransition(
  401. type: transitionInfo.transitionType,
  402. duration: transitionInfo.duration,
  403. reverseDuration: transitionInfo.duration,
  404. alignment: transitionInfo.alignment,
  405. child: child,
  406. ).buildTransitions(
  407. context,
  408. animation,
  409. secondaryAnimation,
  410. child,
  411. ),
  412. )
  413. : MaterialPage(
  414. key: state.pageKey, name: state.name, child: child);
  415. },
  416. routes: routes,
  417. );
  418. }
  419. class TransitionInfo {
  420. const TransitionInfo({
  421. required this.hasTransition,
  422. this.transitionType = PageTransitionType.fade,
  423. this.duration = const Duration(milliseconds: 300),
  424. this.alignment,
  425. });
  426. final bool hasTransition;
  427. final PageTransitionType transitionType;
  428. final Duration duration;
  429. final Alignment? alignment;
  430. static TransitionInfo appDefault() => TransitionInfo(hasTransition: false);
  431. }
  432. class RootPageContext {
  433. const RootPageContext(this.isRootPage, [this.errorRoute]);
  434. final bool isRootPage;
  435. final String? errorRoute;
  436. static bool isInactiveRootPage(BuildContext context) {
  437. final rootPageContext = context.read<RootPageContext?>();
  438. final isRootPage = rootPageContext?.isRootPage ?? false;
  439. final location = GoRouterState.of(context).uri.toString();
  440. return isRootPage &&
  441. location != '/' &&
  442. location != rootPageContext?.errorRoute;
  443. }
  444. static Widget wrap(Widget child, {String? errorRoute}) => Provider.value(
  445. value: RootPageContext(true, errorRoute),
  446. child: child,
  447. );
  448. }
  449. extension GoRouterLocationExtension on GoRouter {
  450. String getCurrentLocation() {
  451. final RouteMatch lastMatch = routerDelegate.currentConfiguration.last;
  452. final RouteMatchList matchList = lastMatch is ImperativeRouteMatch
  453. ? lastMatch.matches
  454. : routerDelegate.currentConfiguration;
  455. return matchList.uri.toString();
  456. }
  457. }