Commit d084c3f

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-09-01 16:48:30
Simplify settings storage (#397)
* deprecate old db loading Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * generify settings loader interface Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * implement new file based settings Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * automatically migrate old settings Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * update settings export Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * update settings import Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * delete old config db data update code Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * test file settings loader Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * simplify code and allow loading old settings without restart Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * fix settings import Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * simplify legacy setting dao cache Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * finalize feature Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> * remove print statement Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com> --------- Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 0fabc08
app/lib/l10n/app_en.arb
@@ -516,5 +516,9 @@
     "intakes": "Medicine intakes",
     "@intakes": {},
     "errFeatureNotSupported": "This feature is not available on this platform.",
-    "@errFeatureNotSupported": {}
+    "@errFeatureNotSupported": {},
+    "invalidZip": "Invalid zip file.",
+    "@invalidZip": {},
+    "errCantCreateArchive": "Can''t create archive. Please report the bug if possible.",
+    "@errCantCreateArchive": {}
 }
\ No newline at end of file
app/lib/model/storage/db/config_dao.dart
@@ -1,42 +1,37 @@
 import 'package:blood_pressure_app/model/storage/db/config_db.dart';
+import 'package:blood_pressure_app/model/storage/db/settings_loader.dart';
 import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
 import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
 import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
 import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
 import 'package:blood_pressure_app/model/storage/interval_store.dart';
 import 'package:blood_pressure_app/model/storage/settings_store.dart';
-import 'package:sqflite/sqflite.dart';
 
 /// Class for loading data from the database.
 ///
-/// The user of this class needs to pay attention to dispose all old instances
+/// The user of this class needs to pay attention to dispose all old instance
 /// of objects created by the instance methods in order to ensure there are no
-/// concurrent writes to the database. Having multiple instances will cause data
+/// concurrent writes to the database. Having multiple instance will cause data
 /// loss because states are not synced again after one changes.
 ///
 /// The load... methods have to schedule a initial save to db in case an
 /// migration / update of fields occurred.
-class ConfigDao {
+@deprecated
+class ConfigDao implements SettingsLoader {
   /// Create a serializer to initialize data from a database.
   ConfigDao(this._configDB);
 
   final ConfigDB _configDB;
 
-  final Map<int, Settings> _settingsInstances = {};
-  /// Loads the profiles [Settings] object from the database.
-  ///
-  /// If any errors occur or the object is not present, a default one will be
-  /// created. Changes in the object will save to the database automatically (a
-  /// listener gets attached).
-  ///
-  /// Changes to the database will not propagate to the object.
-  Future<Settings> loadSettings(int profileID) async {
-    if (_settingsInstances.containsKey(profileID)) return _settingsInstances[profileID]!;
+  Settings? _settingsInstance = null;
+  @override
+  Future<Settings> loadSettings() async {
+    if (_settingsInstance != null) return _settingsInstance!;
     final dbEntry = await _configDB.database.query(
         ConfigDB.settingsTable,
         columns: ['settings_json'],
         where: 'profile_id = ?',
-        whereArgs: [profileID],
+        whereArgs: [0],
     );
 
     late final Settings settings;
@@ -51,43 +46,19 @@ class ConfigDao {
         settings = Settings.fromJson(settingsJson.toString());
       }
     }
-    _updateSettings(profileID, settings);
-    settings.addListener(() {
-      _updateSettings(profileID, settings);
-    });
-    _settingsInstances[profileID] = settings;
+    _settingsInstance = settings;
     return settings;
   }
 
-  /// Update settings for a profile in the database.
-  ///
-  /// Adds an entry if no settings where saved for this profile.
-  Future<void> _updateSettings(int profileID, Settings settings) async {
-    if (!_configDB.database.isOpen) return;
-    await _configDB.database.insert(
-      ConfigDB.settingsTable,
-      {
-        'profile_id': profileID,
-        'settings_json': settings.toJson(),
-      },
-      conflictAlgorithm: ConflictAlgorithm.replace,
-    );
-  }
-
-  final Map<int, ExportSettings> _exportSettingsInstances = {};
-  /// Loads the profiles [ExportSettings] object from the database.
-  ///
-  /// If any errors occur or the object is not present, a default one will be created. Changes in the object
-  /// will save to the database automatically (a listener gets attached).
-  ///
-  /// Changes to the database will not propagate to the object.
-  Future<ExportSettings> loadExportSettings(int profileID) async {
-    if (_exportSettingsInstances.containsKey(profileID)) return _exportSettingsInstances[profileID]!;
+  ExportSettings? _exportSettingsInstance = null;
+  @override
+  Future<ExportSettings> loadExportSettings() async {
+    if (_exportSettingsInstance != null) return _exportSettingsInstance!;
     final dbEntry = await _configDB.database.query(
         ConfigDB.exportSettingsTable,
         columns: ['json'],
         where: 'profile_id = ?',
-        whereArgs: [profileID],
+        whereArgs: [0],
     );
 
     late final ExportSettings exportSettings;
@@ -102,44 +73,19 @@ class ConfigDao {
         exportSettings = ExportSettings.fromJson(settingsJson.toString());
       }
     }
-    _updateExportSettings(profileID, exportSettings);
-    exportSettings.addListener(() {
-      _updateExportSettings(profileID, exportSettings);
-    });
-    _exportSettingsInstances[profileID] = exportSettings;
+    _exportSettingsInstance = exportSettings;
     return exportSettings;
   }
 
-  /// Update [ExportSettings] for a profile in the database.
-  ///
-  /// Adds an entry if necessary.
-  Future<void> _updateExportSettings(int profileID, ExportSettings settings) async {
-    if (!_configDB.database.isOpen) return;
-    await _configDB.database.insert(
-        ConfigDB.exportSettingsTable,
-        {
-          'profile_id': profileID,
-          'json': settings.toJson(),
-        },
-        conflictAlgorithm: ConflictAlgorithm.replace,
-    );
-  }
-
-  final Map<int, CsvExportSettings> _csvExportSettingsInstances = {};
-
-  /// Loads the profiles [CsvExportSettings] object from the database.
-  ///
-  /// If any errors occur or the object is not present, a default one will be created. Changes in the object
-  /// will save to the database automatically (a listener gets attached).
-  ///
-  /// Changes to the database will not propagate to the object.
-  Future<CsvExportSettings> loadCsvExportSettings(int profileID) async {
-    if (_csvExportSettingsInstances.containsKey(profileID)) return _csvExportSettingsInstances[profileID]!;
+  CsvExportSettings? _csvExportSettingsInstance = null;
+  @override
+  Future<CsvExportSettings> loadCsvExportSettings() async {
+    if (_csvExportSettingsInstance != null) return _csvExportSettingsInstance!;
     final dbEntry = await _configDB.database.query(
         ConfigDB.exportCsvSettingsTable,
         columns: ['json'],
         where: 'profile_id = ?',
-        whereArgs: [profileID],
+        whereArgs: [0],
     );
 
     late final CsvExportSettings exportSettings;
@@ -154,44 +100,19 @@ class ConfigDao {
         exportSettings = CsvExportSettings.fromJson(settingsJson.toString());
       }
     }
-    _updateCsvExportSettings(profileID, exportSettings);
-    exportSettings.addListener(() {
-      _updateCsvExportSettings(profileID, exportSettings);
-    });
-    _csvExportSettingsInstances[profileID] = exportSettings;
+    _csvExportSettingsInstance = exportSettings;
     return exportSettings;
   }
 
-  /// Update [CsvExportSettings] for a profile in the database.
-  ///
-  /// Adds an entry if necessary.
-  Future<void> _updateCsvExportSettings(int profileID, CsvExportSettings settings) async {
-    if (!_configDB.database.isOpen) return;
-    await _configDB.database.insert(
-        ConfigDB.exportCsvSettingsTable,
-        {
-          'profile_id': profileID,
-          'json': settings.toJson(),
-        },
-        conflictAlgorithm: ConflictAlgorithm.replace,
-    );
-  }
-
-  final Map<int, PdfExportSettings> _pdfExportSettingsInstances = {};
-
-  /// Loads the profiles [PdfExportSettings] object from the database.
-  ///
-  /// If any errors occur or the object is not present, a default one will be created. Changes in the object
-  /// will save to the database automatically (a listener gets attached).
-  ///
-  /// Changes to the database will not propagate to the object.
-  Future<PdfExportSettings> loadPdfExportSettings(int profileID) async {
-    if (_pdfExportSettingsInstances.containsKey(profileID)) return _pdfExportSettingsInstances[profileID]!;
+  PdfExportSettings? _pdfExportSettingsInstance = null;
+  @override
+  Future<PdfExportSettings> loadPdfExportSettings() async {
+    if (_pdfExportSettingsInstance != null) return _pdfExportSettingsInstance!;
     final dbEntry = await _configDB.database.query(
         ConfigDB.exportPdfSettingsTable,
         columns: ['json'],
         where: 'profile_id = ?',
-        whereArgs: [profileID],
+        whereArgs: [0],
     );
 
     late final PdfExportSettings exportSettings;
@@ -206,49 +127,27 @@ class ConfigDao {
         exportSettings = PdfExportSettings.fromJson(settingsJson.toString());
       }
     }
-    _updatePdfExportSettings(profileID, exportSettings);
-    exportSettings.addListener(() {
-      _updatePdfExportSettings(profileID, exportSettings);
-    });
-    _pdfExportSettingsInstances[profileID] = exportSettings;
+    _pdfExportSettingsInstance = exportSettings;
     return exportSettings;
   }
 
-  /// Update [PdfExportSettings] for a profile in the database.
-  ///
-  /// Adds an entry if necessary.
-  Future<void> _updatePdfExportSettings(int profileID, PdfExportSettings settings) async {
-    if (!_configDB.database.isOpen) return;
-    await _configDB.database.insert(
-        ConfigDB.exportPdfSettingsTable,
-        {
-          'profile_id': profileID,
-          'json': settings.toJson(),
-        },
-        conflictAlgorithm: ConflictAlgorithm.replace,
+  IntervalStoreManager? _intervallStorageInstance;
+  @override
+  Future<IntervalStoreManager> loadIntervalStorageManager() async {
+    _intervallStorageInstance ??= IntervalStoreManager(
+      await _loadStore(0),
+      await _loadStore(1),
+      await _loadStore(2),
     );
+    return _intervallStorageInstance!;
   }
 
-  final Map<(int, int), IntervalStorage> _intervallStorageInstances = {};
-
-  /// Loads a [IntervalStorage] object of a [profileID] from the database.
-  ///
-  /// The [storageID] allows for associating multiple intervalls with one profile.
-  ///
-  /// If any errors occur or the object is not present, a default one will be created. Changes in the object
-  /// will save to the database automatically (a listener gets attached).
-  ///
-  /// Changes to the database will not propagate to the object.
-  ///
-  /// This should not be invoked directly in order to centralise [storageID] allocation. Currently this is done by
-  /// the [IntervalStoreManager] class.
-  Future<IntervalStorage> loadIntervalStorage(int profileID, int storageID) async {
-    if (_intervallStorageInstances.containsKey((profileID, storageID))) return _intervallStorageInstances[(profileID, storageID)]!;
+  Future<IntervalStorage> _loadStore(int storageID) async {
     final dbEntry = await _configDB.database.query(
-        ConfigDB.selectedIntervalStorageTable,
-        columns: ['stepSize', 'start', 'end'],
-        where: 'profile_id = ? AND storage_id = ?',
-        whereArgs: [profileID, storageID],
+      ConfigDB.selectedIntervalStorageTable,
+      columns: ['stepSize', 'start', 'end'],
+      where: 'profile_id = ? AND storage_id = ?',
+      whereArgs: [0, storageID],
     );
     late final IntervalStorage intervallStorage;
     if (dbEntry.isEmpty) {
@@ -257,47 +156,18 @@ class ConfigDao {
       assert(dbEntry.length == 1, 'Keys should ensure only one entry is possible.');
       intervallStorage = IntervalStorage.fromMap(dbEntry.first);
     }
-
-    _updateIntervallStorage(profileID, storageID, intervallStorage);
-    intervallStorage.addListener(() {
-      _updateIntervallStorage(profileID, storageID, intervallStorage);
-    });
-    _intervallStorageInstances[(profileID, storageID)] = intervallStorage;
     return intervallStorage;
   }
 
-  /// Update specific [IntervalStorage] for a profile in the database.
-  ///
-  /// Adds an entry if necessary.
-  Future<void> _updateIntervallStorage(int profileID, int storageID, IntervalStorage intervallStorage) async {
-    if (!_configDB.database.isOpen) return;
-    final Map<String, dynamic> columnValueMap = {
-      'profile_id': profileID,
-      'storage_id': storageID,
-    };
-    columnValueMap.addAll(intervallStorage.toMap());
-    await _configDB.database.insert(
-        ConfigDB.selectedIntervalStorageTable,
-        columnValueMap,
-        conflictAlgorithm: ConflictAlgorithm.replace,
-    );
-  }
-
-  final Map<int, ExportColumnsManager> _exportColumnsManagerInstances = {};
-
-  /// Loads the profiles [ExportColumnsManager] object from the database.
-  ///
-  /// If any errors occur or the object is not present, a default one will be created. Changes in the object
-  /// will save to the database automatically (a listener gets attached).
-  ///
-  /// Changes to the database will not propagate to the object.
-  Future<ExportColumnsManager> loadExportColumnsManager(int profileID) async {
-    if (_exportColumnsManagerInstances.containsKey(profileID)) return _exportColumnsManagerInstances[profileID]!;
+  ExportColumnsManager? _exportColumnsManagerInstance = null;
+  @override
+  Future<ExportColumnsManager> loadExportColumnsManager() async {
+    if (_exportColumnsManagerInstance != null) return _exportColumnsManagerInstance!;
     final dbEntry = await _configDB.database.query(
         ConfigDB.exportColumnsTable,
         columns: ['json'],
         where: 'profile_id = ?',
-        whereArgs: [profileID],
+        whereArgs: [0],
     );
 
     late final ExportColumnsManager columnsManager;
@@ -312,26 +182,7 @@ class ConfigDao {
         columnsManager = ExportColumnsManager.fromJson(json.toString());
       }
     }
-    _updateExportColumnsManager(profileID, columnsManager);
-    columnsManager.addListener(() {
-      _updateExportColumnsManager(profileID, columnsManager);
-    });
-    _exportColumnsManagerInstances[profileID] = columnsManager;
+    _exportColumnsManagerInstance = columnsManager;
     return columnsManager;
   }
-
-  /// Update [ExportColumnsManager] for a profile in the database.
-  ///
-  /// Adds an entry if necessary.
-  Future<void> _updateExportColumnsManager(int profileID, ExportColumnsManager manager) async {
-    if (!_configDB.database.isOpen) return;
-    await _configDB.database.insert(
-        ConfigDB.exportColumnsTable,
-        {
-          'profile_id': profileID,
-          'json': manager.toJson(),
-        },
-        conflictAlgorithm: ConflictAlgorithm.replace,
-    );
-  }
 }
app/lib/model/storage/db/config_db.dart
@@ -8,6 +8,7 @@ import 'package:sqflite/sqflite.dart';
 /// Database for storing settings and internal app state.
 ///
 /// The table names ensured by this class are stored as constant public strings ending in Table.
+@deprecated
 class ConfigDB {
   ConfigDB._create();
   
@@ -22,9 +23,6 @@ class ConfigDB {
   /// Format:
   /// `CREATE TABLE settings(profile_id INTEGER PRIMARY KEY, settings_json STRING)`
   static const String settingsTable = 'settings';
-  // instead of just changing this string when changing the format, _onDBUpgrade should be used.
-  static const String _settingsTableCreationString =
-      'CREATE TABLE settings(profile_id INTEGER PRIMARY KEY, settings_json STRING)';
 
   /// Name of the table for storing [ExportSettings] objects.
   ///
@@ -33,8 +31,6 @@ class ConfigDB {
   /// Format:
   /// `CREATE TABLE exportSettings(profile_id INTEGER PRIMARY KEY, json STRING)`
   static const String exportSettingsTable = 'exportSettings';
-  static const String _exportSettingsTableCreationString =
-      'CREATE TABLE exportSettings(profile_id INTEGER PRIMARY KEY, json STRING)';
 
   /// Name of the table for storing [CsvExportSettings] objects.
   ///
@@ -43,8 +39,6 @@ class ConfigDB {
   /// Format:
   /// `CREATE TABLE exportCsvSettings(profile_id INTEGER PRIMARY KEY, json STRING)`
   static const String exportCsvSettingsTable = 'exportCsvSettings';
-  static const String _exportCsvSettingsTableCreationString =
-      'CREATE TABLE exportCsvSettings(profile_id INTEGER PRIMARY KEY, json STRING)';
 
   /// Name of the table for storing [PdfExportSettings] objects.
   ///
@@ -53,8 +47,6 @@ class ConfigDB {
   /// Format:
   /// `CREATE TABLE exportPdfSettings(profile_id INTEGER PRIMARY KEY, json STRING)`
   static const String exportPdfSettingsTable = 'exportPdfSettings';
-  static const String _exportPdfSettingsTableCreationString =
-      'CREATE TABLE exportPdfSettings(profile_id INTEGER PRIMARY KEY, json STRING)';
 
   /// Name of the table for storing time intervals to display.
   ///
@@ -68,9 +60,6 @@ class ConfigDB {
   /// Format: `CREATE TABLE selectedIntervallStorage(profile_id INTEGER, storage_id INTEGER, stepSize INTEGER,`
   /// ` start INTEGER, end INTEGER, PRIMARY KEY(profile_id, storage_id))`
   static const String selectedIntervalStorageTable = 'selectedIntervallStorage';
-  static const String _selectedIntervalStorageCreationString = 'CREATE TABLE selectedIntervallStorage(profile_id '
-      'INTEGER, storage_id INTEGER, stepSize INTEGER, start INTEGER, end INTEGER, '
-      'PRIMARY KEY(profile_id, storage_id))';
 
   /// Name of the exportStrings table. It is used to to update old columns.
   ///
@@ -78,8 +67,6 @@ class ConfigDB {
   /// `CREATE TABLE exportStrings(internalColumnName STRING PRIMARY KEY, columnTitle STRING, formatPattern STRING)`
   @Deprecated('removed after all usages are replaced by export_columns_store')
   static const String exportStringsTable = 'exportStrings';
-  static const String _exportStringsTableCreationString =
-      'CREATE TABLE exportStrings(internalColumnName STRING PRIMARY KEY, columnTitle STRING, formatPattern STRING)';
 
   /// Name of table of storing [ExportColumnsManager] objects
   ///
@@ -93,60 +80,43 @@ class ConfigDB {
 
   /// [dbPath] is the path to the folder the database is in. When [dbPath] is left empty the default database file is
   /// used. The [isFullPath] option tells the constructor not to add the default filename at the end of [dbPath].
-  Future<void> _asyncInit(String? dbPath, bool isFullPath) async {
+  Future<bool> _asyncInit(String? dbPath, bool isFullPath) async {
     dbPath ??= await getDatabasesPath();
     if (dbPath != inMemoryDatabasePath && !isFullPath) {
       dbPath = join(dbPath, 'config.db');
     }
-
+    bool dbTooOld = false;
     _database = await openDatabase(
       dbPath,
-      onCreate: _onDBCreate,
-      onUpgrade: _onDBUpgrade,
+      onCreate: (_, __) => dbTooOld = true,
+      onUpgrade: (Database db, int oldVersion, int newVersion) async {
+        assert(newVersion == 3);
+        if (oldVersion == 1) {
+          dbTooOld = true;
+        } else if (oldVersion == 2) {
+          await db.execute(_exportColumnsTableCreationString);
+        } else {
+          assert(false, 'Unexpected version upgrade from $oldVersion to $newVersion.');
+        }
+      },
       // When increasing the version an update procedure from every other possible version is needed
       version: 3,
       // In integration tests the file may be deleted which causes deadlocks.
       singleInstance: false,
     );
-  }
-
-  FutureOr<void> _onDBCreate(Database db, int version) async {
-    await db.execute(_exportStringsTableCreationString);
-    await db.execute(_settingsTableCreationString);
-    await db.execute(_exportSettingsTableCreationString);
-    await db.execute(_exportCsvSettingsTableCreationString);
-    await db.execute(_exportPdfSettingsTableCreationString);
-    await db.execute(_selectedIntervalStorageCreationString);
-    await db.execute(_exportColumnsTableCreationString);
-  }
-
-  FutureOr<void> _onDBUpgrade(Database db, int oldVersion, int newVersion) async {
-    // When adding more versions the upgrade procedure proposed in https://stackoverflow.com/a/75153875/21489239
-    // might be useful, to avoid duplicated code. Currently this would only lead to complexity without benefits.
-    assert(newVersion == 3);
-    if (oldVersion == 1) {
-      await db.execute(_settingsTableCreationString);
-      await db.execute(_exportSettingsTableCreationString);
-      await db.execute(_exportCsvSettingsTableCreationString);
-      await db.execute(_exportPdfSettingsTableCreationString);
-      await db.execute(_selectedIntervalStorageCreationString);
-      await db.execute(_exportColumnsTableCreationString);
-      await db.database.setVersion(2);
-    } else if (oldVersion == 2) {
-      await db.execute(_exportColumnsTableCreationString);
-    } else {
-      assert(false, 'Unexpected version upgrade from $oldVersion to $newVersion.');
-    }
+    return dbTooOld;// TODO: test
   }
 
   /// Factory method that either opens an existing db file or creates a new one if no file is present.
   ///
   /// [dbPath] is the path to the folder the database is in. When [dbPath] is left empty the default database file is
   /// used. The [isFullPath] option tells the constructor not to add the default filename at the end of [dbPath].
-  static Future<ConfigDB> open({String? dbPath, bool isFullPath = false}) async {
+  static Future<ConfigDB?> open({String? dbPath, bool isFullPath = false}) async {
     final instance = ConfigDB._create();
-    await instance._asyncInit(dbPath, isFullPath);
-    return instance;
+    if(await instance._asyncInit(dbPath, isFullPath)) {
+      return instance;
+    }
+    return null;
   }
 
   /// The database created by this class for getting and setting entries.
app/lib/model/storage/db/file_settings_loader.dart
@@ -0,0 +1,133 @@
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:archive/archive_io.dart';
+import 'package:blood_pressure_app/model/storage/db/settings_loader.dart';
+import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
+import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/interval_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
+import 'package:flutter/widgets.dart';
+import 'package:path/path.dart';
+import 'package:sqflite/sqflite.dart';
+
+/// Store settings in a directory format on disk.
+class FileSettingsLoader implements SettingsLoader {
+  FileSettingsLoader._create(this._path);
+
+  /// Creates setting loader from relative directory [path] or the default
+  /// settings path.
+  static Future<FileSettingsLoader> load([String? path]) async {
+    path ??= join(await getDatabasesPath(), 'settings');
+    Directory(path).createSync();
+    return FileSettingsLoader._create(path);
+  }
+
+  final String _path;
+
+  /// Instantiates a settings file relative to [_path] and writes changes.
+  ///
+  /// If the is read successfully [build] is called else [createNew] is called.
+  T _loadFile<T extends ChangeNotifier>(
+    String fileName,
+    T Function(String) build,
+    T Function() createNew,
+    String Function(T) serialize,
+  ) {
+    final f = File(join(_path, fileName));
+    T? obj;
+    try {
+      obj = build(f.readAsStringSync());
+    } on FileSystemException {}
+    obj ??= createNew();
+
+    obj.addListener(() => f.writeAsStringSync(serialize(obj!)));
+    f.writeAsStringSync(serialize(obj));
+    return obj;
+  }
+
+  @override
+  Future<CsvExportSettings> loadCsvExportSettings() async => _loadFile(
+    'csv-export',
+    CsvExportSettings.fromJson,
+    CsvExportSettings.new,
+    (e) => e.toJson(),
+  );
+
+  @override
+  Future<ExportColumnsManager> loadExportColumnsManager() async => _loadFile(
+    'export-columns',
+    ExportColumnsManager.fromJson,
+    ExportColumnsManager.new,
+    (e) => e.toJson(),
+  );
+
+  @override
+  Future<ExportSettings> loadExportSettings() async => _loadFile(
+    'export',
+    ExportSettings.fromJson,
+    ExportSettings.new,
+    (e) => e.toJson(),
+  );
+
+  @override
+  Future<IntervalStoreManager> loadIntervalStorageManager() async => _loadFile(
+    'intervall-store',
+    (String jsonStr) {
+      final json = jsonDecode(jsonStr);
+      if (json is Map<String, dynamic>) {
+        return IntervalStoreManager(
+          json['main'] is! String ? IntervalStorage() : IntervalStorage.fromJson(json['main']!),
+          json['export'] is! String ? IntervalStorage() : IntervalStorage.fromJson(json['export']!),
+          json['stats'] is! String ? IntervalStorage() : IntervalStorage.fromJson(json['stats']!),
+        );
+      }
+      return IntervalStoreManager(IntervalStorage(), IntervalStorage(), IntervalStorage());
+    },
+    () => IntervalStoreManager(IntervalStorage(), IntervalStorage(), IntervalStorage()),
+    (e) => jsonEncode({
+      'main': e.mainPage.toJson(),
+      'export': e.exportPage.toJson(),
+      'stats': e.statsPage.toJson(),
+    }),
+  );
+
+  @override
+  Future<PdfExportSettings> loadPdfExportSettings() async => _loadFile(
+    'pdf-export',
+    PdfExportSettings.fromJson,
+    PdfExportSettings.new,
+    (e) => e.toJson(),
+  );
+
+  @override
+  Future<Settings> loadSettings() async => _loadFile(
+    'general',
+    Settings.fromJson,
+    Settings.new,
+    (e) => e.toJson(),
+  );
+
+  /// Attempt to backup all stored data to archive.
+  Archive? createArchive() {
+    try {
+      final archive = Archive();
+      _backupFile(archive, 'general');
+      _backupFile(archive, 'export');
+      _backupFile(archive, 'csv-export');
+      _backupFile(archive, 'pdf-export');
+      _backupFile(archive, 'export-columns');
+      _backupFile(archive, 'intervall-store');
+      return archive;
+    } on FileSystemException {
+      return null;
+    }
+  }
+
+  void _backupFile(Archive archive, String fileName) {
+    final data = File(join(_path, fileName)).readAsStringSync();
+    archive.addFile(ArchiveFile.string(fileName, data));
+  }
+}
app/lib/model/storage/db/settings_loader.dart
@@ -0,0 +1,57 @@
+import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
+import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/interval_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
+
+/// A backend agnostic loader for settings data.
+abstract class SettingsLoader {
+  /// Loads the profiles [Settings] object from disk.
+  ///
+  /// If any errors occur or the object is not present, a default one will be
+  /// created. Changes in the object will save to the automatically.
+  ///
+  /// Changes to the disk data will not propagate to the object.
+  Future<Settings> loadSettings();
+
+  /// Loads the profiles [ExportSettings] object from disk.
+  ///
+  /// If any errors occur or the object is not present, a default one will be
+  /// created. Changes in the object will save to the automatically.
+  ///
+  /// Changes to the disk data will not propagate to the object.
+  Future<ExportSettings> loadExportSettings();
+
+  /// Loads the profiles [CsvExportSettings] object from disk.
+  ///
+  /// If any errors occur or the object is not present, a default one will be
+  /// created. Changes in the object will save to the automatically.
+  ///
+  /// Changes to the disk data will not propagate to the object.
+  Future<CsvExportSettings> loadCsvExportSettings();
+
+  /// Loads the profiles [PdfExportSettings] object from disk.
+  ///
+  /// If any errors occur or the object is not present, a default one will be
+  /// created. Changes in the object will save to the automatically.
+  ///
+  /// Changes to the disk data will not propagate to the object.
+  Future<PdfExportSettings> loadPdfExportSettings();
+
+  /// Loads a [IntervalStoreManager] object from disk.
+  ///
+  /// If any errors occur or the object is not present, a default one will be
+  /// created. Changes in the object will save to the automatically.
+  ///
+  /// Changes to the database will not propagate to the object.
+  Future<IntervalStoreManager> loadIntervalStorageManager();
+
+  /// Loads the profiles [ExportColumnsManager] object from disk.
+  ///
+  /// If any errors occur or the object is not present, a default one will be
+  /// created. Changes in the object will save to the automatically.
+  ///
+  /// Changes to the disk data will not propagate to the object.
+  Future<ExportColumnsManager> loadExportColumnsManager();
+}
app/lib/model/storage/export_columns_store.dart
@@ -42,6 +42,11 @@ class ExportColumnsManager extends ChangeNotifier {
     _userColumns.clear();
     notifyListeners();
   }
+  void copyFrom(ExportColumnsManager other) {
+    _userColumns.clear();
+    _userColumns.addAll(other._userColumns);
+    notifyListeners();
+  }
 
   /// Namespaces that may not lead a user columns internal identifier.
   static const List<String> reservedNamespaces = ['buildIn', 'myHeart'];
app/lib/model/storage/export_csv_settings_store.dart
@@ -61,16 +61,18 @@ class CsvExportSettings extends ChangeNotifier implements CustomFieldsSettings {
   /// Serializes the object to json string.
   String toJson() => jsonEncode(toMap());
 
-  /// Reset all fields to their default values.
-  void reset() {
-    final d = CsvExportSettings();
-    _fieldDelimiter = d.fieldDelimiter;
-    _textDelimiter = d._textDelimiter;
-    _exportHeadline = d._exportHeadline;
-    _exportFieldsConfiguration = d._exportFieldsConfiguration;
+  /// Copy all values from another instance.
+  void copyFrom(CsvExportSettings other) {
+    _fieldDelimiter = other._fieldDelimiter;
+    _textDelimiter = other._textDelimiter;
+    _exportHeadline = other._exportHeadline;
+    _exportFieldsConfiguration = other._exportFieldsConfiguration;
     notifyListeners();
   }
 
+  /// Reset all fields to their default values.
+  void reset() => copyFrom(CsvExportSettings());
+
   String _fieldDelimiter = ',';
   String get fieldDelimiter => _fieldDelimiter;
   set fieldDelimiter(String value) {
app/lib/model/storage/export_pdf_settings_store.dart
@@ -64,16 +64,18 @@ class PdfExportSettings extends ChangeNotifier implements CustomFieldsSettings {
   String toJson() => jsonEncode(toMap());
 
   /// Reset all fields to their default values.
-  void reset() {
-    final d = PdfExportSettings();
-    _exportTitle = d._exportTitle;
-    _exportStatistics = d._exportStatistics;
-    _exportData = d._exportData;
-    _headerHeight = d._headerHeight;
-    _cellHeight = d._cellHeight;
-    _headerFontSize = d._headerFontSize;
-    _cellFontSize = d._cellFontSize;
-    _exportFieldsConfiguration = d._exportFieldsConfiguration;
+  void reset() => copyFrom(PdfExportSettings());
+
+  // Copy all values from another instance.
+  void copyFrom(PdfExportSettings other) {
+    _exportTitle = other._exportTitle;
+    _exportStatistics = other._exportStatistics;
+    _exportData = other._exportData;
+    _headerHeight = other._headerHeight;
+    _cellHeight = other._cellHeight;
+    _headerFontSize = other._headerFontSize;
+    _cellFontSize = other._cellFontSize;
+    _exportFieldsConfiguration = other._exportFieldsConfiguration;
     notifyListeners();
   }
 
app/lib/model/storage/export_settings_store.dart
@@ -35,17 +35,20 @@ class ExportSettings extends ChangeNotifier {
     'exportAfterEveryEntry': exportAfterEveryEntry,
   };
 
+  /// Serialize the object to a restoreable string.
   String toJson() => jsonEncode(toMap());
 
-  /// Reset all fields to their default values.
-  void reset() {
-    final d = ExportSettings();
-    _exportFormat = d._exportFormat;
-    _defaultExportDir = d._defaultExportDir;
-    _exportAfterEveryEntry = d._exportAfterEveryEntry;
+  /// Copy all values from another instance.
+  void copyFrom(ExportSettings other) {
+    _exportFormat = other._exportFormat;
+    _defaultExportDir = other._defaultExportDir;
+    _exportAfterEveryEntry = other._exportAfterEveryEntry;
     notifyListeners();
   }
 
+  /// Reset all fields to their default values.
+  void reset() => copyFrom(ExportSettings());
+
   ExportFormat _exportFormat = ExportFormat.csv;
   ExportFormat get exportFormat => _exportFormat;
   set exportFormat(ExportFormat value) {
app/lib/model/storage/interval_store.dart
@@ -1,7 +1,7 @@
 import 'dart:convert';
 
 import 'package:blood_pressure_app/model/storage/convert_util.dart';
-import 'package:blood_pressure_app/model/storage/db/config_dao.dart';
+import 'package:blood_pressure_app/model/storage/db/settings_loader.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:health_data_store/health_data_store.dart';
@@ -195,21 +195,13 @@ enum TimeStep {
 class IntervalStoreManager extends ChangeNotifier {
   /// Constructor for creating [IntervalStoreManager] from items.
   ///
-  /// Consider using [IntervalStoreManager.load] for loading [IntervalStorage] objects from the database, as it
-  /// automatically uses the correct storage ids.
+  /// You should use [SettingsLoader.loadExportColumnsManager] for most cases.
   IntervalStoreManager(this.mainPage, this.exportPage, this.statsPage) {
     mainPage.addListener(notifyListeners);
     exportPage.addListener(notifyListeners);
     statsPage.addListener(notifyListeners);
   }
 
-  static Future<IntervalStoreManager> load(ConfigDao configDao, int profileID) async =>
-    IntervalStoreManager(
-      await configDao.loadIntervalStorage(profileID, 0),
-      await configDao.loadIntervalStorage(profileID, 1),
-      await configDao.loadIntervalStorage(profileID, 2),
-    );
-
   IntervalStorage get(IntervalStoreManagerLocation type) => switch (type) {
     IntervalStoreManagerLocation.mainPage => mainPage,
     IntervalStoreManagerLocation.exportPage => exportPage,
@@ -224,6 +216,14 @@ class IntervalStoreManager extends ChangeNotifier {
     notifyListeners();
   }
 
+  // Copy all values from another instance.
+  void copyFrom(IntervalStoreManager other) {
+    mainPage = other.mainPage;
+    exportPage = other.exportPage;
+    statsPage = other.statsPage;
+    notifyListeners();
+  }
+
   /// Intervall for the page with graph and list.
   IntervalStorage mainPage;
 
app/lib/model/storage/settings_store.dart
@@ -162,39 +162,42 @@ class Settings extends ChangeNotifier {
   /// Serialize the object to a restoreable string.
   String toJson() => jsonEncode(toMap());
 
-  /// Reset all fields to their default values.
-  void reset() {
-    final d = Settings();
-    _language = d._language;
-    _accentColor = d._accentColor;
-    _sysColor = d._sysColor;
-    _diaColor = d._diaColor;
-    _pulColor = d._pulColor;
-    _horizontalGraphLines = d._horizontalGraphLines;
-    _dateFormatString = d._dateFormatString;
-    _graphLineThickness = d._graphLineThickness;
-    _needlePinBarWidth = d._needlePinBarWidth;
-    _animationSpeed = d._animationSpeed;
-    _sysWarn = d._sysWarn;
-    _diaWarn = d._diaWarn;
-    _lastVersion = d._lastVersion;
-    _allowManualTimeInput = d._allowManualTimeInput;
-    _confirmDeletion = d._confirmDeletion;
-    _themeMode = d._themeMode;
-    _validateInputs = d._validateInputs;
-    _allowMissingValues = d._allowMissingValues;
-    _drawRegressionLines = d._drawRegressionLines;
-    _startWithAddMeasurementPage = d._startWithAddMeasurementPage;
-    _useLegacyList = d._useLegacyList;
-    _bottomAppBars = d._bottomAppBars;
+  /// Copy all values from another instance.
+  void copyFrom(Settings other) {
+    _language = other._language;
+    _accentColor = other._accentColor;
+    _sysColor = other._sysColor;
+    _diaColor = other._diaColor;
+    _pulColor = other._pulColor;
+    _horizontalGraphLines = other._horizontalGraphLines;
+    _dateFormatString = other._dateFormatString;
+    _graphLineThickness = other._graphLineThickness;
+    _needlePinBarWidth = other._needlePinBarWidth;
+    _animationSpeed = other._animationSpeed;
+    _sysWarn = other._sysWarn;
+    _diaWarn = other._diaWarn;
+    _lastVersion = other._lastVersion;
+    _allowManualTimeInput = other._allowManualTimeInput;
+    _confirmDeletion = other._confirmDeletion;
+    _themeMode = other._themeMode;
+    _validateInputs = other._validateInputs;
+    _allowMissingValues = other._allowMissingValues;
+    _drawRegressionLines = other._drawRegressionLines;
+    _startWithAddMeasurementPage = other._startWithAddMeasurementPage;
+    _useLegacyList = other._useLegacyList;
+    _bottomAppBars = other._bottomAppBars;
+    _preferredPressureUnit = other._preferredPressureUnit;
+    _knownBleDev = other._knownBleDev;
+    _bleInput = other._bleInput;
     _medications.clear();
-    _highestMedIndex = d._highestMedIndex;
-    _preferredPressureUnit = d._preferredPressureUnit;
-    _knownBleDev = d._knownBleDev;
-    _bleInput = d._bleInput;
+    _medications.addAll(other._medications);
+    _highestMedIndex = other._highestMedIndex;
     notifyListeners();
   }
 
+  /// Reset all fields to their default values.
+  void reset() => copyFrom(Settings());
+
   Locale? _language;
   /// Language to use the app in.
   ///
app/lib/model/storage/update_legacy_settings.dart
@@ -14,11 +14,14 @@ import 'package:blood_pressure_app/model/storage/interval_store.dart';
 import 'package:blood_pressure_app/model/storage/settings_store.dart';
 import 'package:flutter/material.dart';
 import 'package:fluttertoast/fluttertoast.dart';
+import 'package:health_data_store/health_data_store.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 import 'package:sqflite/sqflite.dart';
 
+import 'db/config_dao.dart';
+
 /// Function for upgrading shared preferences from pre 1.5.4 (Oct 23) versions.
-Future<void> updateLegacySettings(Settings settings, ExportSettings exportSettings, CsvExportSettings csvExportSettings,
+Future<void> migrateSharedPreferences(Settings settings, ExportSettings exportSettings, CsvExportSettings csvExportSettings,
     PdfExportSettings pdfExportSettings, IntervalStoreManager intervallStoreManager,) async {
   final SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
 
@@ -199,7 +202,7 @@ Future<void> updateLegacySettings(Settings settings, ExportSettings exportSettin
 /// Function for upgrading pre 1.5.8 columns and settings to new structures.
 /// 
 /// - Adds columns from old db table to [manager].
-Future<void> updateLegacyExport(ConfigDB database, ExportColumnsManager manager) async {
+Future<void> _updateLegacyExport(ConfigDB database, ExportColumnsManager manager) async {
   if (await _tableExists(database.database, ConfigDB.exportStringsTable)) {
     final existingDbEntries = await database.database.query(
         ConfigDB.exportStringsTable,
@@ -225,3 +228,35 @@ Future<void> updateLegacyExport(ConfigDB database, ExportColumnsManager manager)
 
 Future<bool> _tableExists(Database database, String tableName) async => (await database.rawQuery(
   "SELECT name FROM sqlite_master WHERE type='table' AND name='$tableName';",)).isNotEmpty;
+
+/// Migrate to file based settings format from db in pre 1.7.4 (Jul 24).
+Future<void> migrateDatabaseSettings(
+  Settings settings,
+  ExportSettings exportSettings,
+  CsvExportSettings csvExportSettings,
+  PdfExportSettings pdfExportSettings,
+  IntervalStoreManager intervallStoreManager,
+  ExportColumnsManager manager,
+  MedicineRepository medRepo,
+) async {
+  final configDB = await ConfigDB.open();
+  if(configDB == null) return; // not upgradable
+
+  await _updateLegacyExport(configDB, manager); // TODO: test these older migrations
+  final configDao = ConfigDao(configDB);
+
+  final oldSettings = await configDao.loadSettings();
+  settings.copyFrom(oldSettings);
+  final oldMeds = settings.medications.map((e) => Medicine(
+    designation: e.designation,
+    color: e.color.value,
+    dosis: e.defaultDosis == null ? null : Weight.mg(e.defaultDosis!),
+  ));
+  await Future.forEach(oldMeds, medRepo.add);
+
+  exportSettings.copyFrom(await configDao.loadExportSettings());
+  csvExportSettings.copyFrom(await configDao.loadCsvExportSettings());
+  pdfExportSettings.copyFrom(await configDao.loadPdfExportSettings());
+  intervallStoreManager.copyFrom(await configDao.loadIntervalStorageManager());
+  manager.copyFrom(await configDao.loadExportColumnsManager());
+}
app/lib/screens/settings_screen.dart
@@ -1,6 +1,7 @@
 import 'dart:io';
+import 'dart:typed_data';
 
-import 'package:blood_pressure_app/components/custom_banner.dart';
+import 'package:archive/archive_io.dart';
 import 'package:blood_pressure_app/components/input_dialoge.dart';
 import 'package:blood_pressure_app/data_util/consistent_future_builder.dart';
 import 'package:blood_pressure_app/features/settings/delete_data_screen.dart';
@@ -15,9 +16,14 @@ import 'package:blood_pressure_app/features/settings/tiles/slider_list_tile.dart
 import 'package:blood_pressure_app/features/settings/tiles/titled_column.dart';
 import 'package:blood_pressure_app/features/settings/version_screen.dart';
 import 'package:blood_pressure_app/features/settings/warn_about_screen.dart';
+import 'package:blood_pressure_app/logging.dart';
 import 'package:blood_pressure_app/model/blood_pressure/pressure_unit.dart';
 import 'package:blood_pressure_app/model/blood_pressure/warn_values.dart';
 import 'package:blood_pressure_app/model/iso_lang_names.dart';
+import 'package:blood_pressure_app/model/storage/db/config_db.dart';
+import 'package:blood_pressure_app/model/storage/db/file_settings_loader.dart';
+import 'package:blood_pressure_app/model/storage/db/settings_loader.dart';
+import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
 import 'package:blood_pressure_app/model/storage/storage.dart';
 import 'package:blood_pressure_app/platform_integration/platform_client.dart';
 import 'package:file_picker/file_picker.dart';
@@ -26,7 +32,6 @@ 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:sqflite/sqflite.dart';
 import 'package:url_launcher/url_launcher.dart';
 
 /// Primary settings page to manage basic settings and link to subsettings.
@@ -306,11 +311,20 @@ class SettingsPage extends StatelessWidget {
                   title: Text(localizations.exportSettings),
                   leading: const Icon(Icons.tune),
                   onTap: () async {
-                    String dbPath = await getDatabasesPath();
-                    assert(dbPath != inMemoryDatabasePath);
-                    dbPath = join(dbPath, 'config.db');
-                    assert(Platform.isAndroid);
-                    await PlatformClient.shareFile(dbPath, 'application/vnd.sqlite3');
+                    final messenger = ScaffoldMessenger.of(context);
+                    final loader = await FileSettingsLoader.load();
+                    final archive = loader.createArchive();
+                    if (archive == null) {
+                      messenger.showSnackBar(SnackBar(content: Text(localizations.errCantCreateArchive)));
+                      return;
+                    }
+                    final compressedArchive = ZipEncoder().encode(archive);
+                    if (compressedArchive == null) {
+                      messenger.showSnackBar(SnackBar(content: Text(localizations.errCantCreateArchive)));
+                      return;
+                    }
+                    final archiveData = Uint8List.fromList(compressedArchive);
+                    await PlatformClient.shareData(archiveData, 'application/zip', 'bloodPressureSettings.zip');
                   },
                 ),
                 ListTile(
@@ -330,11 +344,33 @@ class SettingsPage extends StatelessWidget {
                       return;
                     }
 
-                    String dbPath = await getDatabasesPath();
-                    dbPath = join(dbPath, 'config.db');
-                    File(path).copySync(dbPath);
-                    messenger.showMaterialBanner(CustomBanner(content: Text(localizations.pleaseRestart)));
-                    // TODO: read settings and replace them on running app.
+                    late SettingsLoader loader;
+                    if (path.endsWith('db')) {
+                      final configDB = await ConfigDB.open(dbPath: path, isFullPath: true);
+                      if(configDB == null) return; // too old (doesn't contain settings yet)
+                      loader = ConfigDao(configDB);
+                    } else if (path.endsWith('zip')) {
+                      try {
+                        final decoded = ZipDecoder().decodeBuffer(InputFileStream(result.files.single.path!));
+                        final dir = join(Directory.systemTemp.path, 'settingsBackup');
+                        await extractArchiveToDisk(decoded, dir);
+                        loader = await FileSettingsLoader.load(dir);
+                      } on FormatException catch (e, stack) {
+                        messenger.showSnackBar(SnackBar(content: Text(localizations.invalidZip)));
+                        Log.err('invalid zip', [e, stack]);
+                        return;
+                      }
+                    } else {
+                      messenger.showSnackBar(SnackBar(content: Text(localizations.errNotImportable)));
+                      return;
+                    }
+                    settings.copyFrom(await loader.loadSettings());
+                    context.read<ExportSettings>().copyFrom(await loader.loadExportSettings());
+                    context.read<CsvExportSettings>().copyFrom(await loader.loadCsvExportSettings());
+                    context.read<PdfExportSettings>().copyFrom(await loader.loadPdfExportSettings());
+                    context.read<IntervalStoreManager>().copyFrom(await loader.loadIntervalStorageManager());
+                    context.read<ExportColumnsManager>().copyFrom(await loader.loadExportColumnsManager());
+                    messenger.showSnackBar(SnackBar(content: Text(localizations.success(localizations.importSettings))));
                   },
                 ),
                 ListTile(
app/lib/app.dart
@@ -3,7 +3,8 @@ import 'dart:io';
 import 'package:blood_pressure_app/data_util/consistent_future_builder.dart';
 import 'package:blood_pressure_app/model/blood_pressure/update_legacy_entries.dart';
 import 'package:blood_pressure_app/model/export_import/export_configuration.dart';
-import 'package:blood_pressure_app/model/storage/db/config_db.dart';
+import 'package:blood_pressure_app/model/storage/db/file_settings_loader.dart';
+import 'package:blood_pressure_app/model/storage/db/settings_loader.dart';
 import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
 import 'package:blood_pressure_app/model/storage/storage.dart';
 import 'package:blood_pressure_app/screens/error_reporting_screen.dart';
@@ -33,8 +34,6 @@ class App extends StatefulWidget {
 }
 
 class _AppState extends State<App> {
-  /// Database object for app settings.
-  ConfigDB? _configDB;
   Database? _entryDB;
 
   /// The result of the first [_loadApp] call.
@@ -50,8 +49,6 @@ class _AppState extends State<App> {
 
   @override
   void dispose() {
-    _configDB?.database.close();
-    _configDB = null;
     _entryDB?.close();
     _entryDB = null;
     _settings?.dispose();
@@ -87,17 +84,15 @@ class _AppState extends State<App> {
     }
 
     try {
-      _configDB = await ConfigDB.open();
-      final configDao = ConfigDao(_configDB!);
-
-      _settings ??= await configDao.loadSettings(0);
-      _exportSettings ??= await configDao.loadExportSettings(0);
-      _csvExportSettings ??= await configDao.loadCsvExportSettings(0);
-      _pdfExportSettings ??= await configDao.loadPdfExportSettings(0);
-      _intervalStorageManager ??= await IntervalStoreManager.load(configDao, 0);
-      _exportColumnsManager ??= await configDao.loadExportColumnsManager(0);
+      final SettingsLoader settingsLoader = await FileSettingsLoader.load();
+      _settings ??= await settingsLoader.loadSettings();
+      _exportSettings ??= await settingsLoader.loadExportSettings();
+      _csvExportSettings ??= await settingsLoader.loadCsvExportSettings();
+      _pdfExportSettings ??= await settingsLoader.loadPdfExportSettings();
+      _intervalStorageManager ??= await settingsLoader.loadIntervalStorageManager();
+      _exportColumnsManager ??= await settingsLoader.loadExportColumnsManager();
     } catch (e, stack) {
-      await ErrorReporting.reportCriticalError('Error loading config db', '$e\n$stack',);
+      await ErrorReporting.reportCriticalError('Error loading settings from files', '$e\n$stack',);
     }
 
     late BloodPressureRepository bpRepo;
@@ -129,8 +124,7 @@ class _AppState extends State<App> {
 
       // update logic
       if (_settings!.lastVersion == 0) {
-        await updateLegacySettings(_settings!, _exportSettings!, _csvExportSettings!, _pdfExportSettings!, _intervalStorageManager!);
-        await updateLegacyExport(_configDB!, _exportColumnsManager!);
+        await migrateSharedPreferences(_settings!, _exportSettings!, _csvExportSettings!, _pdfExportSettings!, _intervalStorageManager!);
 
         _settings!.lastVersion = 30;
         if (_exportSettings!.exportAfterEveryEntry) {
@@ -157,6 +151,27 @@ class _AppState extends State<App> {
       await ErrorReporting.reportCriticalError('Error performing upgrades:', '$e\n$stack',);
     }
 
+    final dbPath = await getDatabasesPath();
+    if (File(join(dbPath, 'config.db')).existsSync()) {
+      try {
+        await migrateDatabaseSettings(
+          _settings!,
+          _exportSettings!,
+          _csvExportSettings!,
+          _pdfExportSettings!,
+          _intervalStorageManager!,
+          _exportColumnsManager!,
+          medRepo,
+        );
+        File(join(dbPath, 'config.db')).copySync(join(dbPath, 'v39_config.db.backup'));
+        File(join(dbPath, 'config.db')).deleteSync();
+        File(join(dbPath, 'config.db-journal')).copySync(join(dbPath, 'v39_config.db-journal.backup'));
+        File(join(dbPath, 'config.db-journal')).deleteSync();
+      } catch (e, stack) {
+        await ErrorReporting.reportCriticalError('Error upgrading to file based settings:', '$e\n$stack',);
+      }
+    }
+
     _loadedChild = MultiRepositoryProvider(
       providers: [
         RepositoryProvider.value(value: bpRepo),
@@ -183,7 +198,7 @@ class _AppState extends State<App> {
   @override
   Widget build(BuildContext context) {
     if (!(kDebugMode && (const bool.fromEnvironment('testing_mode')))
-        && _loadedChild != null && _configDB != null && _entryDB != null) {
+        && _loadedChild != null && _settings != null && _entryDB != null) {
       return _loadedChild!;
     }
     return ConsistentFutureBuilder(
app/test/model/storage/db/file_settings_loader_test.dart
@@ -0,0 +1,106 @@
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:blood_pressure_app/model/export_import/column.dart';
+import 'package:blood_pressure_app/model/storage/db/file_settings_loader.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/interval_store.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  test('constructs new objects', () async {
+    await _loader((loader) async {
+      await expectLater(loader.loadSettings, returnsNormally);
+      await expectLater(loader.loadExportSettings, returnsNormally);
+      await expectLater(loader.loadCsvExportSettings, returnsNormally);
+      await expectLater(loader.loadPdfExportSettings, returnsNormally);
+      await expectLater(loader.loadIntervalStorageManager, returnsNormally);
+      await expectLater(loader.loadExportColumnsManager, returnsNormally);
+    });
+
+  });
+
+  test('persists changes', () async {
+    await _loader((loader1) async {
+      final loader2 = await FileSettingsLoader.load('tmp');
+
+      final settings1 = await loader1.loadSettings();
+      settings1.sysColor = Colors.blueGrey;
+      final settings2 = await loader2.loadSettings();
+      expect(settings2.sysColor.value, settings1.sysColor.value);
+
+      final exportSettings1 = await loader1.loadExportSettings();
+      exportSettings1.exportFormat = ExportFormat.db;
+      final exportSettings2 = await loader2.loadExportSettings();
+      expect(exportSettings2.exportFormat, exportSettings1.exportFormat);
+
+      final csvExportSettings1 = await loader1.loadCsvExportSettings();
+      csvExportSettings1.fieldDelimiter = 'asdsf.",';
+      final csvExportSettings2 = await loader2.loadCsvExportSettings();
+      expect(csvExportSettings2.fieldDelimiter, csvExportSettings1.fieldDelimiter);
+
+      final pdfExportSettings1 = await loader1.loadPdfExportSettings();
+      pdfExportSettings1.cellHeight = 3.1415926;
+      final pdfExportSettings2 = await loader2.loadPdfExportSettings();
+      expect(pdfExportSettings2.cellHeight, pdfExportSettings1.cellHeight);
+
+      final intervalStorageManager1 = await loader1.loadIntervalStorageManager();
+      intervalStorageManager1.mainPage.changeStepSize(TimeStep.lifetime);
+      final intervalStorageManager2 = await loader2.loadIntervalStorageManager();
+      expect(intervalStorageManager2.mainPage.stepSize, intervalStorageManager1.mainPage.stepSize);
+
+      final exportColumnsManager1 = await loader1.loadExportColumnsManager();
+      exportColumnsManager1.addOrUpdate(TimeColumn('tsttime', 'tst-YYYY'));
+      final exportColumnsManager2 = await loader2.loadExportColumnsManager();
+      expect(exportColumnsManager2.userColumns.keys, exportColumnsManager1.userColumns.keys);
+    });
+  });
+}
+
+Future<void> _loader(void Function(FileSettingsLoader) body) async {
+  final files = {};
+  return IOOverrides.runZoned(
+    () async => body(await FileSettingsLoader.load('tmp')),
+    createDirectory: (path) => _MockDir(),
+    createFile: (path) {
+      if (files[path] == null) files[path] = _MockFile();
+      return files[path];
+    },
+  );
+}
+
+class _MockDir implements Directory {
+  bool _exists = false;
+
+  @override
+  void createSync({bool recursive = false}) => _exists = true;
+
+  @override
+  Future<bool> exists() async => _exists;
+
+  @override
+  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+}
+
+class _MockFile implements File {
+  String? _content;
+
+  @override
+  String readAsStringSync({Encoding encoding = utf8}) {
+    if (_content == null) throw FileSystemException();
+    return _content!;
+  }
+
+  @override
+  void writeAsStringSync(String contents, {
+    FileMode mode = FileMode.write,
+    Encoding encoding = utf8,
+    bool flush = false,
+  }) {
+    _content = contents;
+  }
+
+  @override
+  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
+}
app/test/model/config_db_test.dart
@@ -1,84 +0,0 @@
-import 'package:blood_pressure_app/model/storage/db/config_dao.dart';
-import 'package:blood_pressure_app/model/storage/db/config_db.dart';
-import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
-import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
-import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
-import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
-import 'package:blood_pressure_app/model/storage/interval_store.dart';
-import 'package:blood_pressure_app/model/storage/settings_store.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:sqflite_common_ffi/sqflite_ffi.dart';
-
-void main() {
-  group('ConfigDB', () {
-    setUpAll(() {
-      sqfliteFfiInit();
-      // Avoid warning in logs by avoiding `databaseFactory` setter
-      databaseFactoryOrNull = databaseFactoryFfi;
-    });
-
-    test('should initialize database without error', () async {
-      final db = await ConfigDB.open(dbPath: inMemoryDatabasePath, isFullPath: true);
-      expect(db.database.isOpen, true);
-    });
-    test('tables should exist', () async {
-      final db = await ConfigDB.open(dbPath: inMemoryDatabasePath, isFullPath: true);
-      final existingTables = await db.database.query('sqlite_master');
-      final tableNames = existingTables.map((e) => e['name'] as String);
-
-      expect(tableNames.contains(ConfigDB.settingsTable), true);
-      expect(tableNames.contains(ConfigDB.exportSettingsTable), true);
-      expect(tableNames.contains(ConfigDB.exportCsvSettingsTable), true);
-      expect(tableNames.contains(ConfigDB.exportPdfSettingsTable), true);
-      expect(tableNames.contains(ConfigDB.selectedIntervalStorageTable), true);
-      expect(tableNames.contains(ConfigDB.exportColumnsTable), true);
-    });
-    test('should save and load table entries', () async {
-      final db = await ConfigDB.open(dbPath: inMemoryDatabasePath, isFullPath: true);
-      await db.database.insert(ConfigDB.settingsTable, {'profile_id': 0, 'settings_json': '{"test": 123}'});
-      final queriedData = await db.database.query(ConfigDB.settingsTable);
-      expect(queriedData.length, 1);
-      expect(queriedData[0]['profile_id'], 0);
-      expect(queriedData[0]['settings_json'], '{"test": 123}');
-    });
-  });
-
-  group('ConfigDAO', () {
-    setUpAll(() {
-      sqfliteFfiInit();
-      databaseFactoryOrNull = databaseFactoryFfi;
-    });
-
-    test('should initialize', () async {
-      final rawDB = await ConfigDB.open(dbPath: inMemoryDatabasePath, isFullPath: true);
-      ConfigDao(rawDB);
-    });
-    test('should create classes when no data is present', () async {
-      final rawDB = await ConfigDB.open(dbPath: inMemoryDatabasePath, isFullPath: true);
-      final dao = ConfigDao(rawDB);
-
-      expect((await dao.loadSettings(0)).toJson(), Settings().toJson());
-      expect((await dao.loadExportSettings(0)).toJson(), ExportSettings().toJson());
-      expect((await dao.loadCsvExportSettings(0)).toJson(), CsvExportSettings().toJson());
-      expect((await dao.loadPdfExportSettings(0)).toJson(), PdfExportSettings().toJson());
-      expect((await dao.loadIntervalStorage(0,0)).stepSize, IntervalStorage().stepSize);
-      expect((await dao.loadExportColumnsManager(0)).userColumns, ExportColumnsManager().userColumns);
-    });
-    test('should save changes', () async {
-      final rawDB = await ConfigDB.open(dbPath: inMemoryDatabasePath, isFullPath: true);
-      final dao = ConfigDao(rawDB);
-
-      final settings = await dao.loadSettings(0);
-      settings.dateFormatString = 'Test string';
-      settings.sysColor = Colors.deepOrange;
-      settings.animationSpeed = 69;
-
-      final initialSettingsJson = settings.toJson();
-      settings.dispose();
-
-      final newSettings = await dao.loadSettings(0);
-      expect(newSettings.toJson(), initialSettingsJson);
-    });
-  });
-}
app/pubspec.lock
@@ -31,7 +31,7 @@ packages:
     source: hosted
     version: "5.1.1"
   archive:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: archive
       sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
@@ -194,10 +194,10 @@ packages:
     dependency: "direct main"
     description:
       name: collection
-      sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
+      sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
       url: "https://pub.dev"
     source: hosted
-    version: "1.19.0"
+    version: "1.18.0"
   convert:
     dependency: transitive
     description:
@@ -945,10 +945,10 @@ packages:
     dependency: transitive
     description:
       name: string_scanner
-      sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
+      sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
       url: "https://pub.dev"
     source: hosted
-    version: "1.3.0"
+    version: "1.2.0"
   sync_http:
     dependency: transitive
     description:
@@ -977,26 +977,26 @@ packages:
     dependency: transitive
     description:
       name: test
-      sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f"
+      sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e"
       url: "https://pub.dev"
     source: hosted
-    version: "1.25.8"
+    version: "1.25.7"
   test_api:
     dependency: transitive
     description:
       name: test_api
-      sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
+      sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
       url: "https://pub.dev"
     source: hosted
-    version: "0.7.3"
+    version: "0.7.2"
   test_core:
     dependency: transitive
     description:
       name: test_core
-      sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d"
+      sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696"
       url: "https://pub.dev"
     source: hosted
-    version: "0.6.5"
+    version: "0.6.4"
   timing:
     dependency: transitive
     description:
app/pubspec.yaml
@@ -40,6 +40,7 @@ dependencies:
 
   # desktop only
   sqflite_common_ffi: ^2.3.3
+  archive: ^3.6.1
 
 dev_dependencies:
   integration_test:
@@ -56,4 +57,3 @@ dev_dependencies:
 flutter:
   uses-material-design: true
   generate: true
-
.gitignore
@@ -1,3 +1,8 @@
-/.idea/
 /fastlane/metadata/android/en-US/images/phoneScreenshots/resizeAll.sh
-**/failures/*.png
\ No newline at end of file
+**/failures/*.png
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
\ No newline at end of file
blood_pressure_app.iml
@@ -1,156 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module type="JAVA_MODULE" version="4">
-  <component name="NewModuleRootManager" inherit-compiler-output="true">
-    <exclude-output />
-    <content url="file://$MODULE_DIR$">
-      <excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/file_saver/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/file_saver/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/file_saver/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/file_saver/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/file_saver/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/file_saver/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/share_plus_linux/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/share_plus_linux/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/share_plus_linux/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/file_saver/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/file_saver/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/file_saver/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/file_saver/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/file_saver/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/file_saver/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/share_plus_windows/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/share_plus_windows/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/share_plus_windows/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/share_plus/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/share_plus/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/share_plus/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/share_plus/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/share_plus/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/share_plus/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/share_plus/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/share_plus/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/share_plus/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/share_plus/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/share_plus/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/share_plus/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/build" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/build" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/health_data_store/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/health_data_store/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/health_data_store/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/.dart_tool/flutter_gen/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/.dart_tool/flutter_gen/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/.dart_tool/flutter_gen/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/package_info_plus/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/shared_preferences_windows/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/url_launcher_windows/example/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/build" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/.dart_tool" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/.pub" />
-      <excludeFolder url="file://$MODULE_DIR$/app/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/build" />
-    </content>
-    <orderEntry type="sourceFolder" forTests="false" />
-    <orderEntry type="library" name="Dart SDK" level="project" />
-    <orderEntry type="library" name="Flutter Plugins" level="project" />
-    <orderEntry type="library" name="Dart Packages" level="project" />
-  </component>
-</module>
\ No newline at end of file