Commit 5fb33a0

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2023-10-06 14:02:56
add settings import and export
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 285aaf5
Changed files (5)
lib/l10n/app_en.arb
@@ -428,8 +428,12 @@
   "@note": {},
   "color": "Color",
   "@color": {},
-  "exportConfigDB": "export config.db",
-  "@exportConfigDB": {},
-  "configDBExplainer": "The config.db does not include all configuration info. For more debug information use the copy button at the top.",
-  "@configDBExplainer": {}
+  "exportSettings": "Backup settings",
+  "@exportSettings": {},
+  "importSettings": "Restore settings",
+  "@importSettings": {},
+  "requiresAppRestart": "Requires app restart",
+  "@requiresAppRestart": {},
+  "pleaseRestart": "Please restart the app to apply changes",
+  "@pleaseRestart": {}
 }
lib/screens/subsettings/version.dart
@@ -1,14 +1,9 @@
-import 'dart:io';
-
 import 'package:blood_pressure_app/components/consistent_future_builder.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:package_info_plus/package_info_plus.dart';
-import 'package:path/path.dart';
-import 'package:share_plus/share_plus.dart';
 import 'package:shared_preferences/shared_preferences.dart';
-import 'package:sqflite/sqflite.dart';
 
 class VersionScreen extends StatelessWidget {
 
@@ -56,31 +51,6 @@ class VersionScreen extends StatelessWidget {
                       Text(localizations.versionOf(packageInfo.version)),
                       Text(localizations.buildNumberOf(packageInfo.buildNumber)),
                       Text(localizations.buildSignatureOf(packageInfo.buildSignature)),
-                      Container(
-                        padding: const EdgeInsets.symmetric(vertical: 20),
-                        child: Column(
-                          children: [
-                            Center(
-                              child: FilledButton.icon(
-                                onPressed: () async {
-                                  String dbPath = await getDatabasesPath();
-
-                                  assert(dbPath != inMemoryDatabasePath);
-                                  dbPath = join(dbPath, 'config.db');
-                                  assert(Platform.isAndroid);
-                                  Share.shareXFiles([
-                                    XFile(dbPath,)
-                                  ]);
-                                },
-                                icon: const Icon(Icons.share),
-                                label: Text(localizations.exportConfigDB),
-                              ),
-                            ),
-                            Text(localizations.configDBExplainer,
-                                style: Theme.of(context).textTheme.labelMedium,),
-                          ],
-                        ),
-                      ),
                       Text(localizations.sharedPrefsDump),
                       Table(
                         children: [
lib/screens/settings.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
 import 'package:blood_pressure_app/components/consistent_future_builder.dart';
 import 'package:blood_pressure_app/components/input_dialoge.dart';
 import 'package:blood_pressure_app/components/settings_widgets.dart';
@@ -10,11 +12,16 @@ import 'package:blood_pressure_app/screens/subsettings/export_import_screen.dart
 import 'package:blood_pressure_app/screens/subsettings/graph_markings.dart';
 import 'package:blood_pressure_app/screens/subsettings/version.dart';
 import 'package:blood_pressure_app/screens/subsettings/warn_about.dart';
+import 'package:file_picker/file_picker.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:package_info_plus/package_info_plus.dart';
+import 'package:path/path.dart';
 import 'package:provider/provider.dart';
+import 'package:restart_app/restart_app.dart';
+import 'package:share_plus/share_plus.dart';
+import 'package:sqflite/sqflite.dart';
 import 'package:url_launcher/url_launcher.dart';
 
 class SettingsPage extends StatelessWidget {
@@ -22,18 +29,19 @@ class SettingsPage extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
+    final localizations = AppLocalizations.of(context)!;
     return Scaffold(
       appBar: AppBar(
-        title: Text(AppLocalizations.of(context)!.settings),
+        title: Text(localizations.settings),
         backgroundColor: Theme.of(context).primaryColor,
       ),
       body: Consumer<Settings>(builder: (context, settings, child) {
         return ListView(
           children: [
-            SettingsSection(title: Text(AppLocalizations.of(context)!.layout), children: [
+            SettingsSection(title: Text(localizations.layout), children: [
               SettingsTile(
                 key: const Key('EnterTimeFormatScreen'),
-                title: Text(AppLocalizations.of(context)!.enterTimeFormatScreen),
+                title: Text(localizations.enterTimeFormatScreen),
                 leading: const Icon(Icons.schedule),
                 trailing: const Icon(Icons.arrow_forward_ios),
                 description: Text(settings.dateFormatString),
@@ -47,12 +55,12 @@ class SettingsPage extends StatelessWidget {
               DropDownSettingsTile<int>(
                 key: const Key('thema'),
                 leading: const Icon(Icons.brightness_4),
-                title: Text(AppLocalizations.of(context)!.theme),
+                title: Text(localizations.theme),
                 value: settings.followSystemDarkMode ? 0 : (settings.darkMode ? 1 : 2),
                 items: [
-                  DropdownMenuItem(value: 0, child: Text(AppLocalizations.of(context)!.system)),
-                  DropdownMenuItem(value: 1, child: Text(AppLocalizations.of(context)!.dark)),
-                  DropdownMenuItem(value: 2, child: Text(AppLocalizations.of(context)!.light))
+                  DropdownMenuItem(value: 0, child: Text(localizations.system)),
+                  DropdownMenuItem(value: 1, child: Text(localizations.dark)),
+                  DropdownMenuItem(value: 2, child: Text(localizations.light))
                 ],
                 onChanged: (int? value) {
                   switch (value) {
@@ -76,14 +84,14 @@ class SettingsPage extends StatelessWidget {
                 key: const Key('accentColor'),
                 onMainColorChanged: (color) => settings.accentColor = createMaterialColor((color ?? Colors.teal).value),
                 initialColor: settings.accentColor,
-                title: Text(AppLocalizations.of(context)!.accentColor)),
+                title: Text(localizations.accentColor)),
               DropDownSettingsTile<Locale?>(
                 key: const Key('language'),
                 leading: const Icon(Icons.language),
-                title: Text(AppLocalizations.of(context)!.language),
+                title: Text(localizations.language),
                 value: settings.language,
                 items: [
-                  DropdownMenuItem(value: null, child: Text(AppLocalizations.of(context)!.system)),
+                  DropdownMenuItem(value: null, child: Text(localizations.system)),
                   for (final l in AppLocalizations.supportedLocales)
                     DropdownMenuItem(value: l, child: Text(getDisplayLanguage(l) ?? l.languageCode)),
                 ],
@@ -93,7 +101,7 @@ class SettingsPage extends StatelessWidget {
               ),
               SliderSettingsTile(
                 key: const Key('graphLineThickness'),
-                title: Text(AppLocalizations.of(context)!.graphLineThickness),
+                title: Text(localizations.graphLineThickness),
                 leading: const Icon(Icons.line_weight),
                 onChanged: (double value) {
                   settings.graphLineThickness = value;
@@ -105,7 +113,7 @@ class SettingsPage extends StatelessWidget {
               ),
               SliderSettingsTile(
                 key: const Key('animationSpeed'),
-                title: Text(AppLocalizations.of(context)!.animationSpeed),
+                title: Text(localizations.animationSpeed),
                 leading: const Icon(Icons.speed),
                 onChanged: (double value) {
                   settings.animationSpeed = value.toInt();
@@ -119,17 +127,17 @@ class SettingsPage extends StatelessWidget {
                 key: const Key('sysColor'),
                 onMainColorChanged: (color) => settings.sysColor = createMaterialColor((color ?? Colors.green).value),
                 initialColor: settings.sysColor,
-                  title: Text(AppLocalizations.of(context)!.sysColor)),
+                  title: Text(localizations.sysColor)),
               ColorSelectionSettingsTile(
                 key: const Key('diaColor'),
                 onMainColorChanged: (color) => settings.diaColor = createMaterialColor((color ?? Colors.teal).value),
                 initialColor: settings.diaColor,
-                title: Text(AppLocalizations.of(context)!.diaColor)),
+                title: Text(localizations.diaColor)),
               ColorSelectionSettingsTile(
                 key: const Key('pulColor'),
                 onMainColorChanged: (color) => settings.pulColor = createMaterialColor((color ?? Colors.red).value),
                 initialColor: settings.pulColor,
-                title: Text(AppLocalizations.of(context)!.pulColor)),
+                title: Text(localizations.pulColor)),
               SwitchSettingsTile(
                 key: const Key('useLegacyList'),
                 initialValue: settings.useLegacyList,
@@ -137,10 +145,10 @@ class SettingsPage extends StatelessWidget {
                   settings.useLegacyList = value;
                 },
                 leading: const Icon(Icons.list_alt_outlined),
-                title: Text(AppLocalizations.of(context)!.useLegacyList)),
+                title: Text(localizations.useLegacyList)),
             ]),
 
-            SettingsSection(title: Text(AppLocalizations.of(context)!.behavior), children: [
+            SettingsSection(title: Text(localizations.behavior), children: [
               SwitchSettingsTile(
                 key: const Key('allowManualTimeInput'),
                 initialValue: settings.allowManualTimeInput,
@@ -148,11 +156,11 @@ class SettingsPage extends StatelessWidget {
                   settings.allowManualTimeInput = value;
                 },
                 leading: const Icon(Icons.details),
-                title: Text(AppLocalizations.of(context)!.allowManualTimeInput)),
+                title: Text(localizations.allowManualTimeInput)),
               SwitchSettingsTile(
                 key: const Key('validateInputs'),
                 initialValue: settings.validateInputs,
-                title: Text(AppLocalizations.of(context)!.validateInputs),
+                title: Text(localizations.validateInputs),
                 leading: const Icon(Icons.edit),
                 onToggle: (value) {
                   settings.validateInputs = value;
@@ -160,7 +168,7 @@ class SettingsPage extends StatelessWidget {
               SwitchSettingsTile(
                 key: const Key('allowMissingValues'),
                 initialValue: settings.allowMissingValues,
-                title: Text(AppLocalizations.of(context)!.allowMissingValues),
+                title: Text(localizations.allowMissingValues),
                 leading: const Icon(Icons.report_off_outlined),
                 onToggle: (value) {
                   settings.allowMissingValues = value;
@@ -168,14 +176,14 @@ class SettingsPage extends StatelessWidget {
               SwitchSettingsTile(
                 key: const Key('confirmDeletion'),
                 initialValue: settings.confirmDeletion,
-                title: Text(AppLocalizations.of(context)!.confirmDeletion),
+                title: Text(localizations.confirmDeletion),
                 leading: const Icon(Icons.check),
                 onToggle: (value) {
                   settings.confirmDeletion = value;
                 }),
               InputSettingsTile(
                 key: const Key('sysWarn'),
-                title: Text(AppLocalizations.of(context)!.sysWarn),
+                title: Text(localizations.sysWarn),
                 leading: const Icon(Icons.warning_amber_outlined),
                 keyboardType: TextInputType.number,
                 inputFormatters: [FilteringTextInputFormatter.digitsOnly],
@@ -186,12 +194,12 @@ class SettingsPage extends StatelessWidget {
                   }
                   settings.sysWarn = int.parse(value);
                 },
-                decoration: InputDecoration(hintText: AppLocalizations.of(context)!.sysWarn),
+                decoration: InputDecoration(hintText: localizations.sysWarn),
                 inputWidth: 120,
               ),
               InputSettingsTile(
                 key: const Key('diaWarn'),
-                title: Text(AppLocalizations.of(context)!.diaWarn),
+                title: Text(localizations.diaWarn),
                 leading: const Icon(Icons.warning_amber_outlined),
                 keyboardType: TextInputType.number,
                 inputFormatters: [FilteringTextInputFormatter.digitsOnly],
@@ -202,18 +210,18 @@ class SettingsPage extends StatelessWidget {
                   }
                   settings.diaWarn = int.parse(value);
                 },
-                decoration: InputDecoration(hintText: AppLocalizations.of(context)!.diaWarn),
+                decoration: InputDecoration(hintText: localizations.diaWarn),
                 inputWidth: 120,
               ),
               SettingsTile(
                 key: const Key('determineWarnValues'),
                 leading: const Icon(Icons.settings_applications_outlined),
-                title: Text(AppLocalizations.of(context)!.determineWarnValues),
+                title: Text(localizations.determineWarnValues),
                 onPressed: (context) {
                   showDialog(
                     context: context,
                     builder: (context) => NumberInputDialoge(
-                      hintText: AppLocalizations.of(context)!.age,
+                      hintText: localizations.age,
                       onParsableSubmit: (age) {
                         settings.sysWarn = BloodPressureWarnValues.getUpperSysWarnValue(age);
                         settings.diaWarn = BloodPressureWarnValues.getUpperDiaWarnValue(age);
@@ -230,8 +238,8 @@ class SettingsPage extends StatelessWidget {
               ),
               SettingsTile(
                 key: const Key('AboutWarnValuesScreen'),
-                title: Text(AppLocalizations.of(context)!.aboutWarnValuesScreen),
-                description: Text(AppLocalizations.of(context)!.aboutWarnValuesScreenDesc),
+                title: Text(localizations.aboutWarnValuesScreen),
+                description: Text(localizations.aboutWarnValuesScreenDesc),
                 leading: const Icon(Icons.info_outline),
                 trailing: const Icon(Icons.arrow_forward_ios),
                 onPressed: (context) {
@@ -243,7 +251,7 @@ class SettingsPage extends StatelessWidget {
               ),
               SettingsTile(
                 key: const Key('GraphMarkingsScreen'),
-                title: Text(AppLocalizations.of(context)!.customGraphMarkings),
+                title: Text(localizations.customGraphMarkings),
                 leading: const Icon(Icons.legend_toggle_outlined),
                 trailing: const Icon(Icons.arrow_forward_ios),
                 onPressed: (context) {
@@ -254,17 +262,17 @@ class SettingsPage extends StatelessWidget {
                 }
               ),
               SwitchSettingsTile(
-                title: Text(AppLocalizations.of(context)!.drawRegressionLines),
+                title: Text(localizations.drawRegressionLines),
                 leading: const Icon(Icons.trending_down_outlined),
-                description: Text(AppLocalizations.of(context)!.drawRegressionLinesDesc),
+                description: Text(localizations.drawRegressionLinesDesc),
                 initialValue: settings.drawRegressionLines,
                 onToggle: (value) {
                   settings.drawRegressionLines = value;
                 }
               ),
               SwitchSettingsTile(
-                title: Text(AppLocalizations.of(context)!.startWithAddMeasurementPage),
-                description: Text(AppLocalizations.of(context)!.startWithAddMeasurementPageDescription),
+                title: Text(localizations.startWithAddMeasurementPage),
+                description: Text(localizations.startWithAddMeasurementPageDescription),
                 leading: const Icon(Icons.electric_bolt_outlined),
                 initialValue: settings.startWithAddMeasurementPage,
                 onToggle: (value) {
@@ -273,10 +281,10 @@ class SettingsPage extends StatelessWidget {
               ),
             ]),
             SettingsSection(
-              title: Text(AppLocalizations.of(context)!.data),
+              title: Text(localizations.data),
               children: [
                 SettingsTile(
-                    title: Text(AppLocalizations.of(context)!.exportImport),
+                    title: Text(localizations.exportImport),
                     leading: const Icon(Icons.download),
                     trailing: const Icon(Icons.arrow_forward_ios),
                     onPressed: (context) {
@@ -286,12 +294,57 @@ class SettingsPage extends StatelessWidget {
                       );
                     }
                 ),
+                SettingsTile(
+                    title: Text(localizations.exportSettings),
+                    leading: const Icon(Icons.tune),
+                    onPressed: (context) async {
+                      String dbPath = await getDatabasesPath();
+                      assert(dbPath != inMemoryDatabasePath);
+                      dbPath = join(dbPath, 'config.db');
+                      assert(Platform.isAndroid);
+                      Share.shareXFiles([
+                        XFile(dbPath,)
+                      ]);
+                    }
+                ),
+                SettingsTile(
+                    title: Text(localizations.importSettings),
+                    description: Text(localizations.requiresAppRestart),
+                    leading: const Icon(Icons.settings_backup_restore),
+                    onPressed: (context) async {
+                      final messenger = ScaffoldMessenger.of(context);
+                      var result = await FilePicker.platform.pickFiles(
+                        allowMultiple: false,
+                        withData: true,
+                      );
+                      if (result == null) {
+                        messenger.showSnackBar(SnackBar(content: Text(localizations.errNoFileOpened)));
+                        return;
+                      }
+                      var binaryContent = result.files.single.bytes;
+                      if (binaryContent == null) {
+                        messenger.showSnackBar(SnackBar(content: Text(localizations.errCantReadFile)));
+                        return;
+                      }
+                      var path = result.files.single.path;
+                      assert(path != null); // null state directly linked to binary content
+
+                      String dbPath = await getDatabasesPath();
+                      assert(dbPath != inMemoryDatabasePath);
+                      dbPath = join(dbPath, 'config.db');
+                      File(path!).copySync(dbPath);
+                      if (!await Restart.restartApp()) {
+                        messenger.showSnackBar(SnackBar(content: Text(localizations.pleaseRestart)));
+                        return;
+                      }
+                    }
+                ),
               ],
             ),
-            SettingsSection(title: Text(AppLocalizations.of(context)!.aboutWarnValuesScreen), children: [
+            SettingsSection(title: Text(localizations.aboutWarnValuesScreen), children: [
               SettingsTile(
                   key: const Key('version'),
-                  title: Text(AppLocalizations.of(context)!.version),
+                  title: Text(localizations.version),
                   leading: const Icon(Icons.info_outline),
                   description: ConsistentFutureBuilder<PackageInfo>(
                     future: PackageInfo.fromPlatform(),
@@ -306,10 +359,9 @@ class SettingsPage extends StatelessWidget {
               ),
               SettingsTile(
                 key: const Key('sourceCode'),
-                title: Text(AppLocalizations.of(context)!.sourceCode),
+                title: Text(localizations.sourceCode),
                 leading: const Icon(Icons.merge),
                 onPressed: (context) async {
-                  final localizations = AppLocalizations.of(context)!;
                   final scaffoldMessenger = ScaffoldMessenger.of(context);
                   var url = Uri.parse('https://github.com/NobodyForNothing/blood-pressure-monitor-fl');
                   if (await canLaunchUrl(url)) {
@@ -322,7 +374,7 @@ class SettingsPage extends StatelessWidget {
               ),
               SettingsTile(
                 key: const Key('licenses'),
-                title: Text(AppLocalizations.of(context)!.licenses),
+                title: Text(localizations.licenses),
                 leading: const Icon(Icons.policy_outlined),
                 trailing: const Icon(Icons.arrow_forward_ios),
                 onPressed: (context) {
pubspec.lock
@@ -573,6 +573,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "3.0.1"
+  restart_app:
+    dependency: "direct main"
+    description:
+      name: restart_app
+      sha256: b37daeb1c02fcab30e19d9e30b6fdd215bd53577efd927042eb77cf6f09daadb
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.2.1"
   share_plus:
     dependency: "direct main"
     description:
pubspec.yaml
@@ -35,6 +35,7 @@ dependencies:
   flutter_markdown: ^0.6.17
   collection: ^1.17.1
   sqflite_common_ffi: ^2.3.0
+  restart_app: ^1.2.1
 
 dev_dependencies:
   flutter_test: