Commit 4b409ce

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2023-11-03 16:16:13
add startup animation
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent b18b391
Changed files (2)
lib/screens/loading.dart
@@ -0,0 +1,108 @@
+import 'dart:math';
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+
+/// Loading page that is displayed on app start.
+class LoadingScreen extends StatelessWidget {
+  const LoadingScreen({super.key});
+
+  static const _duration = Duration(milliseconds: 250);
+
+  @override
+  Widget build(BuildContext context) {
+    final dimensions = MediaQuery.of(context).size;
+    return MaterialApp(
+      home: Center(
+        child: TweenAnimationBuilder(
+          tween: Tween<double>(begin: 0, end: 1),
+          duration: _duration,
+          builder: (BuildContext context, double value, Widget? child) {
+            return Container(
+              padding: const EdgeInsets.only(bottom: 100),
+              child: SizedBox.square(
+                dimension: dimensions.width - 20,
+                child: CustomPaint(
+                  painter: _LogoPainter(progress: value),
+                ),
+              ),
+            );
+          },
+        ),
+      ),
+    );
+  }
+}
+
+/// Paints the logo of the App.
+class _LogoPainter extends CustomPainter {
+  _LogoPainter({required this.progress});
+
+  /// Percentage of the logo to be drawn (ranges from 0 to 1).
+  final double progress;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    Paint paint = Paint()
+      ..color = const Color.fromARGB(255, 0xb0, 0x18, 0x22)
+      ..style = PaintingStyle.stroke
+      ..strokeWidth = size.shortestSide / 20
+      ..strokeCap = StrokeCap.round
+      ..strokeJoin = StrokeJoin.round;
+
+    final path = Path();
+    path.moveTo(size.width * 0.5, size.height * 0.8);
+    path.lineTo(size.width * 0.2, size.height * 0.5);
+    path.lineTo(size.width * 0.15, size.height * 0.4);
+    path.lineTo(size.width * 0.27, size.height * 0.3);
+    path.lineTo(size.width * 0.37, size.height * 0.31);
+    path.lineTo(size.width * 0.5, size.height * 0.43);
+    path.lineTo(size.width * 0.63, size.height * 0.32);
+    path.lineTo(size.width * 0.74, size.height * 0.31);
+    path.lineTo(size.width * 0.76, size.height * 0.32);
+    path.lineTo(size.width * 0.82, size.height * 0.42);
+    path.lineTo(size.width * 0.818, size.height * 0.45);
+    path.lineTo(size.width * 0.65, size.height * 0.65);
+    canvas.drawPath(_subPath(path, progress), paint);
+  }
+
+  @override
+  bool shouldRepaint(_LogoPainter oldDelegate) =>
+      oldDelegate.progress != progress;
+
+  /// Get a percentage of [originalPath].
+  ///
+  /// [animationPercent] is a value from 0 to 1.
+  Path _subPath(Path originalPath, double animationPercent,) {
+    final totalLength = originalPath
+        .computeMetrics()
+        .fold(0.0, (double prev, PathMetric metric) => prev + metric.length);
+
+    return _extractPathUntilLength(originalPath, totalLength * animationPercent);
+  }
+
+  Path _extractPathUntilLength(Path originalPath, double length,) {
+    final path = Path();
+
+    var metricsIterator = originalPath.computeMetrics().iterator;
+    var isLastSegment = false;
+    var currentLength = 0.0;
+
+    while (metricsIterator.moveNext() && !isLastSegment) {
+      var metric = metricsIterator.current;
+
+      var nextLength = currentLength + metric.length;
+      isLastSegment = (nextLength > length);
+
+      assert(length - currentLength >= 0);
+      final pathSegment = metric.extractPath(0.0,
+          min(length - currentLength, metric.length));
+
+      path.addPath(pathSegment, Offset.zero);
+
+      currentLength = nextLength;
+    }
+
+    return path;
+  }
+}
\ No newline at end of file
lib/main.dart
@@ -1,3 +1,4 @@
+import 'package:blood_pressure_app/components/consistent_future_builder.dart';
 import 'package:blood_pressure_app/model/blood_pressure.dart';
 import 'package:blood_pressure_app/model/storage/db/config_dao.dart';
 import 'package:blood_pressure_app/model/storage/db/config_db.dart';
@@ -5,6 +6,7 @@ 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.dart';
+import 'package:blood_pressure_app/screens/loading.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
@@ -17,6 +19,15 @@ 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();
@@ -37,17 +48,16 @@ void main() async {
   // Reset the step size intervall to current on startup
   intervalStorageManager.mainPage.setToMostRecentIntervall();
 
-  runApp(MultiProvider(providers: [
+  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),
-  ], child: const AppRoot()));
+  ], child: const AppRoot());
 }
 
-// TODO: centralize disabling
 class AppRoot extends StatelessWidget {
   const AppRoot({super.key});