nav.dart 17 KB

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