Skip to content
This repository has been archived by the owner on Jan 9, 2024. It is now read-only.

Commit

Permalink
final details
Browse files Browse the repository at this point in the history
  • Loading branch information
erickzanardo committed Dec 5, 2023
1 parent fbc686f commit 51989b9
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 42 deletions.
110 changes: 68 additions & 42 deletions lib/home/widgets/emoji_bubbles.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,35 @@ class Bubble {

class EmojiBubbles extends StatefulWidget {
const EmojiBubbles({super.key});
static const cellebrateImages = [
'assets/rating-assets/confetti.png',
'assets/rating-assets/heart.png',
'assets/rating-assets/star.png',
'assets/rating-assets/thumbs-up.png',
];
static const beDepressedImages = [
'assets/rating-assets/rain.png',
'assets/rating-assets/sad.png',
'assets/rating-assets/thumbs-down.png',
];

@override
State<EmojiBubbles> createState() => _EmojiBubblesState();
State<EmojiBubbles> createState() => EmojiBubblesState();
}

class _EmojiBubblesState extends State<EmojiBubbles>
@visibleForTesting
class EmojiBubblesState extends State<EmojiBubbles>
with SingleTickerProviderStateMixin {
late final _controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: (1000 / 60).round()),
)..addListener(_runLoop);
late final AnimationController _controller;

@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: (1000 / 60).round()),
)..addListener(_runLoop);
}

DateTime? _lastUpdate;
late final List<Bubble> _bubbles = [];
Expand All @@ -49,19 +67,8 @@ class _EmojiBubblesState extends State<EmojiBubbles>
? VertexColors.deepArctic
: VertexColors.white;

const cellebrateImages = [
'assets/rating-assets/confetti.png',
'assets/rating-assets/heart.png',
'assets/rating-assets/star.png',
'assets/rating-assets/thumbs-up.png',
];
const beDepressedImages = [
'assets/rating-assets/rain.png',
'assets/rating-assets/sad.png',
'assets/rating-assets/thumbs-down.png',
];

final emojis = happy ? cellebrateImages : beDepressedImages;
final emojis =
happy ? EmojiBubbles.cellebrateImages : EmojiBubbles.beDepressedImages;

const numberOfBubbles = 15;

Expand Down Expand Up @@ -99,9 +106,11 @@ class _EmojiBubblesState extends State<EmojiBubbles>

@override
void dispose() {
super.dispose();
_controller
..stop()
..dispose();

_controller.dispose();
super.dispose();
}

void _initAnimation(bool happy) {
Expand All @@ -111,6 +120,7 @@ class _EmojiBubblesState extends State<EmojiBubbles>

void _stopAnimation() {
_controller.stop();
setState(() {});
}

void _runLoop() {
Expand All @@ -120,8 +130,10 @@ class _EmojiBubblesState extends State<EmojiBubbles>
: now.difference(_lastUpdate!).inMilliseconds / 1000;
_lastUpdate = now;

_update(dt);
setState(() {});
update(dt);
if (mounted) {
setState(() {});
}
}

double _interpolatePosition(Bubble buble, double dt) {
Expand All @@ -134,7 +146,7 @@ class _EmojiBubblesState extends State<EmojiBubbles>
return buble.position.y - speed * dt;
}

void _update(double dt) {
void update(double dt) {
for (final bubble in _bubbles) {
bubble.position.y = _interpolatePosition(bubble, dt);

Expand Down Expand Up @@ -166,28 +178,42 @@ class _EmojiBubblesState extends State<EmojiBubbles>
Positioned(
left: bubble.position.x,
top: bubble.position.y,
child: DecoratedBox(
decoration: BoxDecoration(
color: bubble.color,
shape: BoxShape.circle,
),
child: SizedBox(
width: bubble.size,
height: bubble.size,
child: Center(
child: Padding(
padding: EdgeInsets.all(bubble.size / 4),
child: Image.asset(
bubble.emoji,
),
),
),
),
),
child: EmojiBubble(bubble: bubble),
),
],
),
),
);
}
}

class EmojiBubble extends StatelessWidget {
const EmojiBubble({
required this.bubble,
super.key,
});

final Bubble bubble;

@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: bubble.color,
shape: BoxShape.circle,
),
child: SizedBox(
width: bubble.size,
height: bubble.size,
child: Center(
child: Padding(
padding: EdgeInsets.all(bubble.size / 4),
child: Image.asset(
bubble.emoji,
),
),
),
),
);
}
}
142 changes: 142 additions & 0 deletions test/home/widgets/emoji_bubbles_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import 'dart:async';

import 'package:bloc_test/bloc_test.dart';
import 'package:dash_ai_search/home/home.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';

import '../../helpers/helpers.dart';

class _MockHomeBloc extends MockBloc<HomeEvent, HomeState>
implements HomeBloc {}

void main() {
group('EmojiBubbles', () {
late HomeBloc homeBloc;

setUp(() {
homeBloc = _MockHomeBloc();

whenListen(
homeBloc,
Stream.fromIterable([const HomeState()]),
initialState: const HomeState(),
);
});

Widget bootstrap() => BlocProvider.value(
value: homeBloc,
child: const MaterialApp(
home: Scaffold(
body: EmojiBubbles(),
),
),
);

testWidgets('renders', (tester) async {
await tester.pumpApp(bootstrap());

expect(find.byType(EmojiBubbles), findsOneWidget);
});

testWidgets(
'when receiving a positive feedback, spawn emojis with '
'cellebration images',
(tester) async {
final controller = StreamController<HomeState>();

whenListen(
homeBloc,
controller.stream,
initialState: const HomeState(),
);

await tester.pumpApp(bootstrap());

controller.add(const HomeState(answerFeedbacks: [AnswerFeedback.good]));

await tester.pump();

expect(find.byType(EmojiBubble), findsWidgets);

final widgets = tester.widgetList(find.byType(EmojiBubble)).toList();

for (final widget in widgets) {
expect(widget, isA<EmojiBubble>());
expect(
EmojiBubbles.cellebrateImages,
contains((widget as EmojiBubble).bubble.emoji),
);
}

for (var i = 0; i < 10; i++) {
await tester.pump();
}
},
);

testWidgets(
'when receiving a negative feedback, spawn emojis with '
'depressing images',
(tester) async {
final controller = StreamController<HomeState>();

whenListen(
homeBloc,
controller.stream,
initialState: const HomeState(),
);

await tester.pumpApp(bootstrap());

controller.add(const HomeState(answerFeedbacks: [AnswerFeedback.bad]));

await tester.pump();

expect(find.byType(EmojiBubble), findsWidgets);

final widgets = tester.widgetList(find.byType(EmojiBubble)).toList();

for (final widget in widgets) {
expect(widget, isA<EmojiBubble>());
expect(
EmojiBubbles.beDepressedImages,
contains((widget as EmojiBubble).bubble.emoji),
);
}

for (var i = 0; i < 10; i++) {
await tester.pump();
}
},
);

testWidgets(
'there are no bubbles anymore when the anymation is over',
(tester) async {
final controller = StreamController<HomeState>();

whenListen(
homeBloc,
controller.stream,
initialState: const HomeState(),
);

await tester.pumpApp(bootstrap());

controller.add(const HomeState(answerFeedbacks: [AnswerFeedback.good]));

final state =
tester.state<EmojiBubblesState>(find.byType(EmojiBubbles));

for (var i = 0; i < 100; i++) {
state.update(1);
await tester.pump();
}

expect(find.byType(EmojiBubble), findsNothing);
},
);
});
}

0 comments on commit 51989b9

Please sign in to comment.