Line data Source code
1 : import 'dart:math';
2 :
3 : import 'package:amadeus_proto/api/models/profile.dart';
4 : import 'package:amadeus_proto/pages/home/widgets/streak%20widgets/streak_calendar_preview.dart';
5 : import 'package:amadeus_proto/pages/home/widgets/streak%20widgets/streak_preview.dart';
6 : import 'package:flutter/material.dart';
7 : import 'package:localization/localization.dart';
8 :
9 : class StreakCard extends StatefulWidget {
10 0 : const StreakCard({super.key, this.height = 250, required this.user});
11 :
12 : final double height;
13 : final Profile user;
14 :
15 0 : @override
16 0 : State<StreakCard> createState() => _StreakCardState();
17 : }
18 :
19 : class _StreakCardState extends State<StreakCard>
20 : with SingleTickerProviderStateMixin {
21 : late Animation<double> animation;
22 : late AnimationController controller;
23 :
24 0 : @override
25 : void initState() {
26 0 : super.initState();
27 0 : controller =
28 0 : AnimationController(duration: const Duration(seconds: 5), vsync: this)
29 0 : ..addStatusListener((status) {
30 0 : if (status == AnimationStatus.completed) {
31 0 : controller.reset();
32 0 : controller.forward();
33 : }
34 : });
35 0 : animation = Tween<double>(begin: -0.8, end: 2.8).animate(controller);
36 0 : controller.forward();
37 : }
38 :
39 0 : @override
40 : void dispose() {
41 0 : controller.dispose();
42 0 : super.dispose();
43 : }
44 :
45 0 : @override
46 : Widget build(BuildContext context) {
47 0 : return Card(
48 0 : color: Theme.of(context).primaryColor,
49 0 : shape: RoundedRectangleBorder(
50 0 : borderRadius: BorderRadius.circular(24),
51 : ),
52 0 : child: ClipRRect(
53 0 : borderRadius: BorderRadius.circular(24),
54 0 : child: AnimatedDottedBackgroud(
55 0 : animation: animation,
56 0 : height: widget.height,
57 0 : child: SizedBox(
58 0 : height: widget.height,
59 0 : child: Column(
60 0 : children: [
61 0 : Padding(
62 : padding: const EdgeInsets.only(top: 8.0),
63 0 : child: SizedBox(
64 0 : height: widget.height / 2.2,
65 0 : child: StreakPreview(
66 0 : user: widget.user,
67 : ),
68 : ),
69 : ),
70 0 : Text(
71 0 : "streak.message".i18n([widget.user.streakDays.length.toString()]),
72 : style: const TextStyle(
73 : color: Colors.white,
74 : fontSize: 17,
75 : ),
76 : ),
77 0 : Text(
78 0 : "calendar.status".i18n(),
79 : style: const TextStyle(
80 : color: Colors.white,
81 : fontSize: 11,
82 : ),
83 : ),
84 0 : Expanded(
85 0 : child: StreakCalendarPreview(
86 0 : user: widget.user,
87 : ),
88 : )
89 : ],
90 : ),
91 : ),
92 : ),
93 : ),
94 : );
95 : }
96 : }
97 :
98 : class AnimatedDottedBackgroud extends AnimatedWidget {
99 0 : const AnimatedDottedBackgroud(
100 : {super.key,
101 : required this.height,
102 : this.curve = Curves.linear,
103 : required Animation<double> animation,
104 : this.child})
105 0 : : super(listenable: animation);
106 :
107 : final double height;
108 : final Curve curve;
109 : final Widget? child;
110 :
111 0 : @override
112 : Widget build(BuildContext context) {
113 0 : final animation = listenable as Animation<double>;
114 0 : return CustomPaint(
115 0 : painter: _DottedFadeOutPatternPainter(
116 : baseOpacity: 0.7,
117 0 : animationProgress: animation.value,
118 : color: Colors.white,
119 : radius: 2,
120 : spacing: 15,
121 : ),
122 0 : child: child);
123 : }
124 : }
125 :
126 : class _DottedFadeOutPatternPainter extends CustomPainter {
127 0 : _DottedFadeOutPatternPainter(
128 : {required this.animationProgress,
129 : this.color,
130 : this.style,
131 : this.radius,
132 : this.spacing,
133 : required this.baseOpacity});
134 :
135 : final Color? color;
136 : final PaintingStyle? style;
137 : final double? radius;
138 : final double? spacing;
139 : final double animationProgress;
140 : final double baseOpacity;
141 :
142 0 : @override
143 : void paint(Canvas canvas, Size size) {
144 0 : assert(0 <= baseOpacity && baseOpacity <= 1);
145 0 : final Paint paint = Paint()
146 0 : ..color = color ?? Colors.blue
147 0 : ..style = style ?? PaintingStyle.fill;
148 :
149 : Offset fadePoint1 =
150 0 : Offset(0, size.height - (animationProgress * size.height));
151 : Offset fadePoint2 =
152 0 : Offset(0 + (animationProgress * size.width), size.height);
153 :
154 0 : double spacing = this.spacing ?? 20.0;
155 0 : for (double x = 0; x < size.width + spacing; x += spacing) {
156 0 : for (double y = size.height; y > 0 - spacing; y -= spacing) {
157 0 : Offset currentPoint = Offset(x, y);
158 0 : double numerator = (currentPoint.dx - fadePoint1.dx) *
159 0 : (fadePoint2.dx - fadePoint1.dx) +
160 0 : (currentPoint.dy - fadePoint1.dy) * (fadePoint2.dy - fadePoint1.dy);
161 0 : num denominator = pow(fadePoint2.dx - fadePoint1.dx, 2) +
162 0 : pow(fadePoint2.dy - fadePoint1.dy, 2);
163 : double k = 0;
164 0 : if (denominator != 0) {
165 0 : k = numerator / denominator;
166 : }
167 :
168 0 : Offset p = Offset(fadePoint1.dx + k * (fadePoint2.dx - fadePoint1.dx),
169 0 : fadePoint1.dy + k * (fadePoint2.dy - fadePoint1.dy));
170 :
171 0 : double distance = (p - currentPoint).distance / size.shortestSide;
172 0 : double opacity = max(baseOpacity - distance, 0);
173 0 : paint.color = paint.color.withOpacity(opacity);
174 0 : canvas.drawCircle(Offset(x, y), radius ?? 5.0, paint);
175 : }
176 : }
177 : }
178 :
179 0 : @override
180 : bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
181 : }
|