Commit 2982b8d

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-06-11 20:01:06
Extract global setup logic to widget
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 4841240
Changed files (2)
app/lib/app.dart
@@ -0,0 +1,182 @@
+import 'dart:io';
+
+import 'package:blood_pressure_app/components/consistent_future_builder.dart';
+import 'package:blood_pressure_app/model/blood_pressure/medicine/intake_history.dart';
+import 'package:blood_pressure_app/model/blood_pressure/model.dart';
+import 'package:blood_pressure_app/model/export_import/export_configuration.dart';
+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/intervall_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
+import 'package:blood_pressure_app/model/storage/update_legacy_settings.dart';
+import 'package:blood_pressure_app/screens/home_screen.dart';
+import 'package:blood_pressure_app/screens/loading_screen.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:fluttertoast/fluttertoast.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';
+
+/// Base class for the entire app.
+///
+/// Sets up databases, performs update logic and provides styles and ancestors
+/// that should be available everywhere in the app.
+class App extends StatefulWidget {
+  /// Create the base for the entire app.
+  const App();
+
+  @override
+  State<App> createState() => _AppState();
+}
+
+class _AppState extends State<App> {
+  /// Database object for app settings.
+  ConfigDB? _configDB;
+  BloodPressureModel? _bloodPressureModel;
+
+  @override
+  void dispose() {
+    _configDB?.database.close();
+    _configDB = null;
+    _bloodPressureModel?.close();
+    _bloodPressureModel = null;
+    super.dispose();
+  }
+
+  /// Load the primary app data asynchronously to allow load animations.
+  Future<Widget> _loadApp() async {
+    WidgetsFlutterBinding.ensureInitialized();
+    // 2 different db files
+    _bloodPressureModel = await BloodPressureModel.create();
+    _configDB = await ConfigDB.open();
+
+    final configDao = ConfigDao(_configDB!);
+
+    final settings = await configDao.loadSettings(0);
+    final exportSettings = await configDao.loadExportSettings(0);
+    final csvExportSettings = await configDao.loadCsvExportSettings(0);
+    final pdfExportSettings = await configDao.loadPdfExportSettings(0);
+    final intervalStorageManager = await IntervallStoreManager.load(configDao, 0);
+    final exportColumnsManager = await configDao.loadExportColumnsManager(0);
+
+    // TODO: unify with blood pressure model (#257)
+    late final IntakeHistory intakeHistory;
+    try {
+      if (settings.medications.isNotEmpty) {
+        final intakeString = File(join(await getDatabasesPath(), 'medicine.intakes')).readAsStringSync();
+        intakeHistory = IntakeHistory.deserialize(intakeString, settings.medications);
+      } else {
+        intakeHistory = IntakeHistory([]);
+      }
+    } catch (e, stack) {
+      assert(e is PathNotFoundException, '$e\n$stack');
+      intakeHistory = IntakeHistory([]);
+    }
+    intakeHistory.addListener(() async {
+      File(join(await getDatabasesPath(), 'medicine.intakes')).writeAsStringSync(intakeHistory.serialize());
+    });
+
+    // update logic
+    if (settings.lastVersion == 0) {
+      await updateLegacySettings(settings, exportSettings, csvExportSettings, pdfExportSettings, intervalStorageManager);
+      await updateLegacyExport(_configDB!, exportColumnsManager);
+
+      settings.lastVersion = 30;
+      if (exportSettings.exportAfterEveryEntry) {
+        await Fluttertoast.showToast(
+          msg: r'Please review your export settings to ensure everything works as expected.',
+        );
+      }
+    }
+    if (settings.lastVersion == 30) {
+      if (pdfExportSettings.exportFieldsConfiguration.activePreset == ExportImportPreset.bloodPressureApp) {
+        pdfExportSettings.exportFieldsConfiguration.activePreset = ExportImportPreset.bloodPressureAppPdf;
+      }
+      settings.lastVersion = 31;
+    }
+    if (settings.allowMissingValues && settings.validateInputs) settings.validateInputs = false;
+
+    settings.lastVersion = int.parse((await PackageInfo.fromPlatform()).buildNumber);
+
+    // Reset the step size intervall to current on startup
+    intervalStorageManager.mainPage.setToMostRecentIntervall();
+
+    return MultiProvider(providers: [
+      ChangeNotifierProvider(create: (context) => _bloodPressureModel!),
+      ChangeNotifierProvider(create: (context) => settings),
+      ChangeNotifierProvider(create: (context) => exportSettings),
+      ChangeNotifierProvider(create: (context) => csvExportSettings),
+      ChangeNotifierProvider(create: (context) => pdfExportSettings),
+      ChangeNotifierProvider(create: (context) => intervalStorageManager),
+      ChangeNotifierProvider(create: (context) => exportColumnsManager),
+      ChangeNotifierProvider(create: (context) => intakeHistory),
+    ], child: _buildAppRoot(),);
+  }
+
+  @override
+  Widget build(BuildContext context) => ConsistentFutureBuilder(
+    future: _loadApp(),
+    onWaiting: const LoadingScreen(),
+    onData: (context, widget) => widget,
+  );
+
+  /// Central [MaterialApp] widget of the app that sets the uniform style options.
+  Widget _buildAppRoot() => Consumer<Settings>(
+    builder: (context, settings, child) => MaterialApp(
+      title: 'Blood Pressure App',
+      onGenerateTitle: (context) => AppLocalizations.of(context)!.title,
+      theme: _buildTheme(ColorScheme.fromSeed(
+        seedColor: settings.accentColor,
+      ),),
+      darkTheme: _buildTheme(ColorScheme.fromSeed(
+        seedColor: settings.accentColor,
+        brightness: Brightness.dark,
+        background: Colors.black,
+      ),),
+      themeMode: settings.themeMode,
+      localizationsDelegates: AppLocalizations.localizationsDelegates,
+      supportedLocales: AppLocalizations.supportedLocales,
+      locale: settings.language,
+      home: const AppHome(),
+    ),
+  );
+
+  ThemeData _buildTheme(ColorScheme colorScheme) {
+    final inputBorder = OutlineInputBorder(
+      borderSide: BorderSide(
+        width: 3,
+        // Through black background outlineVariant has enough contrast.
+        color: (colorScheme.background == Colors.black)
+            ? colorScheme.outlineVariant
+            : colorScheme.outline,
+      ),
+      borderRadius: BorderRadius.circular(20),
+    );
+
+    return ThemeData(
+      colorScheme: colorScheme,
+      useMaterial3: true,
+      inputDecorationTheme: InputDecorationTheme(
+        errorMaxLines: 5,
+        border: inputBorder,
+        enabledBorder: inputBorder,
+      ),
+      appBarTheme: const AppBarTheme(
+        centerTitle: true,
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.only(
+            bottomRight: Radius.circular(15),
+            bottomLeft: Radius.circular(15),
+          ),
+        ),
+      ),
+      snackBarTheme: SnackBarThemeData(
+        shape: RoundedRectangleBorder(
+          borderRadius: BorderRadius.circular(8),
+        ),
+      ),
+    );
+  }
+}
app/lib/main.dart
@@ -1,181 +1,33 @@
-import 'dart:io';
-
-import 'package:blood_pressure_app/components/consistent_future_builder.dart';
-import 'package:blood_pressure_app/model/blood_pressure/medicine/intake_history.dart';
-import 'package:blood_pressure_app/model/blood_pressure/model.dart';
-import 'package:blood_pressure_app/model/export_import/export_configuration.dart';
-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/intervall_store.dart';
-import 'package:blood_pressure_app/model/storage/settings_store.dart';
-import 'package:blood_pressure_app/model/storage/update_legacy_settings.dart';
-import 'package:blood_pressure_app/screens/home_screen.dart';
-import 'package:blood_pressure_app/screens/loading_screen.dart';
+import 'package:blood_pressure_app/app.dart';
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'package:fluttertoast/fluttertoast.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';
-
-late final ConfigDB _database;
-late final BloodPressureModel _bloodPressureModel;
-
-void main() async {
-  runApp(ConsistentFutureBuilder(
-      future: _loadApp(),
-      onWaiting: const LoadingScreen(),
-      onData: (context, widget) => widget,
-  ),);
-}
-
-/// Load the primary app data asynchronously to allow adding load animations.
-Future<Widget> _loadApp() async {
-  WidgetsFlutterBinding.ensureInitialized();
-  // 2 different db files
-  _bloodPressureModel = await BloodPressureModel.create();
-
-  _database = await ConfigDB.open();
-  final configDao = ConfigDao(_database);
-
-  final settings = await configDao.loadSettings(0);
-  final exportSettings = await configDao.loadExportSettings(0);
-  final csvExportSettings = await configDao.loadCsvExportSettings(0);
-  final pdfExportSettings = await configDao.loadPdfExportSettings(0);
-  final intervalStorageManager = await IntervallStoreManager.load(configDao, 0);
-  final exportColumnsManager = await configDao.loadExportColumnsManager(0);
 
-  // TODO: unify with blood pressure model (#257)
-  late final IntakeHistory intakeHistory;
-  try {
-    if (settings.medications.isNotEmpty) {
-      final intakeString = File(join(await getDatabasesPath(), 'medicine.intakes')).readAsStringSync();
-      intakeHistory = IntakeHistory.deserialize(intakeString, settings.medications);
-    } else {
-      intakeHistory = IntakeHistory([]);
-    }
-  } catch (e, stack) {
-    assert(e is PathNotFoundException, '$e\n$stack');
-    intakeHistory = IntakeHistory([]);
-  }
-  intakeHistory.addListener(() async {
-    File(join(await getDatabasesPath(), 'medicine.intakes')).writeAsStringSync(intakeHistory.serialize());
-  });
-
-  // update logic
-  if (settings.lastVersion == 0) {
-    await updateLegacySettings(settings, exportSettings, csvExportSettings, pdfExportSettings, intervalStorageManager);
-    await updateLegacyExport(_database, exportColumnsManager);
-
-    settings.lastVersion = 30;
-    if (exportSettings.exportAfterEveryEntry) {
-      await Fluttertoast.showToast(
-        msg: r'Please review your export settings to ensure everything works as expected.',
-      );
-    }
-  }
-  if (settings.lastVersion == 30) {
-    if (pdfExportSettings.exportFieldsConfiguration.activePreset == ExportImportPreset.bloodPressureApp) {
-      pdfExportSettings.exportFieldsConfiguration.activePreset = ExportImportPreset.bloodPressureAppPdf;
-    }
-    settings.lastVersion = 31;
+/// Start the app and load databases.
+///
+/// In debug mode `FORCE_CLEAR_ALL_APP_DATA` as the first argument deletes
+/// database files before app start.
+void main([List<String>? args]) async {
+  if (kDebugMode && args?.firstOrNull == 'FORCE_CLEAR_ALL_APP_DATA') {
+    await _forceClearAppData();
   }
-  if (settings.allowMissingValues && settings.validateInputs) settings.validateInputs = false;
-
-  settings.lastVersion = int.parse((await PackageInfo.fromPlatform()).buildNumber);
-
-  // Reset the step size intervall to current on startup
-  intervalStorageManager.mainPage.setToMostRecentIntervall();
-
-  print(AppLocalizations.delegate);
-
-  return MultiProvider(providers: [
-    ChangeNotifierProvider(create: (context) => _bloodPressureModel),
-    ChangeNotifierProvider(create: (context) => settings),
-    ChangeNotifierProvider(create: (context) => exportSettings),
-    ChangeNotifierProvider(create: (context) => csvExportSettings),
-    ChangeNotifierProvider(create: (context) => pdfExportSettings),
-    ChangeNotifierProvider(create: (context) => intervalStorageManager),
-    ChangeNotifierProvider(create: (context) => exportColumnsManager),
-    ChangeNotifierProvider(create: (context) => intakeHistory),
-  ], child: const AppRoot(),);
+  runApp(App());
 }
 
-/// Central [MaterialApp] widget of the app that sets the uniform style options.
-class AppRoot extends StatelessWidget {
-  /// Create the base for the entire app.
-  const AppRoot({super.key});
-
-  @override
-  Widget build(BuildContext context) =>
-    Consumer<Settings>(builder: (context, settings, child) => MaterialApp(
-      title: 'Blood Pressure App',
-      onGenerateTitle: (context) => AppLocalizations.of(context)!.title,
-      theme: _buildTheme(ColorScheme.fromSeed(
-        seedColor: settings.accentColor,
-      ),),
-      darkTheme: _buildTheme(ColorScheme.fromSeed(
-        seedColor: settings.accentColor,
-        brightness: Brightness.dark,
-        background: Colors.black,
-      ),),
-      themeMode: settings.themeMode,
-      localizationsDelegates: AppLocalizations.localizationsDelegates,
-      supportedLocales: AppLocalizations.supportedLocales,
-      locale: settings.language,
-      home: const AppHome(),
-    ),
-  );
-
-  ThemeData _buildTheme(ColorScheme colorScheme) {
-    final inputBorder = OutlineInputBorder(
-      borderSide: BorderSide(
-        width: 3,
-        // Through black background outlineVariant has enough contrast.
-        color: (colorScheme.background == Colors.black)
-            ? colorScheme.outlineVariant
-            : colorScheme.outline,
-      ),
-      borderRadius: BorderRadius.circular(20),
-    );
-
-    return ThemeData(
-      colorScheme: colorScheme,
-      useMaterial3: true,
-      inputDecorationTheme: InputDecorationTheme(
-        errorMaxLines: 5,
-        border: inputBorder,
-        enabledBorder: inputBorder,
-      ),
-      appBarTheme: const AppBarTheme(
-        centerTitle: true,
-        shape: RoundedRectangleBorder(
-          borderRadius: BorderRadius.only(
-            bottomRight: Radius.circular(15),
-            bottomLeft: Radius.circular(15),
-          ),
-        ),
-      ),
-      snackBarTheme: SnackBarThemeData(
-        shape: RoundedRectangleBorder(
-          borderRadius: BorderRadius.circular(8),
-        ),
-      ),
-    );
+/// Removes all files stored by the app without maintaining integrity.
+Future<void> _forceClearAppData() async {
+  /* TODO
+  try {
+    File(join(await getDatabasesPath(), 'blood_pressure.db')).deleteSync();
+    File(join(await getDatabasesPath(), 'blood_pressure.db-journal')).deleteSync();
+  } on FileSystemException {
+    // File is likely already deleted or couldn't be created in the first place.
   }
-}
-
-bool _isDatabaseClosed = false;
-/// Close all connections to the databases and remove all listeners from provided objects.
-///
-/// The app will most likely stop working after invoking this.
-///
-/// Invoking the function multiple times is safe.
-Future<void> closeDatabases() async {
-  if (_isDatabaseClosed) return;
-  _isDatabaseClosed = true;
-
-  await _database.database.close();
-  await _bloodPressureModel.close();
+  try {
+    File(join(await getDatabasesPath(), 'config.db')).deleteSync();
+    File(join(await getDatabasesPath(), 'config.db-journal')).deleteSync();
+  } on FileSystemException { }
+  try {
+    File(join(await getDatabasesPath(), 'medicine.intakes')).deleteSync();
+  } on FileSystemException { }
+   */
 }