diff --git a/packages/neon/neon/lib/settings.dart b/packages/neon/neon/lib/settings.dart index 13193417..08d29b5c 100644 --- a/packages/neon/neon/lib/settings.dart +++ b/packages/neon/neon/lib/settings.dart @@ -1,6 +1,6 @@ export 'package:neon/src/models/label_builder.dart'; -export 'package:neon/src/settings/models/nextcloud_app_options.dart'; export 'package:neon/src/settings/models/options_category.dart'; +export 'package:neon/src/settings/models/options_collection.dart'; export 'package:neon/src/settings/models/select_option.dart'; export 'package:neon/src/settings/models/storage.dart' show Storable; export 'package:neon/src/settings/models/toggle_option.dart'; diff --git a/packages/neon/neon/lib/src/blocs/apps.dart b/packages/neon/neon/lib/src/blocs/apps.dart index b0586b37..6f35a3ec 100644 --- a/packages/neon/neon/lib/src/blocs/apps.dart +++ b/packages/neon/neon/lib/src/blocs/apps.dart @@ -10,7 +10,7 @@ import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/app_ids.dart'; import 'package:neon/src/models/app_implementation.dart'; import 'package:neon/src/models/notifications_interface.dart'; -import 'package:neon/src/settings/models/nextcloud_app_options.dart'; +import 'package:neon/src/settings/models/options_collection.dart'; import 'package:neon/src/utils/request_manager.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:provider/provider.dart'; diff --git a/packages/neon/neon/lib/src/models/app_implementation.dart b/packages/neon/neon/lib/src/models/app_implementation.dart index 1051a571..4c50508c 100644 --- a/packages/neon/neon/lib/src/models/app_implementation.dart +++ b/packages/neon/neon/lib/src/models/app_implementation.dart @@ -7,7 +7,7 @@ import 'package:neon/l10n/localizations.dart'; import 'package:neon/src/bloc/bloc.dart'; import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/models/account.dart'; -import 'package:neon/src/settings/models/nextcloud_app_options.dart'; +import 'package:neon/src/settings/models/options_collection.dart'; import 'package:neon/src/settings/models/storage.dart'; import 'package:neon/src/widgets/drawer_destination.dart'; import 'package:provider/provider.dart'; diff --git a/packages/neon/neon/lib/src/models/notifications_interface.dart b/packages/neon/neon/lib/src/models/notifications_interface.dart index 7164dc24..abcc1356 100644 --- a/packages/neon/neon/lib/src/models/notifications_interface.dart +++ b/packages/neon/neon/lib/src/models/notifications_interface.dart @@ -1,7 +1,7 @@ import 'package:meta/meta.dart'; import 'package:neon/src/bloc/bloc.dart'; import 'package:neon/src/models/app_implementation.dart'; -import 'package:neon/src/settings/models/nextcloud_app_options.dart'; +import 'package:neon/src/settings/models/options_collection.dart'; abstract interface class NotificationsAppInterface extends AppImplementation { diff --git a/packages/neon/neon/lib/src/pages/settings.dart b/packages/neon/neon/lib/src/pages/settings.dart index 92a2fa58..a1e2bd70 100644 --- a/packages/neon/neon/lib/src/pages/settings.dart +++ b/packages/neon/neon/lib/src/pages/settings.dart @@ -1,6 +1,3 @@ -import 'dart:convert'; -import 'dart:typed_data'; - import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @@ -10,6 +7,7 @@ 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/utils/settings_export_helper.dart'; import 'package:neon/src/settings/widgets/account_settings_tile.dart'; import 'package:neon/src/settings/widgets/checkbox_settings_tile.dart'; import 'package:neon/src/settings/widgets/custom_settings_tile.dart'; @@ -23,7 +21,6 @@ import 'package:neon/src/theme/dialog.dart'; import 'package:neon/src/utils/confirmation_dialog.dart'; import 'package:neon/src/utils/global_options.dart'; import 'package:neon/src/utils/save_file.dart'; -import 'package:neon/src/utils/settings_export_helper.dart'; import 'package:neon/src/widgets/exception.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; @@ -250,16 +247,10 @@ class _SettingsPageState extends State { final settingsExportHelper = _buildSettingsExportHelper(context); try { - final fileName = - 'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json.base64'; - final data = base64.encode( - utf8.encode( - json.encode( - settingsExportHelper.toJsonExport(), - ), - ), - ); - await saveFileWithPickDialog(fileName, utf8.encode(data) as Uint8List); + final fileName = 'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json'; + + final data = settingsExportHelper.exportToFile(); + await saveFileWithPickDialog(fileName, data); } catch (e, s) { debugPrint(e.toString()); debugPrint(s.toString()); @@ -280,14 +271,14 @@ class _SettingsPageState extends State { try { final result = await FilePicker.platform.pickFiles( - withData: true, + withReadStream: true, ); if (result == null) { return; } - if (!result.files.single.path!.endsWith('.json.base64')) { + if (!result.files.single.path!.endsWith('.json')) { if (mounted) { NeonException.showSnackbar( context, @@ -297,9 +288,7 @@ class _SettingsPageState extends State { return; } - final data = json.decode(utf8.decode(base64.decode(utf8.decode(result.files.single.bytes!)))); - - await settingsExportHelper.applyFromJson(data as Map); + await settingsExportHelper.applyFromFile(result.files.single.readStream); } catch (e, s) { debugPrint(e.toString()); debugPrint(s.toString()); @@ -329,16 +318,16 @@ class _SettingsPageState extends State { } SettingsExportHelper _buildSettingsExportHelper(final BuildContext context) { - final globalOptions = Provider.of(context); + final globalOptions = Provider.of(context, listen: false); final accountsBloc = Provider.of(context, listen: false); - final appImplementations = Provider.of>(context); + final appImplementations = Provider.of>(context, listen: false); return SettingsExportHelper( - globalOptions: globalOptions, - appImplementations: appImplementations, - accountSpecificOptions: accountsBloc.accounts.value.asMap().map( - (final _, final account) => MapEntry(account, accountsBloc.getOptionsFor(account).options), - ), + exportables: { + globalOptions, + AccountsBlocExporter(accountsBloc), + AppImplementationsExporter(appImplementations), + }, ); } } diff --git a/packages/neon/neon/lib/src/settings/models/exportable.dart b/packages/neon/neon/lib/src/settings/models/exportable.dart new file mode 100644 index 00000000..d2aa8152 --- /dev/null +++ b/packages/neon/neon/lib/src/settings/models/exportable.dart @@ -0,0 +1,40 @@ +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:neon/src/settings/models/option.dart'; + +/// Exportable data interface. +abstract interface class Exportable { + /// Exports into a json map entry. + MapEntry export(); + + /// Imports [data] from an export. + FutureOr import(final Map data); +} + +/// Serialization helpers for a collection of [Option]s. +extension SerializeOptions on Iterable> { + /// Serializes into an [Iterable]. + /// + /// Use [Map.fromEntries] to get a json Map. + Iterable> serialize() sync* { + for (final option in this) { + if (option.enabled) { + yield MapEntry(option.key.value, option.serialize()); + } + } + } + + /// Deserializes [data] and updates the [Option]s. + void deserialize(final Map data) { + for (final entry in data.entries) { + final option = firstWhereOrNull((final option) => option.key.value == entry.key); + + if (entry.value != null) { + option?.load(entry.value); + } else { + option?.reset(); + } + } + } +} diff --git a/packages/neon/neon/lib/src/settings/models/nextcloud_app_options.dart b/packages/neon/neon/lib/src/settings/models/nextcloud_app_options.dart deleted file mode 100644 index 53b32f10..00000000 --- a/packages/neon/neon/lib/src/settings/models/nextcloud_app_options.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:neon/src/settings/models/option.dart'; -import 'package:neon/src/settings/models/options_category.dart'; -import 'package:neon/src/settings/models/storage.dart'; - -abstract class NextcloudAppOptions { - NextcloudAppOptions(this.storage); - - final AppStorage storage; - late final List categories; - late final List