nav.dart 17 KB

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