From 194b5f1e5d41054a7f5fd0122b0c34ddf9322fb0 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 23 May 2023 09:13:58 +0200 Subject: [PATCH] neon: use go_router for all pages --- .../neon/lib/src/pages/account_settings.dart | 2 - packages/neon/neon/lib/src/pages/home.dart | 395 +++++++++--------- packages/neon/neon/lib/src/pages/login.dart | 1 - .../neon/neon/lib/src/pages/settings.dart | 29 +- packages/neon/neon/lib/src/router.dart | 77 +++- packages/neon/neon/lib/src/router.g.dart | 103 ++++- .../neon/lib/src/utils/global_popups.dart | 8 +- 7 files changed, 352 insertions(+), 263 deletions(-) diff --git a/packages/neon/neon/lib/src/pages/account_settings.dart b/packages/neon/neon/lib/src/pages/account_settings.dart index 69e8bd52..11813dbb 100644 --- a/packages/neon/neon/lib/src/pages/account_settings.dart +++ b/packages/neon/neon/lib/src/pages/account_settings.dart @@ -29,8 +29,6 @@ class AccountSettingsPage extends StatelessWidget { AppLocalizations.of(context).accountOptionsRemoveConfirm(account.client.humanReadableID), )) { bloc.removeAccount(account); - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); } }, tooltip: AppLocalizations.of(context).accountOptionsRemove, diff --git a/packages/neon/neon/lib/src/pages/home.dart b/packages/neon/neon/lib/src/pages/home.dart index 482f5608..3ebb4281 100644 --- a/packages/neon/neon/lib/src/pages/home.dart +++ b/packages/neon/neon/lib/src/pages/home.dart @@ -127,14 +127,6 @@ class _HomePageState extends State { ); } - Future _openSettings() async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (final context) => const SettingsPage(), - ), - ); - } - Future _openNotifications( final NotificationsAppInterface app, final List accounts, @@ -208,219 +200,221 @@ class _HomePageState extends State { final account = accounts.find(_account.id)!; final isQuickBar = navigationMode == NavigationMode.quickBar; - final drawer = Drawer( - width: isQuickBar ? kQuickBarWidth : null, - child: Container( - padding: isQuickBar ? const EdgeInsets.all(5) : null, - child: Column( - children: [ - Expanded( - child: Scrollbar( - controller: drawerScrollController, - interactive: true, - child: ListView( + final drawer = Builder( + builder: (final context) => Drawer( + width: isQuickBar ? kQuickBarWidth : null, + child: Container( + padding: isQuickBar ? const EdgeInsets.all(5) : null, + child: Column( + children: [ + Expanded( + child: Scrollbar( controller: drawerScrollController, - // Needed for the drawer header to also render in the statusbar - padding: EdgeInsets.zero, - children: [ - Builder( - builder: (final context) { - if (accountsSnapshot.hasData) { - if (isQuickBar) { - return Column( - children: [ - if (accounts.length != 1) ...[ - for (final account in accounts) ...[ - Container( - margin: const EdgeInsets.symmetric( - vertical: 5, - ), - child: IconButton( - onPressed: () { - _accountsBloc.setActiveAccount(account); - }, - tooltip: account.client.humanReadableID, - icon: IntrinsicHeight( - child: NeonAccountAvatar( - account: account, + interactive: true, + child: ListView( + controller: drawerScrollController, + // Needed for the drawer header to also render in the statusbar + padding: EdgeInsets.zero, + children: [ + Builder( + builder: (final context) { + if (accountsSnapshot.hasData) { + if (isQuickBar) { + return Column( + children: [ + if (accounts.length != 1) ...[ + for (final account in accounts) ...[ + Container( + margin: const EdgeInsets.symmetric( + vertical: 5, + ), + child: IconButton( + onPressed: () { + _accountsBloc.setActiveAccount(account); + }, + tooltip: account.client.humanReadableID, + icon: IntrinsicHeight( + child: NeonAccountAvatar( + account: account, + ), ), ), ), - ), - ], - Container( - margin: const EdgeInsets.only( - top: 10, - ), - child: Divider( - height: 5, - color: Theme.of(context).appBarTheme.foregroundColor, - ), - ), - ], - ], - ); - } - return DrawerHeader( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (capabilities.data != null) ...[ - if (capabilities.data!.capabilities.theming?.name != null) ...[ - Text( - capabilities.data!.capabilities.theming!.name!, - style: DefaultTextStyle.of(context).style.copyWith( - color: Theme.of(context).appBarTheme.foregroundColor, - ), - ), - ], - if (capabilities.data!.capabilities.theming?.logo != null) ...[ - Flexible( - child: NeonCachedUrlImage( - url: capabilities.data!.capabilities.theming!.logo!, + ], + Container( + margin: const EdgeInsets.only( + top: 10, + ), + child: Divider( + height: 5, + color: Theme.of(context).appBarTheme.foregroundColor, ), ), ], - ] else ...[ - NeonException( - capabilities.error, - onRetry: _capabilitiesBloc.refresh, - ), - NeonLinearProgressIndicator( - visible: capabilities.loading, - ), - ], - if (accounts.length != 1) ...[ - DropdownButtonHideUnderline( - child: DropdownButton( - isExpanded: true, - dropdownColor: Theme.of(context).colorScheme.primary, - iconEnabledColor: - Theme.of(context).colorScheme.onBackground, - value: _account.id, - items: accounts - .map>( - (final account) => DropdownMenuItem( - value: account.id, - child: NeonAccountTile( - account: account, - dense: true, - textColor: - Theme.of(context).appBarTheme.foregroundColor, - ), - ), - ) - .toList(), - onChanged: (final id) { - if (id != null) { - _accountsBloc.setActiveAccount(accounts.find(id)); - } - }, - ), - ), ], - ], - ), - ); - } - return Container(); - }, - ), - NeonException( - appImplementations.error, - onlyIcon: isQuickBar, - onRetry: _appsBloc.refresh, - ), - NeonLinearProgressIndicator( - visible: appImplementations.loading, - ), - if (appImplementations.data != null) ...[ - for (final appImplementation in appImplementations.data!) ...[ - StreamBuilder( - stream: appImplementation.getUnreadCounter(_appsBloc) ?? - BehaviorSubject.seeded(0), - builder: (final context, final unreadCounterSnapshot) { - final unreadCount = unreadCounterSnapshot.data ?? 0; - if (isQuickBar) { - return IconButton( - onPressed: () async { - await _appsBloc.setActiveApp(appImplementation.id); - }, - tooltip: appImplementation.name(context), - icon: NeonAppImplementationIcon( - appImplementation: appImplementation, - unreadCount: unreadCount, - color: Theme.of(context).colorScheme.primary, - ), ); } - return ListTile( - key: Key('app-${appImplementation.id}'), - title: Row( + return DrawerHeader( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(appImplementation.name(context)), - if (unreadCount > 0) ...[ - Text( - unreadCount.toString(), - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold, - fontSize: 14, + if (capabilities.data != null) ...[ + if (capabilities.data!.capabilities.theming?.name != + null) ...[ + Text( + capabilities.data!.capabilities.theming!.name!, + style: DefaultTextStyle.of(context).style.copyWith( + color: + Theme.of(context).appBarTheme.foregroundColor, + ), + ), + ], + if (capabilities.data!.capabilities.theming?.logo != + null) ...[ + Flexible( + child: NeonCachedUrlImage( + url: capabilities.data!.capabilities.theming!.logo!, + ), + ), + ], + ] else ...[ + NeonException( + capabilities.error, + onRetry: _capabilitiesBloc.refresh, + ), + NeonLinearProgressIndicator( + visible: capabilities.loading, + ), + ], + if (accounts.length != 1) ...[ + DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + dropdownColor: Theme.of(context).colorScheme.primary, + iconEnabledColor: + Theme.of(context).colorScheme.onBackground, + value: _account.id, + items: accounts + .map>( + (final account) => DropdownMenuItem( + value: account.id, + child: NeonAccountTile( + account: account, + dense: true, + textColor: Theme.of(context) + .appBarTheme + .foregroundColor, + ), + ), + ) + .toList(), + onChanged: (final id) { + if (id != null) { + _accountsBloc.setActiveAccount(accounts.find(id)); + } + }, ), ), ], ], ), - leading: appImplementation.buildIcon(context), - minLeadingWidth: 0, - onTap: () async { - await _appsBloc.setActiveApp(appImplementation.id); - if (navigationMode == NavigationMode.drawer) { - // Don't pop when the drawer is always shown + ); + } + return Container(); + }, + ), + NeonException( + appImplementations.error, + onlyIcon: isQuickBar, + onRetry: _appsBloc.refresh, + ), + NeonLinearProgressIndicator( + visible: appImplementations.loading, + ), + if (appImplementations.data != null) ...[ + for (final appImplementation in appImplementations.data!) ...[ + StreamBuilder( + stream: appImplementation.getUnreadCounter(_appsBloc) ?? + BehaviorSubject.seeded(0), + builder: (final context, final unreadCounterSnapshot) { + final unreadCount = unreadCounterSnapshot.data ?? 0; + if (isQuickBar) { + return IconButton( + onPressed: () async { + await _appsBloc.setActiveApp(appImplementation.id); + }, + tooltip: appImplementation.name(context), + icon: NeonAppImplementationIcon( + appImplementation: appImplementation, + unreadCount: unreadCount, + color: Theme.of(context).colorScheme.primary, + ), + ); + } + return ListTile( + key: Key('app-${appImplementation.id}'), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(appImplementation.name(context)), + if (unreadCount > 0) ...[ + Text( + unreadCount.toString(), + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ], + ], + ), + leading: appImplementation.buildIcon(context), + minLeadingWidth: 0, + onTap: () async { + await _appsBloc.setActiveApp(appImplementation.id); + if (!mounted) { return; } - Navigator.of(context).pop(); - } - }, - ); - }, - ), + Scaffold.maybeOf(context)?.closeDrawer(); + }, + ); + }, + ), + ], ], ], - ], + ), ), ), - ), - if (isQuickBar) ...[ - IconButton( - onPressed: _openSettings, - tooltip: AppLocalizations.of(context).settings, - icon: Icon( - Icons.settings, - color: Theme.of(context).appBarTheme.foregroundColor, + if (isQuickBar) ...[ + IconButton( + onPressed: () => const SettingsRoute().go(context), + tooltip: AppLocalizations.of(context).settings, + icon: Icon( + Icons.settings, + color: Theme.of(context).appBarTheme.foregroundColor, + ), ), - ), - ] else ...[ - ListTile( - key: const Key('settings'), - title: Text(AppLocalizations.of(context).settings), - leading: const Icon(Icons.settings), - minLeadingWidth: 0, - onTap: () async { - if (navigationMode == NavigationMode.drawer) { - Navigator.of(context).pop(); - } - await _openSettings(); - }, - ), + ] else ...[ + ListTile( + key: const Key('settings'), + title: Text(AppLocalizations.of(context).settings), + leading: const Icon(Icons.settings), + minLeadingWidth: 0, + onTap: () async { + Scaffold.maybeOf(context)?.closeDrawer(); + const SettingsRoute().go(context); + }, + ), + ], ], - ], + ), ), ), ); @@ -524,15 +518,8 @@ class _HomePageState extends State { ), ], IconButton( - onPressed: () async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (final context) => AccountSettingsPage( - bloc: _accountsBloc, - account: account, - ), - ), - ); + onPressed: () { + AccountSettingsRoute(accountid: account.id).go(context); }, tooltip: AppLocalizations.of(context).settingsAccount, icon: IntrinsicWidth( diff --git a/packages/neon/neon/lib/src/pages/login.dart b/packages/neon/neon/lib/src/pages/login.dart index e998f2d7..b3ed5c2d 100644 --- a/packages/neon/neon/lib/src/pages/login.dart +++ b/packages/neon/neon/lib/src/pages/login.dart @@ -73,7 +73,6 @@ class _LoginPageState extends State { if (widget.serverURL != null) { _accountsBloc.updateAccount(account); - Navigator.of(context).pop(); } else { final existingAccount = _accountsBloc.accounts.value.find(account.id); if (existingAccount != null) { diff --git a/packages/neon/neon/lib/src/pages/settings.dart b/packages/neon/neon/lib/src/pages/settings.dart index 95c956f1..e51b1c33 100644 --- a/packages/neon/neon/lib/src/pages/settings.dart +++ b/packages/neon/neon/lib/src/pages/settings.dart @@ -80,14 +80,8 @@ class _SettingsPageState extends State { CustomSettingsTile( leading: appImplementation.buildIcon(context), title: Text(appImplementation.name(context)), - onTap: () async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (final context) => NextcloudAppSettingsPage( - appImplementation: appImplementation, - ), - ), - ); + onTap: () { + NextcloudAppSettingsRoute(appid: appImplementation.id).go(context); }, ), ], @@ -181,26 +175,15 @@ class _SettingsPageState extends State { for (final account in accountsSnapshot.data!) ...[ NeonAccountSettingsTile( account: account, - onTap: () async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (final context) => AccountSettingsPage( - bloc: accountsBloc, - account: account, - ), - ), - ); + onTap: () { + AccountSettingsRoute(accountid: account.id).go(context); }, ), ], CustomSettingsTile( title: ElevatedButton.icon( - onPressed: () async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (final context) => const LoginPage(), - ), - ); + onPressed: () { + const LoginRoute().go(context); }, icon: const Icon(MdiIcons.accountPlus), label: Text(AppLocalizations.of(context).globalOptionsAccountsAdd), diff --git a/packages/neon/neon/lib/src/router.dart b/packages/neon/neon/lib/src/router.dart index d8703ad1..d6c397c5 100644 --- a/packages/neon/neon/lib/src/router.dart +++ b/packages/neon/neon/lib/src/router.dart @@ -31,23 +31,45 @@ class AppRouter extends GoRouter { ); } -@TypedGoRoute( - path: '/login', - name: 'login', -) @immutable -class LoginRoute extends GoRouteData { - const LoginRoute({this.server}); +class AccountSettingsRoute extends GoRouteData { + const AccountSettingsRoute({ + required this.accountid, + }); - final String? server; + final String accountid; @override - Widget build(final BuildContext context, final GoRouterState state) => LoginPage(serverURL: server); + Widget build(final BuildContext context, final GoRouterState state) { + final bloc = Provider.of(context, listen: false); + final account = bloc.accounts.value.find(accountid)!; + + return AccountSettingsPage( + bloc: bloc, + account: account, + ); + } } @TypedGoRoute( path: '/', name: 'home', + routes: [ + TypedGoRoute( + path: 'settings', + name: 'Settings', + routes: [ + TypedGoRoute( + path: ':appid', + name: 'NextcloudAppSettings', + ), + TypedGoRoute( + path: 'account/:accountid', + name: 'AccountSettings', + ), + ], + ) + ], ) @immutable class HomeRoute extends GoRouteData { @@ -61,3 +83,42 @@ class HomeRoute extends GoRouteData { return HomePage(key: Key(account.id)); } } + +@TypedGoRoute( + path: '/login', + name: 'login', +) +@immutable +class LoginRoute extends GoRouteData { + const LoginRoute({this.server}); + + final String? server; + + @override + Widget build(final BuildContext context, final GoRouterState state) => LoginPage(serverURL: server); +} + +@immutable +class NextcloudAppSettingsRoute extends GoRouteData { + const NextcloudAppSettingsRoute({ + required this.appid, + }); + + final String appid; + + @override + Widget build(final BuildContext context, final GoRouterState state) { + final appImplementations = Provider.of>(context, listen: false); + final appImplementation = appImplementations.firstWhere((final app) => app.id == appid); + + return NextcloudAppSettingsPage(appImplementation: appImplementation); + } +} + +@immutable +class SettingsRoute extends GoRouteData { + const SettingsRoute(); + + @override + Widget build(final BuildContext context, final GoRouterState state) => const SettingsPage(); +} diff --git a/packages/neon/neon/lib/src/router.g.dart b/packages/neon/neon/lib/src/router.g.dart index 332dc2f9..406213ea 100644 --- a/packages/neon/neon/lib/src/router.g.dart +++ b/packages/neon/neon/lib/src/router.g.dart @@ -7,26 +7,54 @@ part of 'router.dart'; // ************************************************************************** List get $appRoutes => [ - $loginRoute, $homeRoute, + $loginRoute, ]; -RouteBase get $loginRoute => GoRouteData.$route( - path: '/login', - name: 'login', - factory: $LoginRouteExtension._fromState, +RouteBase get $homeRoute => GoRouteData.$route( + path: '/', + name: 'home', + factory: $HomeRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: 'settings', + name: 'Settings', + factory: $SettingsRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: ':appid', + name: 'NextcloudAppSettings', + factory: $NextcloudAppSettingsRouteExtension._fromState, + ), + GoRouteData.$route( + path: 'account/:accountid', + name: 'AccountSettings', + factory: $AccountSettingsRouteExtension._fromState, + ), + ], + ), + ], ); -extension $LoginRouteExtension on LoginRoute { - static LoginRoute _fromState(GoRouterState state) => LoginRoute( - server: state.queryParameters['server'], +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); +} + +extension $SettingsRouteExtension on SettingsRoute { + static SettingsRoute _fromState(GoRouterState state) => const SettingsRoute(); + String get location => GoRouteData.$location( - '/login', - queryParams: { - if (server != null) 'server': server, - }, + '/settings', ); void go(BuildContext context) => context.go(location); @@ -36,17 +64,54 @@ extension $LoginRouteExtension on LoginRoute { void pushReplacement(BuildContext context) => context.pushReplacement(location); } -RouteBase get $homeRoute => GoRouteData.$route( - path: '/', - name: 'home', - factory: $HomeRouteExtension._fromState, +extension $NextcloudAppSettingsRouteExtension on NextcloudAppSettingsRoute { + static NextcloudAppSettingsRoute _fromState(GoRouterState state) => NextcloudAppSettingsRoute( + appid: state.pathParameters['appid']!, + ); + + String get location => GoRouteData.$location( + '/settings/${Uri.encodeComponent(appid)}', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => context.pushReplacement(location); +} + +extension $AccountSettingsRouteExtension on AccountSettingsRoute { + static AccountSettingsRoute _fromState(GoRouterState state) => AccountSettingsRoute( + accountid: state.pathParameters['accountid']!, + ); + + String get location => GoRouteData.$location( + '/settings/account/${Uri.encodeComponent(accountid)}', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => context.pushReplacement(location); +} + +RouteBase get $loginRoute => GoRouteData.$route( + path: '/login', + name: 'login', + factory: $LoginRouteExtension._fromState, ); -extension $HomeRouteExtension on HomeRoute { - static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); +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); diff --git a/packages/neon/neon/lib/src/utils/global_popups.dart b/packages/neon/neon/lib/src/utils/global_popups.dart index 06376b8e..43ac125c 100644 --- a/packages/neon/neon/lib/src/utils/global_popups.dart +++ b/packages/neon/neon/lib/src/utils/global_popups.dart @@ -27,12 +27,8 @@ class GlobalPopups { content: Text(AppLocalizations.of(context).firstLaunchGoToSettingsToEnablePushNotifications), action: SnackBarAction( label: AppLocalizations.of(context).settings, - onPressed: () async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (final context) => const SettingsPage(), - ), - ); + onPressed: () { + const SettingsRoute().go(context); }, ), ),