Skip to content

Commit

Permalink
Refactoring question animations
Browse files Browse the repository at this point in the history
  • Loading branch information
artberri committed Oct 20, 2018
1 parent 389f28c commit d7e1967
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 49 deletions.
75 changes: 27 additions & 48 deletions lib/screens/game/widgets/question/question.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

import 'package:flutter/material.dart';

import '../../../../services/index.dart';
import '../../../../domain/index.dart';
import '../../../../view/index.dart';
import '../../../../i18n/index.dart';
import 'question_presenter.dart';

typedef void DismissAnimationCallback();

Expand Down Expand Up @@ -41,58 +43,35 @@ class QuestionWidget extends StatefulWidget {
_QuestionWidgetState createState() => _QuestionWidgetState();
}

class _QuestionWidgetState extends State<QuestionWidget> with TickerProviderStateMixin {
final Tween colorTween = ColorTween(begin: ScreenColors.black, end: ScreenColors.darkRed);
AnimationController _colorAnimationController;
AnimationController _numberAnimationController;
Animation _colorAnimation;
Animation _numberAnimation;
class _QuestionWidgetState extends State<QuestionWidget> with TickerProviderStateMixin implements QuestionViewContract {
QuestionAnimator _colorAnimator;
QuestionAnimator _numberAnimator;
QuestionPresenter _presenter;

AnimationController _createAnimationController() {
return AnimationController(
bool get isNumberOk => widget.isNumberOk.value;
bool get isColorOk => widget.isColorOk.value;

QuestionAnimator _createAnimator(DismissAnimationCallback dismissAnimationCallbak) {
return Injector.of(context).animatorFactory.createQuestionAnimator(
vsync: this,
duration: Duration(milliseconds: 400)
milliseconds: 400,
onDismissed: dismissAnimationCallbak,
listener: _setState,
);
}

Animation _createAnimation(AnimationController controller, DismissAnimationCallback dismissAnimationCallbak) {
return colorTween.animate(controller)..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.stop();
dismissAnimationCallbak();
}
})..addListener(() {
setState((){});
});
}

void _blinkColorWhenError() {
if (!widget.isColorOk.value) {
_colorAnimationController.forward();
}
}

void _blinkNumberWhenError() {
if (!widget.isNumberOk.value) {
_numberAnimationController.forward();
}
}

void _setState() {
setState(() => null);
}

@override
void initState() {
super.initState();
_colorAnimationController = _createAnimationController();
_numberAnimationController = _createAnimationController();
_colorAnimation = _createAnimation(_colorAnimationController, () => widget.isColorOk.value = true);
_numberAnimation = _createAnimation(_numberAnimationController, () => widget.isNumberOk.value = true);
widget.isColorOk.addListener(_blinkColorWhenError);
widget.isNumberOk.addListener(_blinkNumberWhenError);
void didChangeDependencies() {
super.didChangeDependencies();
_colorAnimator = _createAnimator(() => widget.isColorOk.value = true);
_numberAnimator = _createAnimator(() => widget.isNumberOk.value = true);
_presenter = QuestionPresenter(this, _colorAnimator, _numberAnimator);
widget.isColorOk.addListener(_presenter.onIsColorOkValueChanged);
widget.isNumberOk.addListener(_presenter.onIsNumberOkValueChanged);
widget.question.addListener(_setState);
}

Expand All @@ -108,7 +87,7 @@ class _QuestionWidgetState extends State<QuestionWidget> with TickerProviderStat
style: TextStyle(
fontFamily: Fonts.poiretone,
fontSize: 36.0,
color: _numberAnimation.value,
color: _numberAnimator.animation.value,
),
),
Text(' '),
Expand All @@ -117,7 +96,7 @@ class _QuestionWidgetState extends State<QuestionWidget> with TickerProviderStat
style: TextStyle(
fontFamily: Fonts.poiretone,
fontSize: 36.0,
color: _colorAnimation.value,
color: _colorAnimator.animation.value,
),
),
],
Expand All @@ -128,10 +107,10 @@ class _QuestionWidgetState extends State<QuestionWidget> with TickerProviderStat
@override
void dispose() {
widget.question.removeListener(_setState);
widget.isColorOk.removeListener(_blinkColorWhenError);
widget.isNumberOk.removeListener(_blinkNumberWhenError);
_colorAnimationController.dispose();
_numberAnimationController.dispose();
widget.isColorOk.removeListener(_presenter.onIsColorOkValueChanged);
widget.isNumberOk.removeListener(_presenter.onIsNumberOkValueChanged);
_colorAnimator.dispose();
_numberAnimator.dispose();
super.dispose();
}
}
29 changes: 29 additions & 0 deletions lib/screens/game/widgets/question/question_presenter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (C) 2018 Alberto Varela Sánchez <alberto@berriart.com>
// Use of this source code is governed by the version 3 of the
// GNU General Public License that can be found in the LICENSE file.

import '../../../../view/index.dart';

abstract class QuestionViewContract {
bool get isColorOk;
bool get isNumberOk;
}

class QuestionPresenter {
QuestionPresenter(this._view, this._colorAnimator, this._numberAnimator);

final QuestionAnimator _colorAnimator;
final QuestionAnimator _numberAnimator;
final QuestionViewContract _view;

void onIsColorOkValueChanged() {
if (!_view.isColorOk) {
_colorAnimator.forward();
}
}
void onIsNumberOkValueChanged() {
if (!_view.isNumberOk) {
_numberAnimator.forward();
}
}
}
16 changes: 15 additions & 1 deletion lib/services/animator_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,26 @@ class AnimatorFactory {
GameTimerAnimator createGameAnimator({
@required TickerProviderStateMixin vsync,
@required int milliseconds,
@required Function onCompleted
@required Function onCompleted,
}) {
return GameTimerAnimator(
vsync: vsync,
milliseconds: milliseconds,
onCompleted: onCompleted,
);
}

QuestionAnimator createQuestionAnimator({
@required TickerProviderStateMixin vsync,
@required int milliseconds,
@required Function onDismissed,
@required Function listener,
}) {
return QuestionAnimator(
vsync: vsync,
milliseconds: milliseconds,
onDismissed: onDismissed,
listener: listener,
);
}
}
1 change: 1 addition & 0 deletions lib/view/animators/index.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
export 'animator.dart';
export 'countdown_animator.dart';
export 'game_timer_animator.dart';
export 'question_animator.dart';
36 changes: 36 additions & 0 deletions lib/view/animators/question_animator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (C) 2018 Alberto Varela Sánchez <alberto@berriart.com>
// Use of this source code is governed by the version 3 of the
// GNU General Public License that can be found in the LICENSE file.

import 'package:flutter/material.dart';

import '../styles.dart';
import 'animator.dart';

class QuestionAnimator extends Animator {
QuestionAnimator({
@required TickerProviderStateMixin vsync,
@required int milliseconds,
@required Function onDismissed,
@required Function listener,
}) : super(
vsync: vsync,
milliseconds: milliseconds,
listener: listener,
) {
_animation = ColorTween(begin: ScreenColors.black, end: ScreenColors.darkRed)
.animate(controller);

controller.addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.stop();
onDismissed?.call();
}
});
}

Animation _animation;
Animation get animation => _animation;
}
71 changes: 71 additions & 0 deletions test/screens/game/widgets/question/question_presenter_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (C) 2018 Alberto Varela Sánchez <alberto@berriart.com>
// Use of this source code is governed by the version 3 of the
// GNU General Public License that can be found in the LICENSE file.

import 'package:test/test.dart';
import 'package:mockito/mockito.dart';

import '../../../../../lib/screens/game/widgets/question/question_presenter.dart';
import '../../../../../lib/view/index.dart';

class MockQuestionView extends Mock implements QuestionViewContract {}
class MockQuestionAnimator extends Mock implements QuestionAnimator {}

void main() {
MockQuestionView _mockQuestionView;
MockQuestionAnimator _mockNumberQuestionAnimator;
MockQuestionAnimator _mockColorQuestionAnimator;
QuestionPresenter _questionPresenter;

group('Question Widget:', () {
setUp(() async {
_mockQuestionView = MockQuestionView();
_mockNumberQuestionAnimator = MockQuestionAnimator();
_mockColorQuestionAnimator = MockQuestionAnimator();
_questionPresenter = QuestionPresenter(
_mockQuestionView,
_mockColorQuestionAnimator,
_mockNumberQuestionAnimator);
});

group('On "IsColorOk" value changed', () {
test('if it is not ok it highlights the color of the question', () {
when(_mockQuestionView.isColorOk)
.thenReturn(false);

_questionPresenter.onIsColorOkValueChanged();

verify(_mockColorQuestionAnimator.forward());
});

test('if it is ok it does not highlight the color of the question', () {
when(_mockQuestionView.isColorOk)
.thenReturn(true);

_questionPresenter.onIsColorOkValueChanged();

verifyNever(_mockColorQuestionAnimator.forward());
});
});

group('On "IsNumberOk" value changed', () {
test('if it is not ok it highlights the number of the question', () {
when(_mockQuestionView.isNumberOk)
.thenReturn(false);

_questionPresenter.onIsNumberOkValueChanged();

verify(_mockNumberQuestionAnimator.forward());
});

test('if it is ok it does not highlight the number of the question', () {
when(_mockQuestionView.isNumberOk)
.thenReturn(true);

_questionPresenter.onIsNumberOkValueChanged();

verifyNever(_mockNumberQuestionAnimator.forward());
});
});
});
}

0 comments on commit d7e1967

Please sign in to comment.