Browse Source

feat(neon): use go_router for apps

Signed-off-by: Nikolas Rimikis <rimikis.nikolas@gmail.com>
feature/nested-router
Nikolas Rimikis 1 year ago
parent
commit
23efce9d23
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 5
      packages/neon/neon/lib/src/blocs/accounts.dart
  2. 4
      packages/neon/neon/lib/src/blocs/apps.dart
  3. 9
      packages/neon/neon/lib/src/models/app_implementation.dart
  4. 21
      packages/neon/neon/lib/src/pages/home.dart
  5. 3
      packages/neon/neon/lib/src/pages/settings.dart
  6. 99
      packages/neon/neon/lib/src/router.dart
  7. 292
      packages/neon/neon/lib/src/router.g.dart
  8. 2
      packages/neon/neon/lib/src/widgets/account_switcher.dart

5
packages/neon/neon/lib/src/blocs/accounts.dart

@ -136,8 +136,7 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
BehaviorSubject<List<Account>> accounts = BehaviorSubject<List<Account>>.seeded([]); BehaviorSubject<List<Account>> accounts = BehaviorSubject<List<Account>>.seeded([]);
@override @override
BehaviorSubject<Account?> activeAccount = BehaviorSubject<Account?>() BehaviorSubject<Account?> activeAccount = BehaviorSubject<Account?>();
..distinct((final current, final next) => current?.id != next?.id);
@override @override
void addAccount(final Account account) { void addAccount(final Account account) {
@ -173,8 +172,10 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
@override @override
void setActiveAccount(final Account account) { void setActiveAccount(final Account account) {
if (activeAccount.valueOrNull != account) {
activeAccount.add(account); activeAccount.add(account);
} }
}
@override @override
void updateAccount(final Account account) { void updateAccount(final Account account) {

4
packages/neon/neon/lib/src/blocs/apps.dart

@ -78,6 +78,10 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
unawaited(_checkCompatibility()); unawaited(_checkCompatibility());
}); });
activeApp.distinct().listen((final app) {
_router.go(app.initialPath);
});
unawaited(refresh()); unawaited(refresh());
} }

9
packages/neon/neon/lib/src/models/app_implementation.dart

@ -68,15 +68,6 @@ abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions>
); );
} }
/// Main branch displayed in the home page.
///
/// There's usually no need to override this.
StatefulShellBranch get mainBranch => StatefulShellBranch(
routes: [
route,
],
);
/// Route for the app. /// Route for the app.
/// ///
/// All pages of the app must be specified as subroutes. /// All pages of the app must be specified as subroutes.

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

@ -20,9 +20,12 @@ const kQuickBarWidth = kAvatarSize + 20;
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
const HomePage({ const HomePage({
this.appView,
super.key, super.key,
}); });
final Widget? appView;
@override @override
State<HomePage> createState() => _HomePageState(); State<HomePage> createState() => _HomePageState();
} }
@ -123,14 +126,11 @@ class _HomePageState extends State<HomePage> {
const drawer = NeonDrawer(); const drawer = NeonDrawer();
const appBar = NeonAppBar(); const appBar = NeonAppBar();
final appView = ResultBuilder<Iterable<AppImplementation>>.behaviorSubject( final appView = widget.appView ??
ResultBuilder<Iterable<AppImplementation>>.behaviorSubject(
stream: _appsBloc.appImplementations, stream: _appsBloc.appImplementations,
builder: (final context, final appImplementations) { builder: (final context, final appImplementations) {
if (!appImplementations.hasData) { if (appImplementations.hasData && appImplementations.requireData.isEmpty) {
return const SizedBox();
}
if (appImplementations.requireData.isEmpty) {
return Center( return Center(
child: Text( child: Text(
AppLocalizations.of(context).errorNoCompatibleNextcloudAppsFound, AppLocalizations.of(context).errorNoCompatibleNextcloudAppsFound,
@ -139,16 +139,7 @@ class _HomePageState extends State<HomePage> {
); );
} }
return StreamBuilder<AppImplementation>(
stream: _appsBloc.activeApp,
builder: (final context, final activeAppIDSnapshot) {
if (!activeAppIDSnapshot.hasData) {
return const SizedBox(); return const SizedBox();
}
return activeAppIDSnapshot.requireData.page;
},
);
}, },
); );

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

@ -59,6 +59,9 @@ class _SettingsPageState extends State<SettingsPage> {
final appImplementations = Provider.of<Iterable<AppImplementation>>(context); final appImplementations = Provider.of<Iterable<AppImplementation>>(context);
final appBar = AppBar( final appBar = AppBar(
leading: BackButton(
onPressed: () => const HomeRoute().go(context),
),
title: Text(AppLocalizations.of(context).settings), title: Text(AppLocalizations.of(context).settings),
actions: [ actions: [
IconButton( IconButton(

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

@ -62,12 +62,32 @@ class AppRouter extends GoRouter {
return null; return null;
}, },
routes: $appRoutes, routes: [
ShellRoute(
routes: appImplementations.map((final a) => a.route).toList(),
builder: _mainViewBuilder,
),
...$appRoutes,
],
); );
final GlobalKey<NavigatorState> navigatorKey; final GlobalKey<NavigatorState> navigatorKey;
} }
Widget _mainViewBuilder(
final BuildContext context,
final GoRouterState state, [
final Widget? child,
]) {
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final account = accountsBloc.activeAccount.valueOrNull!;
return HomePage(
appView: child,
key: Key(account.id),
);
}
@immutable @immutable
class AccountSettingsRoute extends GoRouteData { class AccountSettingsRoute extends GoRouteData {
const AccountSettingsRoute({ const AccountSettingsRoute({
@ -91,51 +111,20 @@ class AccountSettingsRoute extends GoRouteData {
@TypedGoRoute<HomeRoute>( @TypedGoRoute<HomeRoute>(
path: '/', path: '/',
name: 'home', name: 'home',
routes: [
TypedGoRoute<SettingsRoute>(
path: 'settings',
name: 'Settings',
routes: [
TypedGoRoute<NextcloudAppSettingsRoute>(
path: 'apps/:appid',
name: 'NextcloudAppSettings',
),
TypedGoRoute<_AddAccountRoute>(
path: 'account/add',
name: 'addAccount',
routes: [
TypedGoRoute<_AddAccountFlowRoute>(
path: 'flow',
),
TypedGoRoute<_AddAccountQrcodeRoute>(
path: 'qrcode',
),
TypedGoRoute<_AddAccountCheckServerStatusRoute>(
path: 'check/server',
),
TypedGoRoute<_AddAccountCheckAccountRoute>(
path: 'check/account',
),
],
),
TypedGoRoute<AccountSettingsRoute>(
path: 'account/:accountid',
name: 'AccountSettings',
),
],
),
],
) )
@immutable @immutable
class HomeRoute extends GoRouteData { class HomeRoute extends GoRouteData {
const HomeRoute(); const HomeRoute();
@override @override
Widget build(final BuildContext context, final GoRouterState state) { Widget build(final BuildContext context, final GoRouterState state) => _mainViewBuilder(context, state);
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final account = accountsBloc.activeAccount.valueOrNull!; @override
String? redirect(final BuildContext context, final GoRouterState state) {
final appsBloc = Provider.of<AccountsBloc>(context, listen: false).activeAppsBloc;
final activeApp = appsBloc.activeApp.valueOrNull;
return HomePage(key: Key(account.id)); return activeApp?.initialPath;
} }
} }
@ -381,6 +370,38 @@ class NextcloudAppSettingsRoute extends GoRouteData {
} }
} }
@TypedGoRoute<SettingsRoute>(
path: '/settings',
name: 'Settings',
routes: [
TypedGoRoute<NextcloudAppSettingsRoute>(
path: 'apps/:appid',
name: 'NextcloudAppSettings',
),
TypedGoRoute<_AddAccountRoute>(
path: 'account/add',
name: 'addAccount',
routes: [
TypedGoRoute<_AddAccountFlowRoute>(
path: 'flow',
),
TypedGoRoute<_AddAccountQrcodeRoute>(
path: 'qrcode',
),
TypedGoRoute<_AddAccountCheckServerStatusRoute>(
path: 'check/server',
),
TypedGoRoute<_AddAccountCheckAccountRoute>(
path: 'check/account',
),
],
),
TypedGoRoute<AccountSettingsRoute>(
path: 'account/:accountid',
name: 'AccountSettings',
),
],
)
@immutable @immutable
class SettingsRoute extends GoRouteData { class SettingsRoute extends GoRouteData {
const SettingsRoute({this.initialCategory}); const SettingsRoute({this.initialCategory});

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

@ -9,15 +9,160 @@ part of 'router.dart';
List<RouteBase> get $appRoutes => [ List<RouteBase> get $appRoutes => [
$homeRoute, $homeRoute,
$loginRoute, $loginRoute,
$settingsRoute,
]; ];
RouteBase get $homeRoute => GoRouteData.$route( RouteBase get $homeRoute => GoRouteData.$route(
path: '/', path: '/',
name: 'home', name: 'home',
factory: $HomeRouteExtension._fromState, 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<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
RouteBase get $loginRoute => GoRouteData.$route(
path: '/login',
name: 'login',
factory: $LoginRouteExtension._fromState,
routes: [ routes: [
GoRouteData.$route( GoRouteData.$route(
path: 'settings', path: 'flow',
factory: $LoginFlowRouteExtension._fromState,
),
GoRouteData.$route(
path: 'qrcode',
factory: $LoginQrcodeRouteExtension._fromState,
),
GoRouteData.$route(
path: 'check/server',
factory: $LoginCheckServerStatusRouteExtension._fromState,
),
GoRouteData.$route(
path: 'check/account',
factory: $LoginCheckAccountRouteExtension._fromState,
),
],
);
extension $LoginRouteExtension on LoginRoute {
static LoginRoute _fromState(GoRouterState state) => const LoginRoute();
String get location => GoRouteData.$location(
'/login',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $LoginFlowRouteExtension on LoginFlowRoute {
static LoginFlowRoute _fromState(GoRouterState state) => LoginFlowRoute(
serverUrl: state.uri.queryParameters['server-url']!,
);
String get location => GoRouteData.$location(
'/login/flow',
queryParams: {
'server-url': serverUrl,
},
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $LoginQrcodeRouteExtension on LoginQrcodeRoute {
static LoginQrcodeRoute _fromState(GoRouterState state) => const LoginQrcodeRoute();
String get location => GoRouteData.$location(
'/login/qrcode',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute {
static LoginCheckServerStatusRoute _fromState(GoRouterState state) => LoginCheckServerStatusRoute(
serverUrl: state.uri.queryParameters['server-url']!,
loginName: state.uri.queryParameters['login-name'],
password: state.uri.queryParameters['password'],
);
String get location => GoRouteData.$location(
'/login/check/server',
queryParams: {
'server-url': serverUrl,
if (loginName != null) 'login-name': loginName,
if (password != null) 'password': password,
},
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $LoginCheckAccountRouteExtension on LoginCheckAccountRoute {
static LoginCheckAccountRoute _fromState(GoRouterState state) => LoginCheckAccountRoute(
serverUrl: state.uri.queryParameters['server-url']!,
loginName: state.uri.queryParameters['login-name']!,
password: state.uri.queryParameters['password']!,
);
String get location => GoRouteData.$location(
'/login/check/account',
queryParams: {
'server-url': serverUrl,
'login-name': loginName,
'password': password,
},
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
RouteBase get $settingsRoute => GoRouteData.$route(
path: '/settings',
name: 'Settings', name: 'Settings',
factory: $SettingsRouteExtension._fromState, factory: $SettingsRouteExtension._fromState,
routes: [ routes: [
@ -55,26 +200,8 @@ RouteBase get $homeRoute => GoRouteData.$route(
factory: $AccountSettingsRouteExtension._fromState, factory: $AccountSettingsRouteExtension._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<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $SettingsRouteExtension on SettingsRoute { extension $SettingsRouteExtension on SettingsRoute {
static SettingsRoute _fromState(GoRouterState state) => SettingsRoute( static SettingsRoute _fromState(GoRouterState state) => SettingsRoute(
initialCategory: initialCategory:
@ -255,130 +382,3 @@ T? _$convertMapValue<T>(
extension<T extends Enum> on Map<T, String> { extension<T extends Enum> on Map<T, String> {
T _$fromName(String value) => entries.singleWhere((element) => element.value == value).key; T _$fromName(String value) => entries.singleWhere((element) => element.value == value).key;
} }
RouteBase get $loginRoute => GoRouteData.$route(
path: '/login',
name: 'login',
factory: $LoginRouteExtension._fromState,
routes: [
GoRouteData.$route(
path: 'flow',
factory: $LoginFlowRouteExtension._fromState,
),
GoRouteData.$route(
path: 'qrcode',
factory: $LoginQrcodeRouteExtension._fromState,
),
GoRouteData.$route(
path: 'check/server',
factory: $LoginCheckServerStatusRouteExtension._fromState,
),
GoRouteData.$route(
path: 'check/account',
factory: $LoginCheckAccountRouteExtension._fromState,
),
],
);
extension $LoginRouteExtension on LoginRoute {
static LoginRoute _fromState(GoRouterState state) => const LoginRoute();
String get location => GoRouteData.$location(
'/login',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $LoginFlowRouteExtension on LoginFlowRoute {
static LoginFlowRoute _fromState(GoRouterState state) => LoginFlowRoute(
serverUrl: state.uri.queryParameters['server-url']!,
);
String get location => GoRouteData.$location(
'/login/flow',
queryParams: {
'server-url': serverUrl,
},
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $LoginQrcodeRouteExtension on LoginQrcodeRoute {
static LoginQrcodeRoute _fromState(GoRouterState state) => const LoginQrcodeRoute();
String get location => GoRouteData.$location(
'/login/qrcode',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute {
static LoginCheckServerStatusRoute _fromState(GoRouterState state) => LoginCheckServerStatusRoute(
serverUrl: state.uri.queryParameters['server-url']!,
loginName: state.uri.queryParameters['login-name'],
password: state.uri.queryParameters['password'],
);
String get location => GoRouteData.$location(
'/login/check/server',
queryParams: {
'server-url': serverUrl,
if (loginName != null) 'login-name': loginName,
if (password != null) 'password': password,
},
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
extension $LoginCheckAccountRouteExtension on LoginCheckAccountRoute {
static LoginCheckAccountRoute _fromState(GoRouterState state) => LoginCheckAccountRoute(
serverUrl: state.uri.queryParameters['server-url']!,
loginName: state.uri.queryParameters['login-name']!,
password: state.uri.queryParameters['password']!,
);
String get location => GoRouteData.$location(
'/login/check/account',
queryParams: {
'server-url': serverUrl,
'login-name': loginName,
'password': password,
},
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}

2
packages/neon/neon/lib/src/widgets/account_switcher.dart

@ -57,7 +57,7 @@ class AccountSwitcherButton extends StatelessWidget {
title: Text(AppLocalizations.of(context).settingsAccountManage), title: Text(AppLocalizations.of(context).settingsAccountManage),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
const SettingsRoute(initialCategory: SettingsCageories.accounts).push(context); const SettingsRoute(initialCategory: SettingsCageories.accounts).go(context);
}, },
), ),
], ],

Loading…
Cancel
Save