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. 7
      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. 39
      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. 320
      packages/neon/neon/lib/src/router.g.dart
  8. 2
      packages/neon/neon/lib/src/widgets/account_switcher.dart

7
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([]);
@override
BehaviorSubject<Account?> activeAccount = BehaviorSubject<Account?>()
..distinct((final current, final next) => current?.id != next?.id);
BehaviorSubject<Account?> activeAccount = BehaviorSubject<Account?>();
@override
void addAccount(final Account account) {
@ -173,7 +172,9 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState
@override
void setActiveAccount(final Account account) {
activeAccount.add(account);
if (activeAccount.valueOrNull != account) {
activeAccount.add(account);
}
}
@override

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

@ -78,6 +78,10 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
unawaited(_checkCompatibility());
});
activeApp.distinct().listen((final app) {
_router.go(app.initialPath);
});
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.
///
/// All pages of the app must be specified as subroutes.

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

@ -20,9 +20,12 @@ const kQuickBarWidth = kAvatarSize + 20;
class HomePage extends StatefulWidget {
const HomePage({
this.appView,
super.key,
});
final Widget? appView;
@override
State<HomePage> createState() => _HomePageState();
}
@ -123,34 +126,22 @@ 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) {
return const SizedBox();
final appView = widget.appView ??
ResultBuilder<Iterable<AppImplementation>>.behaviorSubject(
stream: _appsBloc.appImplementations,
builder: (final context, final appImplementations) {
if (appImplementations.hasData && appImplementations.requireData.isEmpty) {
return Center(
child: Text(
AppLocalizations.of(context).errorNoCompatibleNextcloudAppsFound,
textAlign: TextAlign.center,
),
);
}
return activeAppIDSnapshot.requireData.page;
return const SizedBox();
},
);
},
);
final body = ValueListenableBuilder<global_options.NavigationMode>(
valueListenable: _globalOptions.navigationMode,

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 appBar = AppBar(
leading: BackButton(
onPressed: () => const HomeRoute().go(context),
),
title: Text(AppLocalizations.of(context).settings),
actions: [
IconButton(

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

@ -62,12 +62,32 @@ class AppRouter extends GoRouter {
return null;
},
routes: $appRoutes,
routes: [
ShellRoute(
routes: appImplementations.map((final a) => a.route).toList(),
builder: _mainViewBuilder,
),
...$appRoutes,
],
);
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
class AccountSettingsRoute extends GoRouteData {
const AccountSettingsRoute({
@ -91,51 +111,20 @@ 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',
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
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!;
Widget build(final BuildContext context, final GoRouterState state) => _mainViewBuilder(context, state);
@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
class SettingsRoute extends GoRouteData {
const SettingsRoute({this.initialCategory});

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

@ -9,54 +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,
routes: [
GoRouteData.$route(
path: 'flow',
factory: $_AddAccountFlowRouteExtension._fromState,
),
GoRouteData.$route(
path: 'qrcode',
factory: $_AddAccountQrcodeRouteExtension._fromState,
),
GoRouteData.$route(
path: 'check/server',
factory: $_AddAccountCheckServerStatusRouteExtension._fromState,
),
GoRouteData.$route(
path: 'check/account',
factory: $_AddAccountCheckAccountRouteExtension._fromState,
),
],
),
GoRouteData.$route(
path: 'account/:accountid',
name: 'AccountSettings',
factory: $AccountSettingsRouteExtension._fromState,
),
],
),
],
);
extension $HomeRouteExtension on HomeRoute {
@ -75,17 +34,35 @@ extension $HomeRouteExtension on HomeRoute {
void replace(BuildContext context) => context.replace(location);
}
extension $SettingsRouteExtension on SettingsRoute {
static SettingsRoute _fromState(GoRouterState state) => SettingsRoute(
initialCategory:
_$convertMapValue('initial-category', state.uri.queryParameters, _$SettingsCageoriesEnumMap._$fromName),
);
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(
'/settings',
queryParams: {
if (initialCategory != null) 'initial-category': _$SettingsCageoriesEnumMap[initialCategory!],
},
'/login',
);
void go(BuildContext context) => context.go(location);
@ -97,24 +74,16 @@ extension $SettingsRouteExtension on SettingsRoute {
void replace(BuildContext context) => context.replace(location);
}
const _$SettingsCageoriesEnumMap = {
SettingsCageories.apps: 'apps',
SettingsCageories.theme: 'theme',
SettingsCageories.navigation: 'navigation',
SettingsCageories.pushNotifications: 'push-notifications',
SettingsCageories.startup: 'startup',
SettingsCageories.systemTray: 'system-tray',
SettingsCageories.accounts: 'accounts',
SettingsCageories.other: 'other',
};
extension $NextcloudAppSettingsRouteExtension on NextcloudAppSettingsRoute {
static NextcloudAppSettingsRoute _fromState(GoRouterState state) => NextcloudAppSettingsRoute(
appid: state.pathParameters['appid']!,
extension $LoginFlowRouteExtension on LoginFlowRoute {
static LoginFlowRoute _fromState(GoRouterState state) => LoginFlowRoute(
serverUrl: state.uri.queryParameters['server-url']!,
);
String get location => GoRouteData.$location(
'/settings/apps/${Uri.encodeComponent(appid)}',
'/login/flow',
queryParams: {
'server-url': serverUrl,
},
);
void go(BuildContext context) => context.go(location);
@ -126,11 +95,11 @@ extension $NextcloudAppSettingsRouteExtension on NextcloudAppSettingsRoute {
void replace(BuildContext context) => context.replace(location);
}
extension $_AddAccountRouteExtension on _AddAccountRoute {
static _AddAccountRoute _fromState(GoRouterState state) => const _AddAccountRoute();
extension $LoginQrcodeRouteExtension on LoginQrcodeRoute {
static LoginQrcodeRoute _fromState(GoRouterState state) => const LoginQrcodeRoute();
String get location => GoRouteData.$location(
'/settings/account/add',
'/login/qrcode',
);
void go(BuildContext context) => context.go(location);
@ -142,15 +111,19 @@ extension $_AddAccountRouteExtension on _AddAccountRoute {
void replace(BuildContext context) => context.replace(location);
}
extension $_AddAccountFlowRouteExtension on _AddAccountFlowRoute {
static _AddAccountFlowRoute _fromState(GoRouterState state) => _AddAccountFlowRoute(
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(
'/settings/account/add/flow',
'/login/check/server',
queryParams: {
'server-url': serverUrl,
if (loginName != null) 'login-name': loginName,
if (password != null) 'password': password,
},
);
@ -163,31 +136,19 @@ extension $_AddAccountFlowRouteExtension on _AddAccountFlowRoute {
void replace(BuildContext context) => context.replace(location);
}
extension $_AddAccountQrcodeRouteExtension on _AddAccountQrcodeRoute {
static _AddAccountQrcodeRoute _fromState(GoRouterState state) => const _AddAccountQrcodeRoute();
String get location => GoRouteData.$location(
'/settings/account/add/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 $_AddAccountCheckServerStatusRouteExtension on _AddAccountCheckServerStatusRoute {
static _AddAccountCheckServerStatusRoute _fromState(GoRouterState state) => _AddAccountCheckServerStatusRoute(
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(
'/settings/account/add/check/server',
'/login/check/account',
queryParams: {
'server-url': serverUrl,
'login-name': loginName,
'password': password,
},
);
@ -200,19 +161,57 @@ extension $_AddAccountCheckServerStatusRouteExtension on _AddAccountCheckServerS
void replace(BuildContext context) => context.replace(location);
}
extension $_AddAccountCheckAccountRouteExtension on _AddAccountCheckAccountRoute {
static _AddAccountCheckAccountRoute _fromState(GoRouterState state) => _AddAccountCheckAccountRoute(
serverUrl: state.uri.queryParameters['server-url']!,
loginName: state.uri.queryParameters['login-name']!,
password: state.uri.queryParameters['password']!,
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,
routes: [
GoRouteData.$route(
path: 'flow',
factory: $_AddAccountFlowRouteExtension._fromState,
),
GoRouteData.$route(
path: 'qrcode',
factory: $_AddAccountQrcodeRouteExtension._fromState,
),
GoRouteData.$route(
path: 'check/server',
factory: $_AddAccountCheckServerStatusRouteExtension._fromState,
),
GoRouteData.$route(
path: 'check/account',
factory: $_AddAccountCheckAccountRouteExtension._fromState,
),
],
),
GoRouteData.$route(
path: 'account/:accountid',
name: 'AccountSettings',
factory: $AccountSettingsRouteExtension._fromState,
),
],
);
extension $SettingsRouteExtension on SettingsRoute {
static SettingsRoute _fromState(GoRouterState state) => SettingsRoute(
initialCategory:
_$convertMapValue('initial-category', state.uri.queryParameters, _$SettingsCageoriesEnumMap._$fromName),
);
String get location => GoRouteData.$location(
'/settings/account/add/check/account',
'/settings',
queryParams: {
'server-url': serverUrl,
'login-name': loginName,
'password': password,
if (initialCategory != null) 'initial-category': _$SettingsCageoriesEnumMap[initialCategory!],
},
);
@ -225,13 +224,24 @@ extension $_AddAccountCheckAccountRouteExtension on _AddAccountCheckAccountRoute
void replace(BuildContext context) => context.replace(location);
}
extension $AccountSettingsRouteExtension on AccountSettingsRoute {
static AccountSettingsRoute _fromState(GoRouterState state) => AccountSettingsRoute(
accountid: state.pathParameters['accountid']!,
const _$SettingsCageoriesEnumMap = {
SettingsCageories.apps: 'apps',
SettingsCageories.theme: 'theme',
SettingsCageories.navigation: 'navigation',
SettingsCageories.pushNotifications: 'push-notifications',
SettingsCageories.startup: 'startup',
SettingsCageories.systemTray: 'system-tray',
SettingsCageories.accounts: 'accounts',
SettingsCageories.other: 'other',
};
extension $NextcloudAppSettingsRouteExtension on NextcloudAppSettingsRoute {
static NextcloudAppSettingsRoute _fromState(GoRouterState state) => NextcloudAppSettingsRoute(
appid: state.pathParameters['appid']!,
);
String get location => GoRouteData.$location(
'/settings/account/${Uri.encodeComponent(accountid)}',
'/settings/apps/${Uri.encodeComponent(appid)}',
);
void go(BuildContext context) => context.go(location);
@ -243,48 +253,11 @@ extension $AccountSettingsRouteExtension on AccountSettingsRoute {
void replace(BuildContext context) => context.replace(location);
}
T? _$convertMapValue<T>(
String key,
Map<String, String> map,
T Function(String) converter,
) {
final value = map[key];
return value == null ? null : converter(value);
}
extension<T extends Enum> on Map<T, String> {
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();
extension $_AddAccountRouteExtension on _AddAccountRoute {
static _AddAccountRoute _fromState(GoRouterState state) => const _AddAccountRoute();
String get location => GoRouteData.$location(
'/login',
'/settings/account/add',
);
void go(BuildContext context) => context.go(location);
@ -296,13 +269,13 @@ extension $LoginRouteExtension on LoginRoute {
void replace(BuildContext context) => context.replace(location);
}
extension $LoginFlowRouteExtension on LoginFlowRoute {
static LoginFlowRoute _fromState(GoRouterState state) => LoginFlowRoute(
extension $_AddAccountFlowRouteExtension on _AddAccountFlowRoute {
static _AddAccountFlowRoute _fromState(GoRouterState state) => _AddAccountFlowRoute(
serverUrl: state.uri.queryParameters['server-url']!,
);
String get location => GoRouteData.$location(
'/login/flow',
'/settings/account/add/flow',
queryParams: {
'server-url': serverUrl,
},
@ -317,11 +290,11 @@ extension $LoginFlowRouteExtension on LoginFlowRoute {
void replace(BuildContext context) => context.replace(location);
}
extension $LoginQrcodeRouteExtension on LoginQrcodeRoute {
static LoginQrcodeRoute _fromState(GoRouterState state) => const LoginQrcodeRoute();
extension $_AddAccountQrcodeRouteExtension on _AddAccountQrcodeRoute {
static _AddAccountQrcodeRoute _fromState(GoRouterState state) => const _AddAccountQrcodeRoute();
String get location => GoRouteData.$location(
'/login/qrcode',
'/settings/account/add/qrcode',
);
void go(BuildContext context) => context.go(location);
@ -333,19 +306,15 @@ extension $LoginQrcodeRouteExtension on LoginQrcodeRoute {
void replace(BuildContext context) => context.replace(location);
}
extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute {
static LoginCheckServerStatusRoute _fromState(GoRouterState state) => LoginCheckServerStatusRoute(
extension $_AddAccountCheckServerStatusRouteExtension on _AddAccountCheckServerStatusRoute {
static _AddAccountCheckServerStatusRoute _fromState(GoRouterState state) => _AddAccountCheckServerStatusRoute(
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',
'/settings/account/add/check/server',
queryParams: {
'server-url': serverUrl,
if (loginName != null) 'login-name': loginName,
if (password != null) 'password': password,
},
);
@ -358,15 +327,15 @@ extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute {
void replace(BuildContext context) => context.replace(location);
}
extension $LoginCheckAccountRouteExtension on LoginCheckAccountRoute {
static LoginCheckAccountRoute _fromState(GoRouterState state) => LoginCheckAccountRoute(
extension $_AddAccountCheckAccountRouteExtension on _AddAccountCheckAccountRoute {
static _AddAccountCheckAccountRoute _fromState(GoRouterState state) => _AddAccountCheckAccountRoute(
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',
'/settings/account/add/check/account',
queryParams: {
'server-url': serverUrl,
'login-name': loginName,
@ -382,3 +351,34 @@ extension $LoginCheckAccountRouteExtension on LoginCheckAccountRoute {
void replace(BuildContext context) => context.replace(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);
void replace(BuildContext context) => context.replace(location);
}
T? _$convertMapValue<T>(
String key,
Map<String, String> map,
T Function(String) converter,
) {
final value = map[key];
return value == null ? null : converter(value);
}
extension<T extends Enum> on Map<T, String> {
T _$fromName(String value) => entries.singleWhere((element) => element.value == value).key;
}

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

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

Loading…
Cancel
Save