main
1import 'dart:math';
2import 'dart:ui';
3
4import 'package:flutter/material.dart';
5
6/// Loading page that is displayed on app start.
7///
8/// Contains a simplified app logo animation.
9class LoadingScreen extends StatelessWidget {
10 /// Loading page that is displayed on app start.
11 const LoadingScreen({super.key});
12
13 static const _duration = Duration(milliseconds: 250);
14
15 @override
16 Widget build(BuildContext context) {
17 final dimensions = MediaQuery.of(context).size;
18 return MaterialApp(
19 home: Center(
20 child: TweenAnimationBuilder(
21 tween: Tween<double>(begin: 0, end: 1),
22 duration: _duration,
23 builder: (BuildContext context, double value, Widget? child) => Container(
24 padding: const EdgeInsets.only(bottom: 100),
25 child: SizedBox.square(
26 dimension: max(0, dimensions.width - 20),
27 child: CustomPaint(
28 painter: _LogoPainter(progress: value),
29 ),
30 ),
31 ),
32 ),
33 ),
34 );
35 }
36}
37
38/// Paints the logo of the App.
39class _LogoPainter extends CustomPainter {
40 _LogoPainter({required this.progress});
41
42 /// Percentage of the logo to be drawn (ranges from 0 to 1).
43 final double progress;
44
45 @override
46 void paint(Canvas canvas, Size size) {
47 final Paint paint = Paint()
48 ..color = const Color.fromARGB(255, 0xb0, 0x18, 0x22)
49 ..style = PaintingStyle.stroke
50 ..strokeWidth = size.shortestSide / 20
51 ..strokeCap = StrokeCap.round
52 ..strokeJoin = StrokeJoin.round;
53
54 final path = Path();
55 path.moveTo(size.width * 0.5, size.height * 0.8);
56 path.lineTo(size.width * 0.2, size.height * 0.5);
57 path.lineTo(size.width * 0.15, size.height * 0.4);
58 path.lineTo(size.width * 0.27, size.height * 0.3);
59 path.lineTo(size.width * 0.37, size.height * 0.31);
60 path.lineTo(size.width * 0.5, size.height * 0.43);
61 path.lineTo(size.width * 0.63, size.height * 0.32);
62 path.lineTo(size.width * 0.74, size.height * 0.31);
63 path.lineTo(size.width * 0.76, size.height * 0.32);
64 path.lineTo(size.width * 0.82, size.height * 0.42);
65 path.lineTo(size.width * 0.818, size.height * 0.45);
66 path.lineTo(size.width * 0.65, size.height * 0.65);
67 canvas.drawPath(subPath(path, progress), paint);
68 }
69
70 @override
71 bool shouldRepaint(_LogoPainter oldDelegate) =>
72 oldDelegate.progress != progress;
73}
74
75/// Get a percentage of [originalPath].
76///
77/// [animationPercent] is a value from 0 to 1.
78Path subPath(Path originalPath, double animationPercent) {
79 final totalLength = originalPath
80 .computeMetrics()
81 .fold(0.0, (double prev, PathMetric metric) => prev + metric.length);
82
83 return _extractPathUntilLength(originalPath, totalLength * animationPercent);
84}
85
86Path _extractPathUntilLength(Path originalPath, double length,) {
87 final path = Path();
88
89 final metricsIterator = originalPath.computeMetrics().iterator;
90 var isLastSegment = false;
91 var currentLength = 0.0;
92
93 while (metricsIterator.moveNext() && !isLastSegment) {
94 final metric = metricsIterator.current;
95
96 final nextLength = currentLength + metric.length;
97 isLastSegment = (nextLength > length);
98
99 assert(length - currentLength >= 0);
100 final pathSegment = metric.extractPath(0.0,
101 min(length - currentLength, metric.length),);
102
103 path.addPath(pathSegment, Offset.zero);
104
105 currentLength = nextLength;
106 }
107
108 return path;
109}