From 7431a35ece914fe52567d5a8de6a5e935b03a2a8 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 23 May 2023 09:13:57 +0200 Subject: [PATCH] neon: use go_router --- packages/app/pubspec.lock | 16 ++++ packages/neon/neon/lib/neon.dart | 1 + packages/neon/neon/lib/src/app.dart | 2 +- packages/neon/neon/lib/src/router.dart | 78 ++++++++++++------- packages/neon/neon/lib/src/router.g.dart | 57 ++++++++++++++ .../neon/lib/src/utils/stream_listenable.dart | 38 +++++++++ .../neon/neon/lib/src/widgets/exception.dart | 14 ++-- packages/neon/neon/pubspec.yaml | 4 +- 8 files changed, 171 insertions(+), 39 deletions(-) create mode 100644 packages/neon/neon/lib/src/router.g.dart create mode 100644 packages/neon/neon/lib/src/utils/stream_listenable.dart diff --git a/packages/app/pubspec.lock b/packages/app/pubspec.lock index dfe31e2c..1d9dbc9d 100644 --- a/packages/app/pubspec.lock +++ b/packages/app/pubspec.lock @@ -317,6 +317,14 @@ packages: description: flutter source: sdk version: "0.0.0" + go_router: + dependency: transitive + description: + name: go_router + sha256: "00d1b67d6e9fa443331da229084dd3eb04407f5a2dff22940bd7bba6af5722c3" + url: "https://pub.dev" + source: hosted + version: "7.1.1" html: dependency: transitive description: @@ -434,6 +442,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + logging: + dependency: transitive + description: + name: logging + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" + source: hosted + version: "1.1.1" markdown: dependency: transitive description: diff --git a/packages/neon/neon/lib/neon.dart b/packages/neon/neon/lib/neon.dart index d5db1303..b30ad6bb 100644 --- a/packages/neon/neon/lib/neon.dart +++ b/packages/neon/neon/lib/neon.dart @@ -89,6 +89,7 @@ part 'src/utils/settings_export_helper.dart'; part 'src/utils/sort_box_builder.dart'; part 'src/utils/sort_box_order_option_values.dart'; part 'src/utils/storage.dart'; +part 'src/utils/stream_listenable.dart'; part 'src/utils/theme.dart'; part 'src/utils/validators.dart'; part 'src/widgets/account_avatar.dart'; diff --git a/packages/neon/neon/lib/src/app.dart b/packages/neon/neon/lib/src/app.dart index d7d01963..b4687d30 100644 --- a/packages/neon/neon/lib/src/app.dart +++ b/packages/neon/neon/lib/src/app.dart @@ -295,7 +295,7 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra keepOriginalAccentColor: nextcloudTheme == null || (themeKeepOriginalAccentColor ?? false), oledAsDark: themeOLEDAsDark, ), - routerDelegate: _routerDelegate, + routerConfig: _routerDelegate, ); }, ); diff --git a/packages/neon/neon/lib/src/router.dart b/packages/neon/neon/lib/src/router.dart index 12935224..d8703ad1 100644 --- a/packages/neon/neon/lib/src/router.dart +++ b/packages/neon/neon/lib/src/router.dart @@ -1,41 +1,63 @@ // ignore: prefer_mixin import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:neon/neon.dart'; +import 'package:provider/provider.dart'; -class AppRouter extends RouterDelegate with ChangeNotifier, PopNavigatorRouterDelegateMixin { +part 'router.g.dart'; + +class AppRouter extends GoRouter { AppRouter({ - required this.navigatorKey, - required this.accountsBloc, - }); + required final GlobalKey navigatorKey, + required final AccountsBloc accountsBloc, + }) : super( + refreshListenable: StreamListenable.behaviorSubject(accountsBloc.activeAccount), + navigatorKey: navigatorKey, + initialLocation: const HomeRoute().location, + redirect: (final context, final state) { + final account = accountsBloc.activeAccount.valueOrNull; - final AccountsBloc accountsBloc; + if (account == null) { + return const LoginRoute().location; + } - @override - final GlobalKey navigatorKey; + if (state.location == const LoginRoute().location) { + return const HomeRoute().location; + } - @override - Future setNewRoutePath(final Account? configuration) async {} + return null; + }, + routes: $appRoutes, + ); +} + +@TypedGoRoute( + path: '/login', + name: 'login', +) +@immutable +class LoginRoute extends GoRouteData { + const LoginRoute({this.server}); + + final String? server; @override - Account? get currentConfiguration => accountsBloc.activeAccount.valueOrNull; + Widget build(final BuildContext context, final GoRouterState state) => LoginPage(serverURL: server); +} + +@TypedGoRoute( + path: '/', + name: 'home', +) +@immutable +class HomeRoute extends GoRouteData { + const HomeRoute(); @override - Widget build(final BuildContext context) => Navigator( - key: navigatorKey, - onPopPage: (final route, final result) => route.didPop(result), - pages: [ - if (currentConfiguration == null) ...[ - const MaterialPage( - child: LoginPage(), - ), - ] else ...[ - MaterialPage( - name: 'home', - child: HomePage( - key: Key(currentConfiguration!.id), - ), - ), - ], - ], - ); + Widget build(final BuildContext context, final GoRouterState state) { + final accountsBloc = Provider.of(context, listen: false); + final account = accountsBloc.activeAccount.valueOrNull!; + + return HomePage(key: Key(account.id)); + } } diff --git a/packages/neon/neon/lib/src/router.g.dart b/packages/neon/neon/lib/src/router.g.dart new file mode 100644 index 00000000..332dc2f9 --- /dev/null +++ b/packages/neon/neon/lib/src/router.g.dart @@ -0,0 +1,57 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'router.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $loginRoute, + $homeRoute, + ]; + +RouteBase get $loginRoute => GoRouteData.$route( + path: '/login', + name: 'login', + factory: $LoginRouteExtension._fromState, + ); + +extension $LoginRouteExtension on LoginRoute { + static LoginRoute _fromState(GoRouterState state) => LoginRoute( + server: state.queryParameters['server'], + ); + + String get location => GoRouteData.$location( + '/login', + queryParams: { + if (server != null) 'server': server, + }, + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => context.pushReplacement(location); +} + +RouteBase get $homeRoute => GoRouteData.$route( + path: '/', + name: 'home', + factory: $HomeRouteExtension._fromState, + ); + +extension $HomeRouteExtension on HomeRoute { + static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); + + String get location => GoRouteData.$location( + '/', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => context.pushReplacement(location); +} diff --git a/packages/neon/neon/lib/src/utils/stream_listenable.dart b/packages/neon/neon/lib/src/utils/stream_listenable.dart new file mode 100644 index 00000000..9a93ea49 --- /dev/null +++ b/packages/neon/neon/lib/src/utils/stream_listenable.dart @@ -0,0 +1,38 @@ +part of '../../neon.dart'; + +/// Listenable Stream +/// +/// A class that implements [Listenable] for a stream. +/// Objects need to be manually disposed. +class StreamListenable extends ChangeNotifier { + /// Listenable Stream + /// + /// Implementation for all types of [Stream]s. + /// For an implementation tailored towards [BehaviorSubject] have a look at [StreamListenable.behaviorSubject]. + StreamListenable(final Stream stream) { + notifyListeners(); + + _subscription = stream.asBroadcastStream().listen((final value) { + notifyListeners(); + }); + } + + /// Listenable BehaviorSubject + /// + /// Implementation for a [BehaviorSubject]. It ensures to not unececcary notify listeners. + /// For an implementation tailored towards otnher kinds of [Stream] have a look at [StreamListenable]. + StreamListenable.behaviorSubject(final BehaviorSubject subject) { + _subscription = subject.listen((final value) { + notifyListeners(); + }); + } + + late final StreamSubscription _subscription; + + @override + void dispose() { + unawaited(_subscription.cancel()); + + super.dispose(); + } +} diff --git a/packages/neon/neon/lib/src/widgets/exception.dart b/packages/neon/neon/lib/src/widgets/exception.dart index a16ddee4..1f88bf48 100644 --- a/packages/neon/neon/lib/src/widgets/exception.dart +++ b/packages/neon/neon/lib/src/widgets/exception.dart @@ -62,7 +62,7 @@ class NeonException extends StatelessWidget { : AppLocalizations.of(context).actionRetry, onPressed: () async { if (details.isUnauthorized) { - await _openLoginPage(context); + _openLoginPage(context); } else { onRetry(); } @@ -177,14 +177,10 @@ class NeonException extends StatelessWidget { ); } - static Future _openLoginPage(final BuildContext context) async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (final context) => LoginPage( - serverURL: Provider.of(context, listen: false).activeAccount.value!.serverURL, - ), - ), - ); + static void _openLoginPage(final BuildContext context) { + LoginRoute( + server: Provider.of(context, listen: false).activeAccount.value!.serverURL, + ).go(context); } } diff --git a/packages/neon/neon/pubspec.yaml b/packages/neon/neon/pubspec.yaml index 071b5c59..5bca6d64 100644 --- a/packages/neon/neon/pubspec.yaml +++ b/packages/neon/neon/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: sdk: flutter flutter_native_splash: ^2.2.19 flutter_svg: ^2.0.5 + go_router: ^7.1.1 http: ^0.13.6 intl: ^0.18.0 json_annotation: ^4.8.1 @@ -56,7 +57,8 @@ dependencies: xml: ^6.3.0 dev_dependencies: - build_runner: ^2.4.2 + build_runner: ^2.4.4 + go_router_builder: ^2.0.1 json_serializable: ^6.6.2 nit_picking: git: