Browse Source

neon: add ability to scroll to a specific setting

pull/456/head
Nikolas Rimikis 2 years ago
parent
commit
58e8392dca
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 8
      packages/app/pubspec.lock
  2. 70
      packages/neon/neon/lib/src/pages/settings.dart
  3. 7
      packages/neon/neon/lib/src/router.dart
  4. 50
      packages/neon/neon/lib/src/router.g.dart
  5. 25
      packages/neon/neon/lib/src/settings/widgets/settings_list.dart
  6. 1
      packages/neon/neon/pubspec.yaml

8
packages/app/pubspec.lock

@ -1037,6 +1037,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.6" version: "0.1.6"
scrollable_positioned_list:
dependency: transitive
description:
name: scrollable_positioned_list
sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287"
url: "https://pub.dev"
source: hosted
version: "0.3.8"
share_plus: share_plus:
dependency: transitive dependency: transitive
description: description:

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

@ -27,11 +27,25 @@ import 'package:neon/src/widgets/exception.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
enum SettingsCageories {
apps,
theme,
navigation,
pushNotifications,
startup,
systemTray,
accounts,
other,
}
class SettingsPage extends StatefulWidget { class SettingsPage extends StatefulWidget {
const SettingsPage({ const SettingsPage({
this.initialCategory,
super.key, super.key,
}); });
final SettingsCageories? initialCategory;
@override @override
State<SettingsPage> createState() => _SettingsPageState(); State<SettingsPage> createState() => _SettingsPageState();
} }
@ -42,6 +56,7 @@ class _SettingsPageState extends State<SettingsPage> {
final globalOptions = Provider.of<GlobalOptions>(context); final globalOptions = Provider.of<GlobalOptions>(context);
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false); final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final appImplementations = Provider.of<Iterable<AppImplementation>>(context); final appImplementations = Provider.of<Iterable<AppImplementation>>(context);
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
appBar: AppBar( appBar: AppBar(
@ -68,38 +83,25 @@ class _SettingsPageState extends State<SettingsPage> {
), ),
body: StreamBuilder<List<Account>>( body: StreamBuilder<List<Account>>(
stream: accountsBloc.accounts, stream: accountsBloc.accounts,
initialData: accountsBloc.accounts.valueOrNull,
builder: ( builder: (
final context, final context,
final accountsSnapshot, final accountsSnapshot,
) { ) {
final settingsExportHelper = SettingsExportHelper(
globalOptions: globalOptions,
appImplementations: appImplementations,
accountSpecificOptions: {
if (accountsSnapshot.hasData) ...{
for (final account in accountsSnapshot.requireData) ...{
account: accountsBloc.getOptionsFor(account).options,
},
},
},
);
final platform = Provider.of<NeonPlatform>(context, listen: false); final platform = Provider.of<NeonPlatform>(context, listen: false);
return StreamBuilder<Account?>( return StreamBuilder<bool>(
stream: accountsBloc.activeAccount,
builder: (
final context,
final activeAccountSnapshot,
) =>
StreamBuilder<bool>(
stream: globalOptions.pushNotificationsEnabled.enabled, stream: globalOptions.pushNotificationsEnabled.enabled,
initialData: globalOptions.pushNotificationsEnabled.enabled.valueOrNull,
builder: ( builder: (
final context, final context,
final pushNotificationsEnabledEnabledSnapshot, final pushNotificationsEnabledEnabledSnapshot,
) => ) =>
SettingsList( SettingsList(
initialCategory: widget.initialCategory?.name,
categories: [ categories: [
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).settingsApps), title: Text(AppLocalizations.of(context).settingsApps),
key: ValueKey(SettingsCageories.apps.name),
tiles: <SettingsTile>[ tiles: <SettingsTile>[
for (final appImplementation in appImplementations) ...[ for (final appImplementation in appImplementations) ...[
if (appImplementation.options.options.isNotEmpty) ...[ if (appImplementation.options.options.isNotEmpty) ...[
@ -116,6 +118,7 @@ class _SettingsPageState extends State<SettingsPage> {
), ),
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryTheme), title: Text(AppLocalizations.of(context).optionsCategoryTheme),
key: ValueKey(SettingsCageories.theme.name),
tiles: [ tiles: [
DropdownButtonSettingsTile( DropdownButtonSettingsTile(
option: globalOptions.themeMode, option: globalOptions.themeMode,
@ -130,6 +133,7 @@ class _SettingsPageState extends State<SettingsPage> {
), ),
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryNavigation), title: Text(AppLocalizations.of(context).optionsCategoryNavigation),
key: ValueKey(SettingsCageories.navigation.name),
tiles: [ tiles: [
DropdownButtonSettingsTile( DropdownButtonSettingsTile(
option: globalOptions.navigationMode, option: globalOptions.navigationMode,
@ -139,6 +143,7 @@ class _SettingsPageState extends State<SettingsPage> {
if (platform.canUsePushNotifications) ...[ if (platform.canUsePushNotifications) ...[
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications), title: Text(AppLocalizations.of(context).optionsCategoryPushNotifications),
key: ValueKey(SettingsCageories.pushNotifications.name),
tiles: [ tiles: [
if (pushNotificationsEnabledEnabledSnapshot.hasData && if (pushNotificationsEnabledEnabledSnapshot.hasData &&
!pushNotificationsEnabledEnabledSnapshot.requireData) ...[ !pushNotificationsEnabledEnabledSnapshot.requireData) ...[
@ -163,6 +168,7 @@ class _SettingsPageState extends State<SettingsPage> {
if (platform.canUseWindowManager) ...[ if (platform.canUseWindowManager) ...[
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryStartup), title: Text(AppLocalizations.of(context).optionsCategoryStartup),
key: ValueKey(SettingsCageories.startup.name),
tiles: [ tiles: [
CheckBoxSettingsTile( CheckBoxSettingsTile(
option: globalOptions.startupMinimized, option: globalOptions.startupMinimized,
@ -176,6 +182,7 @@ class _SettingsPageState extends State<SettingsPage> {
if (platform.canUseWindowManager && platform.canUseSystemTray) ...[ if (platform.canUseWindowManager && platform.canUseSystemTray) ...[
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategorySystemTray), title: Text(AppLocalizations.of(context).optionsCategorySystemTray),
key: ValueKey(SettingsCageories.systemTray.name),
tiles: [ tiles: [
CheckBoxSettingsTile( CheckBoxSettingsTile(
option: globalOptions.systemTrayEnabled, option: globalOptions.systemTrayEnabled,
@ -186,9 +193,9 @@ class _SettingsPageState extends State<SettingsPage> {
], ],
), ),
], ],
if (accountsSnapshot.hasData) ...[
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryAccounts), title: Text(AppLocalizations.of(context).optionsCategoryAccounts),
key: ValueKey(SettingsCageories.accounts.name),
tiles: [ tiles: [
if (accountsSnapshot.requireData.length > 1) ...[ if (accountsSnapshot.requireData.length > 1) ...[
CheckBoxSettingsTile( CheckBoxSettingsTile(
@ -215,9 +222,9 @@ class _SettingsPageState extends State<SettingsPage> {
) )
], ],
), ),
],
SettingsCategory( SettingsCategory(
title: Text(AppLocalizations.of(context).optionsCategoryOther), title: Text(AppLocalizations.of(context).optionsCategoryOther),
key: ValueKey(SettingsCageories.other.name),
tiles: <SettingsTile>[ tiles: <SettingsTile>[
CustomSettingsTile( CustomSettingsTile(
leading: Icon( leading: Icon(
@ -243,6 +250,8 @@ class _SettingsPageState extends State<SettingsPage> {
), ),
title: Text(AppLocalizations.of(context).settingsExport), title: Text(AppLocalizations.of(context).settingsExport),
onTap: () async { onTap: () async {
final settingsExportHelper = _buildSettingsExportHelper(context);
try { try {
final fileName = final fileName =
'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json.base64'; 'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json.base64';
@ -268,6 +277,8 @@ class _SettingsPageState extends State<SettingsPage> {
), ),
title: Text(AppLocalizations.of(context).settingsImport), title: Text(AppLocalizations.of(context).settingsImport),
onTap: () async { onTap: () async {
final settingsExportHelper = _buildSettingsExportHelper(context);
try { try {
final result = await FilePicker.platform.pickFiles( final result = await FilePicker.platform.pickFiles(
withData: true, withData: true,
@ -287,8 +298,8 @@ class _SettingsPageState extends State<SettingsPage> {
return; return;
} }
final data = final data = json.decode(utf8.decode(base64.decode(utf8.decode(result.files.single.bytes!))));
json.decode(utf8.decode(base64.decode(utf8.decode(result.files.single.bytes!))));
await settingsExportHelper.applyFromJson(data as Map<String, dynamic>); await settingsExportHelper.applyFromJson(data as Map<String, dynamic>);
} catch (e, s) { } catch (e, s) {
debugPrint(e.toString()); debugPrint(e.toString());
@ -301,12 +312,25 @@ class _SettingsPageState extends State<SettingsPage> {
), ),
], ],
), ),
),
); );
}, },
), ),
); );
} }
SettingsExportHelper _buildSettingsExportHelper(final BuildContext context) {
final globalOptions = Provider.of<GlobalOptions>(context);
final accountsBloc = Provider.of<AccountsBloc>(context, listen: false);
final appImplementations = Provider.of<Iterable<AppImplementation>>(context);
return SettingsExportHelper(
globalOptions: globalOptions,
appImplementations: appImplementations,
accountSpecificOptions: accountsBloc.accounts.value.asMap().map(
(final _, final account) => MapEntry(account, accountsBloc.getOptionsFor(account).options),
),
);
}
} }
enum SettingsAccountAction { enum SettingsAccountAction {

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

@ -367,8 +367,11 @@ class NextcloudAppSettingsRoute extends GoRouteData {
@immutable @immutable
class SettingsRoute extends GoRouteData { class SettingsRoute extends GoRouteData {
const SettingsRoute(); const SettingsRoute({this.initialCategory});
/// The initial category to show.
final SettingsCageories? initialCategory;
@override @override
Widget build(final BuildContext context, final GoRouterState state) => const SettingsPage(); Widget build(final BuildContext context, final GoRouterState state) => SettingsPage(initialCategory: initialCategory);
} }

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

@ -71,13 +71,21 @@ extension $HomeRouteExtension on HomeRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location); Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(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) => const SettingsRoute(); static SettingsRoute _fromState(GoRouterState state) => SettingsRoute(
initialCategory:
_$convertMapValue('initial-category', state.queryParameters, _$SettingsCageoriesEnumMap._$fromName),
);
String get location => GoRouteData.$location( String get location => GoRouteData.$location(
'/settings', '/settings',
queryParams: {
if (initialCategory != null) 'initial-category': _$SettingsCageoriesEnumMap[initialCategory!],
},
); );
void go(BuildContext context) => context.go(location); void go(BuildContext context) => context.go(location);
@ -85,8 +93,21 @@ extension $SettingsRouteExtension on SettingsRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location); Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location); void pushReplacement(BuildContext context) => context.pushReplacement(location);
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 { extension $NextcloudAppSettingsRouteExtension on NextcloudAppSettingsRoute {
static NextcloudAppSettingsRoute _fromState(GoRouterState state) => NextcloudAppSettingsRoute( static NextcloudAppSettingsRoute _fromState(GoRouterState state) => NextcloudAppSettingsRoute(
appid: state.pathParameters['appid']!, appid: state.pathParameters['appid']!,
@ -101,6 +122,8 @@ extension $NextcloudAppSettingsRouteExtension on NextcloudAppSettingsRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location); Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location); void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
} }
extension $_AddAccountRouteExtension on _AddAccountRoute { extension $_AddAccountRouteExtension on _AddAccountRoute {
@ -115,6 +138,8 @@ extension $_AddAccountRouteExtension on _AddAccountRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location); Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location); void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
} }
extension $_AddAccountFlowRouteExtension on _AddAccountFlowRoute { extension $_AddAccountFlowRouteExtension on _AddAccountFlowRoute {
@ -206,6 +231,21 @@ extension $AccountSettingsRouteExtension on AccountSettingsRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location); Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(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;
} }
RouteBase get $loginRoute => GoRouteData.$route( RouteBase get $loginRoute => GoRouteData.$route(
@ -244,6 +284,8 @@ extension $LoginRouteExtension on LoginRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location); Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location); void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
} }
extension $LoginFlowRouteExtension on LoginFlowRoute { extension $LoginFlowRouteExtension on LoginFlowRoute {
@ -277,6 +319,8 @@ extension $LoginQrcodeRouteExtension on LoginQrcodeRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location); Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location); void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
} }
extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute { extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute {
@ -296,6 +340,8 @@ extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location); Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location); void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
} }
extension $LoginCheckAccountRouteExtension on LoginCheckAccountRoute { extension $LoginCheckAccountRouteExtension on LoginCheckAccountRoute {
@ -319,4 +365,6 @@ extension $LoginCheckAccountRouteExtension on LoginCheckAccountRoute {
Future<T?> push<T>(BuildContext context) => context.push<T>(location); Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location); void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
} }

25
packages/neon/neon/lib/src/settings/widgets/settings_list.dart

@ -1,23 +1,36 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intersperse/intersperse.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:neon/src/settings/widgets/settings_category.dart'; import 'package:neon/src/settings/widgets/settings_category.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
@internal @internal
class SettingsList extends StatelessWidget { class SettingsList extends StatelessWidget {
const SettingsList({ const SettingsList({
required this.categories, required this.categories,
this.initialCategory,
super.key, super.key,
}); });
final List<SettingsCategory> categories; final List<SettingsCategory> categories;
final String? initialCategory;
int? _getIndex(final String? initialCategory) {
if (initialCategory == null) {
return null;
}
final key = Key(initialCategory);
final index = categories.indexWhere((final category) => category.key == key);
return index != -1 ? index : null;
}
@override @override
Widget build(final BuildContext context) => Scrollbar( Widget build(final BuildContext context) => ScrollablePositionedList.separated(
child: ListView(
primary: true,
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
children: categories.cast<Widget>().intersperse(const Divider()).toList(), itemCount: categories.length,
), initialScrollIndex: _getIndex(initialCategory) ?? 0,
itemBuilder: (final context, final index) => categories[index],
separatorBuilder: (final context, final index) => const Divider(),
); );
} }

1
packages/neon/neon/pubspec.yaml

@ -40,6 +40,7 @@ dependencies:
provider: ^6.0.5 provider: ^6.0.5
quick_actions: ^1.0.3 quick_actions: ^1.0.3
rxdart: ^0.27.7 rxdart: ^0.27.7
scrollable_positioned_list: ^0.3.8
shared_preferences: ^2.1.1 shared_preferences: ^2.1.1
sort_box: sort_box:
git: git:

Loading…
Cancel
Save