Browse Source

neon: use go_router for all pages

pull/338/head
Nikolas Rimikis 2 years ago
parent
commit
194b5f1e5d
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 2
      packages/neon/neon/lib/src/pages/account_settings.dart
  2. 395
      packages/neon/neon/lib/src/pages/home.dart
  3. 1
      packages/neon/neon/lib/src/pages/login.dart
  4. 29
      packages/neon/neon/lib/src/pages/settings.dart
  5. 77
      packages/neon/neon/lib/src/router.dart
  6. 103
      packages/neon/neon/lib/src/router.g.dart
  7. 8
      packages/neon/neon/lib/src/utils/global_popups.dart

2
packages/neon/neon/lib/src/pages/account_settings.dart

@ -29,8 +29,6 @@ class AccountSettingsPage extends StatelessWidget {
AppLocalizations.of(context).accountOptionsRemoveConfirm(account.client.humanReadableID), AppLocalizations.of(context).accountOptionsRemoveConfirm(account.client.humanReadableID),
)) { )) {
bloc.removeAccount(account); bloc.removeAccount(account);
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
} }
}, },
tooltip: AppLocalizations.of(context).accountOptionsRemove, tooltip: AppLocalizations.of(context).accountOptionsRemove,

395
packages/neon/neon/lib/src/pages/home.dart

@ -127,14 +127,6 @@ class _HomePageState extends State<HomePage> {
); );
} }
Future _openSettings() async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => const SettingsPage(),
),
);
}
Future _openNotifications( Future _openNotifications(
final NotificationsAppInterface app, final NotificationsAppInterface app,
final List<Account> accounts, final List<Account> accounts,
@ -208,219 +200,221 @@ class _HomePageState extends State<HomePage> {
final account = accounts.find(_account.id)!; final account = accounts.find(_account.id)!;
final isQuickBar = navigationMode == NavigationMode.quickBar; final isQuickBar = navigationMode == NavigationMode.quickBar;
final drawer = Drawer( final drawer = Builder(
width: isQuickBar ? kQuickBarWidth : null, builder: (final context) => Drawer(
child: Container( width: isQuickBar ? kQuickBarWidth : null,
padding: isQuickBar ? const EdgeInsets.all(5) : null, child: Container(
child: Column( padding: isQuickBar ? const EdgeInsets.all(5) : null,
children: [ child: Column(
Expanded( children: [
child: Scrollbar( Expanded(
controller: drawerScrollController, child: Scrollbar(
interactive: true,
child: ListView(
controller: drawerScrollController, controller: drawerScrollController,
// Needed for the drawer header to also render in the statusbar interactive: true,
padding: EdgeInsets.zero, child: ListView(
children: [ controller: drawerScrollController,
Builder( // Needed for the drawer header to also render in the statusbar
builder: (final context) { padding: EdgeInsets.zero,
if (accountsSnapshot.hasData) { children: [
if (isQuickBar) { Builder(
return Column( builder: (final context) {
children: [ if (accountsSnapshot.hasData) {
if (accounts.length != 1) ...[ if (isQuickBar) {
for (final account in accounts) ...[ return Column(
Container( children: [
margin: const EdgeInsets.symmetric( if (accounts.length != 1) ...[
vertical: 5, for (final account in accounts) ...[
), Container(
child: IconButton( margin: const EdgeInsets.symmetric(
onPressed: () { vertical: 5,
_accountsBloc.setActiveAccount(account); ),
}, child: IconButton(
tooltip: account.client.humanReadableID, onPressed: () {
icon: IntrinsicHeight( _accountsBloc.setActiveAccount(account);
child: NeonAccountAvatar( },
account: account, tooltip: account.client.humanReadableID,
icon: IntrinsicHeight(
child: NeonAccountAvatar(
account: account,
),
), ),
), ),
), ),
), ],
], Container(
Container( margin: const EdgeInsets.only(
margin: const EdgeInsets.only( top: 10,
top: 10, ),
), child: Divider(
child: Divider( height: 5,
height: 5, color: Theme.of(context).appBarTheme.foregroundColor,
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!,
), ),
), ),
], ],
] else ...[
NeonException(
capabilities.error,
onRetry: _capabilitiesBloc.refresh,
),
NeonLinearProgressIndicator(
visible: capabilities.loading,
),
],
if (accounts.length != 1) ...[
DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
dropdownColor: Theme.of(context).colorScheme.primary,
iconEnabledColor:
Theme.of(context).colorScheme.onBackground,
value: _account.id,
items: accounts
.map<DropdownMenuItem<String>>(
(final account) => DropdownMenuItem<String>(
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<int>(
stream: appImplementation.getUnreadCounter(_appsBloc) ??
BehaviorSubject<int>.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( return DrawerHeader(
key: Key('app-${appImplementation.id}'), decoration: BoxDecoration(
title: Row( color: Theme.of(context).colorScheme.primary,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(appImplementation.name(context)), if (capabilities.data != null) ...[
if (unreadCount > 0) ...[ if (capabilities.data!.capabilities.theming?.name !=
Text( null) ...[
unreadCount.toString(), Text(
style: TextStyle( capabilities.data!.capabilities.theming!.name!,
color: Theme.of(context).colorScheme.primary, style: DefaultTextStyle.of(context).style.copyWith(
fontWeight: FontWeight.bold, color:
fontSize: 14, 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<String>(
isExpanded: true,
dropdownColor: Theme.of(context).colorScheme.primary,
iconEnabledColor:
Theme.of(context).colorScheme.onBackground,
value: _account.id,
items: accounts
.map<DropdownMenuItem<String>>(
(final account) => DropdownMenuItem<String>(
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 { return Container();
await _appsBloc.setActiveApp(appImplementation.id); },
if (navigationMode == NavigationMode.drawer) { ),
// Don't pop when the drawer is always shown NeonException(
appImplementations.error,
onlyIcon: isQuickBar,
onRetry: _appsBloc.refresh,
),
NeonLinearProgressIndicator(
visible: appImplementations.loading,
),
if (appImplementations.data != null) ...[
for (final appImplementation in appImplementations.data!) ...[
StreamBuilder<int>(
stream: appImplementation.getUnreadCounter(_appsBloc) ??
BehaviorSubject<int>.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) { if (!mounted) {
return; return;
} }
Navigator.of(context).pop(); Scaffold.maybeOf(context)?.closeDrawer();
} },
}, );
); },
}, ),
), ],
], ],
], ],
], ),
), ),
), ),
), if (isQuickBar) ...[
if (isQuickBar) ...[ IconButton(
IconButton( onPressed: () => const SettingsRoute().go(context),
onPressed: _openSettings, tooltip: AppLocalizations.of(context).settings,
tooltip: AppLocalizations.of(context).settings, icon: Icon(
icon: Icon( Icons.settings,
Icons.settings, color: Theme.of(context).appBarTheme.foregroundColor,
color: Theme.of(context).appBarTheme.foregroundColor, ),
), ),
), ] else ...[
] else ...[ ListTile(
ListTile( key: const Key('settings'),
key: const Key('settings'), title: Text(AppLocalizations.of(context).settings),
title: Text(AppLocalizations.of(context).settings), leading: const Icon(Icons.settings),
leading: const Icon(Icons.settings), minLeadingWidth: 0,
minLeadingWidth: 0, onTap: () async {
onTap: () async { Scaffold.maybeOf(context)?.closeDrawer();
if (navigationMode == NavigationMode.drawer) { const SettingsRoute().go(context);
Navigator.of(context).pop(); },
} ),
await _openSettings(); ],
},
),
], ],
], ),
), ),
), ),
); );
@ -524,15 +518,8 @@ class _HomePageState extends State<HomePage> {
), ),
], ],
IconButton( IconButton(
onPressed: () async { onPressed: () {
await Navigator.of(context).push( AccountSettingsRoute(accountid: account.id).go(context);
MaterialPageRoute(
builder: (final context) => AccountSettingsPage(
bloc: _accountsBloc,
account: account,
),
),
);
}, },
tooltip: AppLocalizations.of(context).settingsAccount, tooltip: AppLocalizations.of(context).settingsAccount,
icon: IntrinsicWidth( icon: IntrinsicWidth(

1
packages/neon/neon/lib/src/pages/login.dart

@ -73,7 +73,6 @@ class _LoginPageState extends State<LoginPage> {
if (widget.serverURL != null) { if (widget.serverURL != null) {
_accountsBloc.updateAccount(account); _accountsBloc.updateAccount(account);
Navigator.of(context).pop();
} else { } else {
final existingAccount = _accountsBloc.accounts.value.find(account.id); final existingAccount = _accountsBloc.accounts.value.find(account.id);
if (existingAccount != null) { if (existingAccount != null) {

29
packages/neon/neon/lib/src/pages/settings.dart

@ -80,14 +80,8 @@ class _SettingsPageState extends State<SettingsPage> {
CustomSettingsTile( CustomSettingsTile(
leading: appImplementation.buildIcon(context), leading: appImplementation.buildIcon(context),
title: Text(appImplementation.name(context)), title: Text(appImplementation.name(context)),
onTap: () async { onTap: () {
await Navigator.of(context).push( NextcloudAppSettingsRoute(appid: appImplementation.id).go(context);
MaterialPageRoute(
builder: (final context) => NextcloudAppSettingsPage(
appImplementation: appImplementation,
),
),
);
}, },
), ),
], ],
@ -181,26 +175,15 @@ class _SettingsPageState extends State<SettingsPage> {
for (final account in accountsSnapshot.data!) ...[ for (final account in accountsSnapshot.data!) ...[
NeonAccountSettingsTile( NeonAccountSettingsTile(
account: account, account: account,
onTap: () async { onTap: () {
await Navigator.of(context).push( AccountSettingsRoute(accountid: account.id).go(context);
MaterialPageRoute(
builder: (final context) => AccountSettingsPage(
bloc: accountsBloc,
account: account,
),
),
);
}, },
), ),
], ],
CustomSettingsTile( CustomSettingsTile(
title: ElevatedButton.icon( title: ElevatedButton.icon(
onPressed: () async { onPressed: () {
await Navigator.of(context).push( const LoginRoute().go(context);
MaterialPageRoute(
builder: (final context) => const LoginPage(),
),
);
}, },
icon: const Icon(MdiIcons.accountPlus), icon: const Icon(MdiIcons.accountPlus),
label: Text(AppLocalizations.of(context).globalOptionsAccountsAdd), label: Text(AppLocalizations.of(context).globalOptionsAccountsAdd),

77
packages/neon/neon/lib/src/router.dart

@ -31,23 +31,45 @@ class AppRouter extends GoRouter {
); );
} }
@TypedGoRoute<LoginRoute>(
path: '/login',
name: 'login',
)
@immutable @immutable
class LoginRoute extends GoRouteData { class AccountSettingsRoute extends GoRouteData {
const LoginRoute({this.server}); const AccountSettingsRoute({
required this.accountid,
});
final String? server; final String accountid;
@override @override
Widget build(final BuildContext context, final GoRouterState state) => LoginPage(serverURL: server); Widget build(final BuildContext context, final GoRouterState state) {
final bloc = Provider.of<AccountsBloc>(context, listen: false);
final account = bloc.accounts.value.find(accountid)!;
return AccountSettingsPage(
bloc: bloc,
account: account,
);
}
} }
@TypedGoRoute<HomeRoute>( @TypedGoRoute<HomeRoute>(
path: '/', path: '/',
name: 'home', name: 'home',
routes: [
TypedGoRoute<SettingsRoute>(
path: 'settings',
name: 'Settings',
routes: [
TypedGoRoute<NextcloudAppSettingsRoute>(
path: ':appid',
name: 'NextcloudAppSettings',
),
TypedGoRoute<AccountSettingsRoute>(
path: 'account/:accountid',
name: 'AccountSettings',
),
],
)
],
) )
@immutable @immutable
class HomeRoute extends GoRouteData { class HomeRoute extends GoRouteData {
@ -61,3 +83,42 @@ class HomeRoute extends GoRouteData {
return HomePage(key: Key(account.id)); return HomePage(key: Key(account.id));
} }
} }
@TypedGoRoute<LoginRoute>(
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<List<AppImplementation>>(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();
}

103
packages/neon/neon/lib/src/router.g.dart

@ -7,26 +7,54 @@ part of 'router.dart';
// ************************************************************************** // **************************************************************************
List<RouteBase> get $appRoutes => [ List<RouteBase> get $appRoutes => [
$loginRoute,
$homeRoute, $homeRoute,
$loginRoute,
]; ];
RouteBase get $loginRoute => GoRouteData.$route( RouteBase get $homeRoute => GoRouteData.$route(
path: '/login', path: '/',
name: 'login', name: 'home',
factory: $LoginRouteExtension._fromState, 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 { extension $HomeRouteExtension on HomeRoute {
static LoginRoute _fromState(GoRouterState state) => LoginRoute( static HomeRoute _fromState(GoRouterState state) => const HomeRoute();
server: state.queryParameters['server'],
String get location => GoRouteData.$location(
'/',
); );
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
}
extension $SettingsRouteExtension on SettingsRoute {
static SettingsRoute _fromState(GoRouterState state) => const SettingsRoute();
String get location => GoRouteData.$location( String get location => GoRouteData.$location(
'/login', '/settings',
queryParams: {
if (server != null) 'server': server,
},
); );
void go(BuildContext context) => context.go(location); void go(BuildContext context) => context.go(location);
@ -36,17 +64,54 @@ extension $LoginRouteExtension on LoginRoute {
void pushReplacement(BuildContext context) => context.pushReplacement(location); void pushReplacement(BuildContext context) => context.pushReplacement(location);
} }
RouteBase get $homeRoute => GoRouteData.$route( extension $NextcloudAppSettingsRouteExtension on NextcloudAppSettingsRoute {
path: '/', static NextcloudAppSettingsRoute _fromState(GoRouterState state) => NextcloudAppSettingsRoute(
name: 'home', appid: state.pathParameters['appid']!,
factory: $HomeRouteExtension._fromState, );
String get location => GoRouteData.$location(
'/settings/${Uri.encodeComponent(appid)}',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(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<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
}
RouteBase get $loginRoute => GoRouteData.$route(
path: '/login',
name: 'login',
factory: $LoginRouteExtension._fromState,
); );
extension $HomeRouteExtension on HomeRoute { extension $LoginRouteExtension on LoginRoute {
static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); static LoginRoute _fromState(GoRouterState state) => LoginRoute(
server: state.queryParameters['server'],
);
String get location => GoRouteData.$location( String get location => GoRouteData.$location(
'/', '/login',
queryParams: {
if (server != null) 'server': server,
},
); );
void go(BuildContext context) => context.go(location); void go(BuildContext context) => context.go(location);

8
packages/neon/neon/lib/src/utils/global_popups.dart

@ -27,12 +27,8 @@ class GlobalPopups {
content: Text(AppLocalizations.of(context).firstLaunchGoToSettingsToEnablePushNotifications), content: Text(AppLocalizations.of(context).firstLaunchGoToSettingsToEnablePushNotifications),
action: SnackBarAction( action: SnackBarAction(
label: AppLocalizations.of(context).settings, label: AppLocalizations.of(context).settings,
onPressed: () async { onPressed: () {
await Navigator.of(context).push( const SettingsRoute().go(context);
MaterialPageRoute(
builder: (final context) => const SettingsPage(),
),
);
}, },
), ),
), ),

Loading…
Cancel
Save