11import 'package:app_ui/app_ui.dart' ;
22import 'package:flutter/material.dart' ;
33import 'package:flutter_bloc/flutter_bloc.dart' ;
4+ import 'package:magic_yeti/life_counter/bloc/life_change_bloc.dart' ;
45import 'package:magic_yeti/player/player.dart' ;
56import 'package:magic_yeti/player/view/customize_player_page.dart' ;
67import 'package:player_repository/player_repository.dart' ;
@@ -16,46 +17,38 @@ class LifeCounterWidget extends StatelessWidget {
1617
1718 @override
1819 Widget build (BuildContext context) {
19- return BlocBuilder <PlayerBloc , PlayerState >(
20- builder: (context, state) {
21- if (state.status == PlayerStatus .updated) {
22- final player = state.player;
23- textController.text = state.player.name;
24- return RotatedBox (
25- quarterTurns: rotate ? 2 : 0 ,
26- child: Stack (
27- children: [
28- BackgroundWidget (player: state.player),
29- LifePointsWidget (lifePoints: state.player.lifePoints),
30- Column (
31- children: [
32- IncrementLifeWidget (player: player),
33- DecrementLifeWidget (player: player),
34- ],
35- ),
36- _PlayerNameWidget (
37- name: textController.text,
38- onPressed: () async {
39- await Navigator .push (
40- context,
41- MaterialPageRoute <void >(
42- builder: (_) => BlocProvider .value (
43- value: context.read <PlayerBloc >(),
44- child: CustomizePlayerPage (
45- playerId: player.id,
46- ),
47- ),
48- ),
49- );
50- },
20+ final player = context.watch <PlayerBloc >().state.player;
21+ textController.text = player.name;
22+ return RotatedBox (
23+ quarterTurns: rotate ? 2 : 0 ,
24+ child: Stack (
25+ children: [
26+ BackgroundWidget (player: player),
27+ _LifeTrackerWidget (lifePoints: player.lifePoints),
28+ Column (
29+ children: [
30+ IncrementLifeWidget (player: player),
31+ DecrementLifeWidget (player: player),
32+ ],
33+ ),
34+ _PlayerNameWidget (
35+ name: textController.text,
36+ onPressed: () async {
37+ await Navigator .push (
38+ context,
39+ MaterialPageRoute <void >(
40+ builder: (_) => BlocProvider .value (
41+ value: context.read <PlayerBloc >(),
42+ child: CustomizePlayerPage (
43+ playerId: player.id,
44+ ),
45+ ),
5146 ),
52- ],
53- ),
54- );
55- }
56-
57- return const CircularProgressIndicator ();
58- },
47+ );
48+ },
49+ ),
50+ ],
51+ ),
5952 );
6053 }
6154}
@@ -124,21 +117,165 @@ class DecrementLifeWidget extends StatelessWidget {
124117 }
125118}
126119
127- class LifePointsWidget extends StatelessWidget {
128- const LifePointsWidget ({
120+ class _LifeTrackerWidget extends StatelessWidget {
121+ const _LifeTrackerWidget ({
129122 required this .lifePoints,
130- super .key,
131123 });
132124
133125 final int lifePoints;
134126
127+ @override
128+ Widget build (BuildContext context) {
129+ return BlocProvider (
130+ create: (context) => LifeChangeBloc (),
131+ child: BlocConsumer <PlayerBloc , PlayerState >(
132+ listener: (context, state) {
133+ if (state.status == PlayerStatus .lifeTotalUpdated) {
134+ context.read <LifeChangeBloc >().add (
135+ LifePointsChanged (
136+ previousLifePoints: lifePoints,
137+ newLifePoints: state.player.lifePoints,
138+ ),
139+ );
140+ }
141+ },
142+ builder: (context, state) => Stack (
143+ alignment: Alignment .center,
144+ fit: StackFit .expand,
145+ children: [
146+ _LifeText (player: state.player),
147+ const _LifeChangesWidget (),
148+ ],
149+ ),
150+ ),
151+ );
152+ }
153+ }
154+
155+ class _LifeText extends StatelessWidget {
156+ const _LifeText ({
157+ required this .player,
158+ });
159+
160+ final Player player;
161+
135162 @override
136163 Widget build (BuildContext context) {
137164 return Center (
138165 child: StrokeText (
139- text: '$lifePoints ' ,
166+ text: '${ player . lifePoints } ' ,
140167 fontSize: 96 ,
141- color: lifePoints <= 0 ? AppColors .black : AppColors .white,
168+ color: player.lifePoints <= 0 ? AppColors .black : AppColors .white,
169+ ),
170+ );
171+ }
172+ }
173+
174+ class _LifeChangesWidget extends StatelessWidget {
175+ const _LifeChangesWidget ();
176+
177+ @override
178+ Widget build (BuildContext context) {
179+ return BlocBuilder <LifeChangeBloc , LifeChangeState >(
180+ builder: (context, state) {
181+ final change = state.change;
182+ if (change == null || change == 0 ) return const SizedBox ();
183+
184+ return Center (
185+ child: Transform .translate (
186+ offset: Offset (
187+ change > 0 ? 75 : - 75 ,
188+ 0 ,
189+ ),
190+ child: _LifePointChangeAnimation (change: change),
191+ ),
192+ );
193+ },
194+ );
195+ }
196+ }
197+
198+ class _LifePointChangeAnimation extends StatefulWidget {
199+ const _LifePointChangeAnimation ({
200+ required this .change,
201+ });
202+
203+ final int change;
204+
205+ @override
206+ State <_LifePointChangeAnimation > createState () =>
207+ _LifePointChangeAnimationState ();
208+ }
209+
210+ class _LifePointChangeAnimationState extends State <_LifePointChangeAnimation >
211+ with TickerProviderStateMixin {
212+ late AnimationController _controller;
213+ late Animation <double > _opacityAnimation;
214+ int _lastChange = 0 ;
215+
216+ @override
217+ void initState () {
218+ super .initState ();
219+ _initializeAnimation ();
220+ }
221+
222+ @override
223+ void didUpdateWidget (_LifePointChangeAnimation oldWidget) {
224+ super .didUpdateWidget (oldWidget);
225+ if (widget.change != _lastChange) {
226+ // Reset the animation if the value changes
227+ _controller.dispose ();
228+ _initializeAnimation ();
229+ }
230+ }
231+
232+ void _initializeAnimation () {
233+ _controller = AnimationController (
234+ duration: const Duration (seconds: 3 ),
235+ vsync: this ,
236+ );
237+
238+ _opacityAnimation = Tween <double >(
239+ begin: 1 ,
240+ end: 0 ,
241+ ).animate (
242+ CurvedAnimation (
243+ parent: _controller,
244+ curve: const Interval (0.5 , 1 ),
245+ ),
246+ );
247+
248+ _lastChange = widget.change;
249+ _controller
250+ ..forward ()
251+ ..addStatusListener ((status) {
252+ if (status == AnimationStatus .completed) {
253+ context.read <LifeChangeBloc >().add (
254+ const LifePointChangeCompleted (),
255+ );
256+ }
257+ });
258+ }
259+
260+ @override
261+ void dispose () {
262+ _controller.dispose ();
263+ super .dispose ();
264+ }
265+
266+ @override
267+ Widget build (BuildContext context) {
268+ final prefix = widget.change > 0 ? '+' : '' ;
269+
270+ return FadeTransition (
271+ opacity: _opacityAnimation,
272+ child: Text (
273+ '$prefix ${widget .change }' ,
274+ style: TextStyle (
275+ fontSize: 32 ,
276+ fontWeight: FontWeight .bold,
277+ color: AppColors .white.withOpacity (0.3 ),
278+ ),
142279 ),
143280 );
144281 }
0 commit comments