Skip to content

Commit

Permalink
Merge pull request #9 from VGVentures/feat/animate-goal-progress-indi…
Browse files Browse the repository at this point in the history
…cator

feat: animate goal progress indicator
  • Loading branch information
B0berman authored Aug 14, 2024
2 parents beed289 + cdc5717 commit b7830eb
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 24 deletions.
85 changes: 62 additions & 23 deletions lib/ui/widgets/goal_progress_indicator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,41 @@ import 'package:financial_dashboard/ui/ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class GoalProgressIndicator extends StatelessWidget {
class GoalProgressIndicator extends StatefulWidget {
const GoalProgressIndicator({
super.key,
this.size = AppSpacing.xxxlg,
this.value = 0,
this.isGradient = false,
});

final double size;

/// Value as a percentage between 0.0 and 1.0.
final double value;
final bool isGradient;

@override
State<GoalProgressIndicator> createState() => _GoalProgressIndicatorState();
}

class _GoalProgressIndicatorState extends State<GoalProgressIndicator>
with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;

@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
}

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

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
Expand All @@ -35,28 +56,45 @@ class GoalProgressIndicator extends StatelessWidget {
if (monthlySpendingLimitGoal != 0) {
value = transactions.spent.abs() / monthlySpendingLimitGoal;
}
final displayValue = (value * 100).toInt();

var begin = 0.0;
if (_controller.isCompleted) {
begin = _animation.value;
_controller.reset();
}

_animation = Tween<double>(begin: begin, end: value).animate(
_controller,
);

_controller.forward();
return SizedBox(
height: size,
width: size,
child: CustomPaint(
painter: CircleProgressPainter(
colorScheme: coloScheme,
isGradient: isGradient,
value: value,
),
child: Center(
child: DefaultTextStyle(
style: textTheme.titleMedium!,
textHeightBehavior: const TextHeightBehavior(
applyHeightToFirstAscent: false,
height: widget.size,
width: widget.size,
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
final value = _animation.value;
final displayValue = (value * 100).toInt();
return CustomPaint(
painter: CircleProgressPainter(
colorScheme: coloScheme,
isGradient: widget.isGradient,
value: value,
),
child: Text(
'$displayValue%',
child: Center(
child: DefaultTextStyle(
style: textTheme.titleMedium!,
textHeightBehavior: const TextHeightBehavior(
applyHeightToFirstAscent: false,
),
child: Text(
'$displayValue%',
),
),
),
),
),
);
},
),
);
}
Expand Down Expand Up @@ -133,6 +171,7 @@ class CircleProgressPainter extends CustomPainter {
@override
bool shouldRepaint(covariant CircleProgressPainter oldDelegate) {
return colorScheme != oldDelegate.colorScheme ||
isGradient != oldDelegate.isGradient;
isGradient != oldDelegate.isGradient ||
value != oldDelegate.value;
}
}
57 changes: 56 additions & 1 deletion test/src/ui/widgets/goal_progress_indicator_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:bloc_test/bloc_test.dart';
import 'package:financial_dashboard/financial_data/financial_data.dart';
import 'package:financial_dashboard/ui/ui.dart';
Expand All @@ -19,6 +21,59 @@ class _MockFinancialDataBloc
implements FinancialDataBloc {}

void main() {
group('$GoalProgressIndicator', () {
late FinancialDataBloc financialDataBloc;

setUp(() {
financialDataBloc = _MockFinancialDataBloc();
});

testWidgets('animates from last value if present', (tester) async {
final streamController = StreamController<FinancialDataState>();
final beginState = FinancialDataState(
currentSavings: 12456,
savingsDataPoints: createSampleData(),
monthlySpendingLimitGoal: 100,
transactions: createSampleTransactions(),
);
final endState = FinancialDataState(
currentSavings: 12456,
savingsDataPoints: createSampleData(),
monthlySpendingLimitGoal: 1000,
transactions: createSampleTransactions(),
);
whenListen(
financialDataBloc,
streamController.stream,
initialState: beginState,
);

var value = beginState.transactions.spent.abs() /
beginState.monthlySpendingLimitGoal;
var displayValue = (value * 100).toInt();

await tester.pumpExperience(
GoalProgressIndicator(),
financialDataBloc: financialDataBloc,
);

await tester.pumpAndSettle();
expect(find.text('$displayValue%'), findsOneWidget);

streamController.add(endState);

await tester.pump();
expect(find.text('$displayValue%'), findsOneWidget);

await tester.pumpAndSettle();

value =
endState.transactions.spent.abs() / endState.monthlySpendingLimitGoal;
displayValue = (value * 100).toInt();
expect(find.text('$displayValue%'), findsOneWidget);
});
});

group('CircleProgressPainter', () {
late FinancialDataBloc financialDataBloc;

Expand All @@ -36,7 +91,7 @@ void main() {
group('$GoalProgressIndicator', () {
testWidgets('renders without gradient', (tester) async {
await tester.pumpExperience(
GoalProgressIndicator(value: 1),
GoalProgressIndicator(),
financialDataBloc: financialDataBloc,
);
await tester.pumpAndSettle();
Expand Down

0 comments on commit b7830eb

Please sign in to comment.