Skip to content

Commit c781624

Browse files
committed
added damage fade widget
1 parent 9686477 commit c781624

9 files changed

Lines changed: 281 additions & 48 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'package:bloc/bloc.dart';
2+
import 'package:equatable/equatable.dart';
3+
4+
part 'life_change_event.dart';
5+
part 'life_change_state.dart';
6+
7+
class LifeChangeBloc extends Bloc<LifeChangeEvent, LifeChangeState> {
8+
LifeChangeBloc() : super(const LifeChangeState()) {
9+
on<LifePointsChanged>(_onLifePointsChanged);
10+
on<LifePointChangeCompleted>(_onLifePointChangeCompleted);
11+
}
12+
13+
void _onLifePointsChanged(
14+
LifePointsChanged event,
15+
Emitter<LifeChangeState> emit,
16+
) {
17+
int? previousLifePoints;
18+
19+
// If this is the first time or life points have changed
20+
if (event.newLifePoints != previousLifePoints) {
21+
final newChange = event.newLifePoints - (event.previousLifePoints);
22+
23+
// Accumulate or set the new change
24+
final totalChange =
25+
state.change != null ? state.change! + newChange : newChange;
26+
27+
emit(state.copyWith(change: totalChange));
28+
}
29+
}
30+
31+
void _onLifePointChangeCompleted(
32+
LifePointChangeCompleted event,
33+
Emitter<LifeChangeState> emit,
34+
) {
35+
// Reset the change when animation is complete
36+
emit(const LifeChangeState());
37+
}
38+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
part of 'life_change_bloc.dart';
2+
3+
abstract class LifeChangeEvent extends Equatable {
4+
const LifeChangeEvent();
5+
6+
@override
7+
List<Object?> get props => [];
8+
}
9+
10+
class LifePointsChanged extends LifeChangeEvent {
11+
const LifePointsChanged({
12+
required this.previousLifePoints,
13+
required this.newLifePoints,
14+
});
15+
16+
final int previousLifePoints;
17+
final int newLifePoints;
18+
19+
@override
20+
List<Object> get props => [previousLifePoints, newLifePoints];
21+
}
22+
23+
class LifePointChangeCompleted extends LifeChangeEvent {
24+
const LifePointChangeCompleted();
25+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
part of 'life_change_bloc.dart';
2+
3+
class LifeChangeState extends Equatable {
4+
const LifeChangeState({this.change});
5+
6+
final int? change;
7+
8+
LifeChangeState copyWith({
9+
int? change,
10+
}) {
11+
return LifeChangeState(
12+
change: change ?? this.change,
13+
);
14+
}
15+
16+
@override
17+
List<Object?> get props => [change];
18+
}

lib/life_counter/widgets/life_counter_widget.dart

Lines changed: 181 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:app_ui/app_ui.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_bloc/flutter_bloc.dart';
4+
import 'package:magic_yeti/life_counter/bloc/life_change_bloc.dart';
45
import 'package:magic_yeti/player/player.dart';
56
import 'package:magic_yeti/player/view/customize_player_page.dart';
67
import '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
}

lib/player/bloc/player_bloc.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class PlayerBloc extends Bloc<PlayerEvent, PlayerState> {
3939
_playerId = playerId,
4040
super(
4141
PlayerState(
42-
status: PlayerStatus.updated,
4342
player: playerRepository.getPlayerById(playerId),
4443
),
4544
) {
@@ -128,7 +127,7 @@ class PlayerBloc extends Bloc<PlayerEvent, PlayerState> {
128127
_playerRepository.updatePlayer(updatedPlayer);
129128
emit(
130129
state.copyWith(
131-
status: PlayerStatus.updated,
130+
status: PlayerStatus.lifeTotalUpdated,
132131
player: updatedPlayer,
133132
lifePoints: newLifePoints,
134133
),
@@ -163,7 +162,7 @@ class PlayerBloc extends Bloc<PlayerEvent, PlayerState> {
163162

164163
emit(
165164
state.copyWith(
166-
status: PlayerStatus.updated,
165+
status: PlayerStatus.lifeTotalUpdated,
167166
player: updatedPlayer,
168167
lifePoints: newLifePoints,
169168
),

lib/player/bloc/player_state.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ enum PlayerStatus {
44
initial,
55
updating,
66
updated,
7+
lifeTotalUpdated,
78
}
89

910
class PlayerState extends Equatable {
1011
const PlayerState({
11-
required this.status,
1212
required this.player,
13+
this.status = PlayerStatus.initial,
1314
this.lifePoints,
1415
});
1516

packages/app_ui/lib/src/colors/app_colors.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ abstract class AppColors {
2929
/// neutral60
3030
static const Color neutral60 = Color(0xFF919094);
3131

32+
/// green
33+
static const Color green = Color(0xFF048000);
34+
35+
/// red
36+
static const Color red = Color(0xFFD00000);
37+
3238
/// On Surface Variant
3339
static const Color onSurfaceVariant = Color(0xFF43474E);
3440
}

0 commit comments

Comments
 (0)