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}