Browse Source

neon: use go_router for the main app page

pull/423/head
Nikolas Rimikis 2 years ago
parent
commit
c00b2b8cac
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 10
      packages/neon/neon/lib/src/app.dart
  2. 24
      packages/neon/neon/lib/src/blocs/accounts.dart
  3. 8
      packages/neon/neon/lib/src/blocs/apps.dart
  4. 14
      packages/neon/neon/lib/src/pages/account_settings.dart
  5. 41
      packages/neon/neon/lib/src/pages/home.dart
  6. 5
      packages/neon/neon/lib/src/pages/login.dart
  7. 12
      packages/neon/neon/lib/src/pages/settings.dart
  8. 73
      packages/neon/neon/lib/src/router.dart
  9. 98
      packages/neon/neon/lib/src/router.g.dart
  10. 4
      packages/neon/neon/lib/src/utils/global_popups.dart
  11. 4
      packages/neon/neon/lib/src/widgets/app_bar.dart
  12. 4
      packages/neon/neon/lib/src/widgets/drawer.dart

10
packages/neon/neon/lib/src/app.dart

@ -37,15 +37,12 @@ class NeonApp extends StatefulWidget {
// ignore: prefer_mixin
class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.TrayListener, WindowListener {
final _appRegex = RegExp(r'^app_([a-z]+)$', multiLine: true);
final _navigatorKey = GlobalKey<NavigatorState>();
late final GlobalKey<NavigatorState> _navigatorKey;
late final Iterable<AppImplementation> _appImplementations;
late final NeonPlatform _platform;
late final GlobalOptions _globalOptions;
late final AccountsBloc _accountsBloc;
late final _routerDelegate = AppRouter(
navigatorKey: _navigatorKey,
accountsBloc: _accountsBloc,
);
late final AppRouter _routerDelegate;
Rect? _lastBounds;
@ -58,6 +55,9 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
_globalOptions = Provider.of<GlobalOptions>(context, listen: false);
_accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
_routerDelegate = _accountsBloc.router;
_navigatorKey = _routerDelegate.navigatorKey;
WidgetsBinding.instance.addObserver(this);
if (_platform.canUseSystemTray) {
tray.trayManager.addListener(this);

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

@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:neon/src/bloc/bloc.dart';
import 'package:neon/src/blocs/apps.dart';
import 'package:neon/src/blocs/capabilities.dart';
@ -10,6 +10,7 @@ import 'package:neon/src/blocs/user_statuses.dart';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/app_implementation.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/settings/models/storage.dart';
import 'package:neon/src/utils/account_options.dart';
import 'package:neon/src/utils/global_options.dart';
@ -63,6 +64,11 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
this._globalOptions,
this._allAppImplementations,
) {
router = AppRouter(
navigatorKey: GlobalKey<NavigatorState>(),
accountsBloc: this,
appImplementations: _allAppImplementations,
);
accounts
..add(loadAccounts(_storage))
..listen((final as) async {
@ -109,6 +115,7 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
final SharedPreferences _sharedPreferences;
final GlobalOptions _globalOptions;
final Iterable<AppImplementation> _allAppImplementations;
late final AppRouter router;
final _keyLastUsedAccount = 'last-used-account';
final _accountsOptions = <String, AccountSpecificOptions>{};
@ -134,8 +141,7 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
BehaviorSubject<List<Account>> accounts = BehaviorSubject<List<Account>>.seeded([]);
@override
BehaviorSubject<Account?> activeAccount = BehaviorSubject<Account?>.seeded(null)
..distinct((final current, final next) => current?.id != next?.id);
BehaviorSubject<Account?> activeAccount = BehaviorSubject<Account?>.seeded(null);
@override
void addAccount(final Account account) {
@ -154,6 +160,9 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
if (aa?.id == account.id) {
if (as.firstOrNull != null) {
setActiveAccount(as.first);
final activeApp = getAppsBlocFor(as.first).activeApp.value.initialRouteName;
router.goNamed(activeApp);
} else {
activeAccount.add(null);
}
@ -171,7 +180,13 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
@override
void setActiveAccount(final Account account) {
activeAccount.add(account);
if (activeAccount.valueOrNull?.id != account.id) {
if (activeAccount.valueOrNull == null) {
router.go(const HomeRoute().location);
}
activeAccount.add(account);
}
}
@override
@ -240,6 +255,7 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
this,
account,
_allAppImplementations,
router,
);
}

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

@ -9,6 +9,7 @@ import 'package:neon/src/blocs/capabilities.dart';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/app_implementation.dart';
import 'package:neon/src/models/notifications_interface.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/settings/models/nextcloud_app_options.dart';
import 'package:neon/src/utils/request_manager.dart';
import 'package:nextcloud/nextcloud.dart';
@ -43,6 +44,7 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
this._accountsBloc,
this._account,
this._allAppImplementations,
this._router,
) {
apps.listen((final result) {
appImplementations
@ -156,6 +158,7 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
final AccountsBloc _accountsBloc;
final Account _account;
final Iterable<AppImplementation> _allAppImplementations;
final AppRouter _router;
@override
void dispose() {
@ -216,6 +219,11 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
if (app != null) {
if (activeApp.valueOrNull?.id != appID) {
activeApp.add(app);
// avoid inactive accounts calling the navigator with a potentially unsupported app.
if (_accountsBloc.activeAccount.value?.id == _account.id) {
_router.goNamed(app.initialRouteName);
}
}
} else {
throw Exception('App $appID not found');

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

@ -5,7 +5,6 @@ import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/bloc/result_builder.dart';
import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/settings/widgets/custom_settings_tile.dart';
import 'package:neon/src/settings/widgets/dropdown_button_settings_tile.dart';
import 'package:neon/src/settings/widgets/settings_category.dart';
@ -41,20 +40,7 @@ class AccountSettingsPage extends StatelessWidget {
context,
AppLocalizations.of(context).accountOptionsRemoveConfirm(account.client.humanReadableID),
)) {
final isActive = bloc.activeAccount.value == account;
bloc.removeAccount(account);
// ignore: use_build_context_synchronously
if (!context.mounted) {
return;
}
if (isActive) {
const HomeRoute().go(context);
} else {
Navigator.of(context).pop();
}
}
},
tooltip: AppLocalizations.of(context).accountOptionsRemove,

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

@ -21,9 +21,12 @@ const kQuickBarWidth = kAvatarSize + 20;
class HomePage extends StatefulWidget {
const HomePage({
this.appView,
super.key,
});
final Widget? appView;
@override
State<HomePage> createState() => _HomePageState();
}
@ -121,34 +124,26 @@ class _HomePageState extends State<HomePage> {
const drawer = NeonDrawer();
const appBar = NeonAppBar();
final appView = ResultBuilder<Iterable<AppImplementation>>.behaviorSubject(
stream: _appsBloc.appImplementations,
builder: (final context, final appImplementations) {
if (!appImplementations.hasData) {
return const SizedBox();
}
if (appImplementations.requireData.isEmpty) {
return Center(
child: Text(
AppLocalizations.of(context).errorNoCompatibleNextcloudAppsFound,
textAlign: TextAlign.center,
),
);
}
return StreamBuilder<AppImplementation>(
stream: _appsBloc.activeApp,
builder: (final context, final activeAppIDSnapshot) {
if (!activeAppIDSnapshot.hasData) {
final appView = widget.appView ??
ResultBuilder<Iterable<AppImplementation>>.behaviorSubject(
stream: _appsBloc.appImplementations,
builder: (final context, final appImplementations) {
if (!appImplementations.hasData) {
return const SizedBox();
}
return activeAppIDSnapshot.requireData.page;
if (appImplementations.requireData.isEmpty) {
return Center(
child: Text(
AppLocalizations.of(context).errorNoCompatibleNextcloudAppsFound,
textAlign: TextAlign.center,
),
);
}
return const SizedBox();
},
);
},
);
final body = OptionBuilder<global_options.NavigationMode>(
option: _globalOptions.navigationMode,

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

@ -5,7 +5,6 @@ import 'package:neon/src/blocs/login.dart';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/branding.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/utils/validators.dart';
import 'package:neon/src/widgets/exception.dart';
import 'package:neon/src/widgets/linear_progress_indicator.dart';
@ -99,10 +98,6 @@ class _LoginPageState extends State<LoginPage> {
..addAccount(account)
..setActiveAccount(account);
}
if (mounted) {
const HomeRoute().go(context);
}
} catch (e, s) {
debugPrint(e.toString());
debugPrint(s.toString());

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

@ -106,9 +106,7 @@ class _SettingsPageState extends State<SettingsPage> {
CustomSettingsTile(
leading: appImplementation.buildIcon(),
title: Text(appImplementation.name(context)),
onTap: () {
NextcloudAppSettingsRoute(appid: appImplementation.id).go(context);
},
onTap: () async => NextcloudAppSettingsRoute(appid: appImplementation.id).push(context),
),
],
],
@ -201,16 +199,12 @@ class _SettingsPageState extends State<SettingsPage> {
for (final account in accountsSnapshot.requireData) ...[
AccountSettingsTile(
account: account,
onTap: () {
AccountSettingsRoute(accountid: account.id).go(context);
},
onTap: () async => AccountSettingsRoute(accountid: account.id).push(context),
),
],
CustomSettingsTile(
title: ElevatedButton.icon(
onPressed: () {
const AddAccountRoute().go(context);
},
onPressed: () async => const AddAccountRoute().push(context),
icon: Icon(MdiIcons.accountPlus),
label: Text(AppLocalizations.of(context).globalOptionsAccountsAdd),
),

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

@ -18,8 +18,9 @@ part 'router.g.dart';
@internal
class AppRouter extends GoRouter {
AppRouter({
required final GlobalKey<NavigatorState> navigatorKey,
required this.navigatorKey,
required final AccountsBloc accountsBloc,
required final Iterable<AppImplementation> appImplementations,
}) : super(
debugLogDiagnostics: kDebugMode,
refreshListenable: StreamListenable(accountsBloc.activeAccount),
@ -35,8 +36,30 @@ class AppRouter extends GoRouter {
return null;
},
routes: $appRoutes,
routes: [
StatefulShellRoute.indexedStack(
branches: appImplementations.map((final a) => a.mainBranch).toList(),
builder: _mainViewBuilder,
),
...$appRoutes,
],
);
final GlobalKey<NavigatorState> navigatorKey;
}
Widget _mainViewBuilder(
final BuildContext context,
final GoRouterState state, [
final StatefulNavigationShell? navigationShell,
]) {
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final account = accountsBloc.activeAccount.valueOrNull!;
return HomePage(
appView: navigationShell,
key: Key(account.id),
);
}
@immutable
@ -62,38 +85,14 @@ class AccountSettingsRoute extends GoRouteData {
@TypedGoRoute<HomeRoute>(
path: '/',
name: 'home',
routes: [
TypedGoRoute<SettingsRoute>(
path: 'settings',
name: 'Settings',
routes: [
TypedGoRoute<NextcloudAppSettingsRoute>(
path: 'apps/:appid',
name: 'NextcloudAppSettings',
),
TypedGoRoute<AddAccountRoute>(
path: 'account/add',
name: 'addAccount',
),
TypedGoRoute<AccountSettingsRoute>(
path: 'account/:accountid',
name: 'AccountSettings',
),
],
)
],
)
@immutable
@internal
class HomeRoute extends GoRouteData {
const HomeRoute();
@override
Widget build(final BuildContext context, final GoRouterState state) {
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final account = accountsBloc.activeAccount.valueOrNull!;
return HomePage(key: Key(account.id));
}
Widget build(final BuildContext context, final GoRouterState state) => _mainViewBuilder(context, state);
}
@TypedGoRoute<LoginRoute>(
@ -135,6 +134,24 @@ 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',
),
TypedGoRoute<AccountSettingsRoute>(
path: 'account/:accountid',
name: 'AccountSettings',
),
],
)
@immutable
class SettingsRoute extends GoRouteData {
const SettingsRoute();

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

@ -9,36 +9,13 @@ part of 'router.dart';
List<RouteBase> get $appRoutes => [
$homeRoute,
$loginRoute,
$settingsRoute,
];
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: 'apps/:appid',
name: 'NextcloudAppSettings',
factory: $NextcloudAppSettingsRouteExtension._fromState,
),
GoRouteData.$route(
path: 'account/add',
name: 'addAccount',
factory: $AddAccountRouteExtension._fromState,
),
GoRouteData.$route(
path: 'account/:accountid',
name: 'AccountSettings',
factory: $AccountSettingsRouteExtension._fromState,
),
],
),
],
);
extension $HomeRouteExtension on HomeRoute {
@ -55,6 +32,54 @@ extension $HomeRouteExtension on HomeRoute {
void pushReplacement(BuildContext context) => context.pushReplacement(location);
}
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<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
}
RouteBase get $settingsRoute => GoRouteData.$route(
path: '/settings',
name: 'Settings',
factory: $SettingsRouteExtension._fromState,
routes: [
GoRouteData.$route(
path: 'apps/:appid',
name: 'NextcloudAppSettings',
factory: $NextcloudAppSettingsRouteExtension._fromState,
),
GoRouteData.$route(
path: 'account/add',
name: 'addAccount',
factory: $AddAccountRouteExtension._fromState,
),
GoRouteData.$route(
path: 'account/:accountid',
name: 'AccountSettings',
factory: $AccountSettingsRouteExtension._fromState,
),
],
);
extension $SettingsRouteExtension on SettingsRoute {
static SettingsRoute _fromState(GoRouterState state) => const SettingsRoute();
@ -114,28 +139,3 @@ extension $AccountSettingsRouteExtension on AccountSettingsRoute {
void pushReplacement(BuildContext context) => context.pushReplacement(location);
}
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<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
}

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

@ -42,9 +42,7 @@ class GlobalPopups {
content: Text(AppLocalizations.of(context).firstLaunchGoToSettingsToEnablePushNotifications),
action: SnackBarAction(
label: AppLocalizations.of(context).settings,
onPressed: () {
const SettingsRoute().go(context);
},
onPressed: () => const SettingsRoute().push(context),
),
),
);

4
packages/neon/neon/lib/src/widgets/app_bar.dart

@ -80,9 +80,7 @@ class NeonAppBar extends StatelessWidget implements PreferredSizeWidget {
actions: [
const NotificationIconButton(),
IconButton(
onPressed: () {
AccountSettingsRoute(accountid: account.id).go(context);
},
onPressed: () async => AccountSettingsRoute(accountid: account.id).push(context),
tooltip: AppLocalizations.of(context).settingsAccount,
icon: NeonUserAvatar(
account: account,

4
packages/neon/neon/lib/src/widgets/drawer.dart

@ -60,7 +60,7 @@ class __NeonDrawerState extends State<_NeonDrawer> with SingleTickerProviderStat
late AppsBloc _appsBloc;
late List<AppImplementation> _apps;
int _activeApp = 0;
late int _activeApp;
@override
void initState() {
@ -89,7 +89,7 @@ class __NeonDrawerState extends State<_NeonDrawer> with SingleTickerProviderStat
// selected item is not a registered app like the SettingsPage
if (index >= _apps.length) {
const SettingsRoute().go(context);
unawaited(const SettingsRoute().push(context));
return;
}

Loading…
Cancel
Save