nav.dart 17 KB

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