nav.dart 18 KB

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