nav.dart 16 KB

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