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

Commit

Permalink
feat: adding background circles animation (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
erickzanardo authored Nov 28, 2023
1 parent c9067f6 commit 57d02af
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 58 deletions.
2 changes: 2 additions & 0 deletions lib/home/view/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class HomeView extends StatelessWidget {
const Positioned(
top: 0,
bottom: 0,
left: 0,
right: 0,
child: Background(),
),
if (state.isWelcomeVisible) const WelcomeView(),
Expand Down
209 changes: 176 additions & 33 deletions lib/home/widgets/background.dart
Original file line number Diff line number Diff line change
@@ -1,47 +1,190 @@
import 'package:app_ui/app_ui.dart';
import 'package:dash_ai_search/home/home.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:phased/phased.dart';

class Background extends StatelessWidget {
const Background({super.key});
class Background extends StatefulWidget {
const Background({
super.key,
this.backgroundState,
});

final PhasedState<BackgroundPhase>? backgroundState;

@override
State<Background> createState() => _BackgroundState();
}

class _BackgroundState extends State<Background> {
late final _circlesState = widget.backgroundState ??
PhasedState<BackgroundPhase>(
values: BackgroundPhase.values,
initialValue: BackgroundPhase.initial,
autostart: false,
);

@override
void initState() {
super.initState();

WidgetsBinding.instance.addPostFrameCallback((_) {
_circlesState.value = BackgroundPhase.circlesIn;
});
}

@override
Widget build(BuildContext context) {
const leftOffset = -50.0;
const baseRadius = 303.0;
const baseMediumRadius = 255.0;
const baseSmallRadius = 185.0;
const horizontalOffset = baseRadius * 2;

return const SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
Circle(
offset: Offset(leftOffset, 0),
),
Circle(
offset: Offset(horizontalOffset + leftOffset, 0),
child: Circle(
offset: Offset(horizontalOffset + leftOffset, 0),
radius: baseMediumRadius,
borderColor: VertexColors.lilac,
child: Circle(
offset: Offset(horizontalOffset + leftOffset, 0),
radius: baseSmallRadius,
borderColor: VertexColors.lilac,
dotted: true,
),
return BlocListener<HomeBloc, HomeState>(
listener: (BuildContext context, state) {
if (state.status == Status.welcomeToAskQuestion) {
_circlesState.value = BackgroundPhase.circlesOut;
}
},
child: LayoutBuilder(
builder: (context, constraints) {
final baseRadius = constraints.maxWidth / 7;
final offset = (baseRadius * 8) - constraints.maxWidth;
final baseMediumRadius = baseRadius * .84;
final baseSmallRadius = baseRadius * .61;

return _Circles(
state: _circlesState,
baseRadius: baseRadius,
offset: offset,
baseMediumRadius: baseMediumRadius,
baseSmallRadius: baseSmallRadius,
baseContainerHeight: constraints.maxHeight,
);
},
),
);
}
}

@visibleForTesting
enum BackgroundPhase {
initial,
circlesIn,
circlesOut,
}

class _Circles extends Phased<BackgroundPhase> {
const _Circles({
required super.state,
required this.baseRadius,
required this.offset,
required this.baseMediumRadius,
required this.baseSmallRadius,
required this.baseContainerHeight,
});

final double baseRadius;
final double offset;
final double baseMediumRadius;
final double baseSmallRadius;
final double baseContainerHeight;

@override
Widget build(BuildContext context) {
final size = baseRadius * 2;
const duration = Duration(seconds: 1);
const scaleDuration = Duration(milliseconds: 500);
const curve = Curves.decelerate;
return Stack(
children: [
AnimatedPositioned(
curve: curve,
left: state.phaseValue(
values: {
BackgroundPhase.circlesOut: -offset * 2,
},
defaultValue: -offset,
),
duration: duration,
top: state.phaseValue(
values: {
BackgroundPhase.initial: baseContainerHeight,
},
defaultValue: baseContainerHeight / 2 - baseRadius,
),
child: Circle(radius: baseRadius),
),
AnimatedPositioned(
curve: curve,
duration: duration,
left: state.phaseValue(
values: {
BackgroundPhase.circlesOut: -(size * 2 - offset),
},
defaultValue: size - offset,
),
top: state.phaseValue(
values: {
BackgroundPhase.initial: -size,
},
defaultValue: baseContainerHeight / 2 - baseRadius,
),
child: SizedBox.square(
dimension: baseRadius * 2,
child: Stack(
alignment: Alignment.center,
children: [
Circle(
radius: baseRadius,
),
Circle(
radius: baseMediumRadius,
borderColor: VertexColors.lilac,
),
Circle(
radius: baseSmallRadius,
borderColor: VertexColors.lilac,
dotted: true,
),
],
),
),
Circle(
offset: Offset(horizontalOffset * 2 + leftOffset, 0),
),
AnimatedPositioned(
curve: curve,
duration: duration,
left: size * 2 - offset,
top: state.phaseValue(
values: {
BackgroundPhase.initial: baseContainerHeight,
},
defaultValue: baseContainerHeight / 2 - baseRadius,
),
Circle(
offset: Offset(horizontalOffset * 3 + leftOffset, 0),
child: AnimatedScale(
duration: scaleDuration,
scale: state.phaseValue(
values: {
BackgroundPhase.circlesOut: 4,
},
defaultValue: 1,
),
child: Circle(radius: baseRadius),
),
],
),
),
AnimatedPositioned(
curve: curve,
duration: duration,
left: state.phaseValue(
values: {
BackgroundPhase.circlesOut: size * 4,
},
defaultValue: size * 3 - offset,
),
top: state.phaseValue(
values: {
BackgroundPhase.initial: -size,
},
defaultValue: baseContainerHeight / 2 - baseRadius,
),
child: Circle(radius: baseRadius),
),
],
);
}
}
44 changes: 23 additions & 21 deletions lib/home/widgets/circle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:path_drawing/path_drawing.dart';

class Circle extends StatelessWidget {
const Circle({
this.offset = Offset.zero,
this.radius = 303,
this.borderColor = VertexColors.white,
this.dotted = false,
Expand All @@ -13,7 +12,6 @@ class Circle extends StatelessWidget {
super.key,
});

final Offset offset;
final double radius;
final Color borderColor;
final bool dotted;
Expand All @@ -22,23 +20,24 @@ class Circle extends StatelessWidget {

@override
Widget build(BuildContext context) {
return CustomPaint(
painter: CirclePainter(
offset: offset,
radius: radius,
borderColor: borderColor,
dotted: dotted,
backgroundColor: backgroundColor,
return SizedBox.square(
dimension: radius * 2,
child: CustomPaint(
painter: CirclePainter(
radius: radius,
borderColor: borderColor,
dotted: dotted,
backgroundColor: backgroundColor,
),
child: child,
),
child: child,
);
}
}

class CirclePainter extends CustomPainter {
@visibleForTesting
CirclePainter({
required this.offset,
required this.radius,
required this.borderColor,
required this.dotted,
Expand All @@ -54,7 +53,6 @@ class CirclePainter extends CustomPainter {
..style = PaintingStyle.stroke;
}

final Offset offset;
final double radius;
final Color borderColor;
final bool dotted;
Expand All @@ -65,11 +63,14 @@ class CirclePainter extends CustomPainter {

@override
void paint(Canvas canvas, Size size) {
canvas.drawCircle(
offset,
radius,
_paintCircle,
);
canvas
..save()
..translate(radius, radius)
..drawCircle(
Offset.zero,
radius,
_paintCircle,
);

if (dotted) {
const dashPattern = <double>[4, 4];
Expand All @@ -79,23 +80,24 @@ class CirclePainter extends CustomPainter {
..addRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(
offset.dx - s / 2,
offset.dy / 2 - s / 2,
-radius,
-radius,
s,
s,
),
Radius.circular(s / 2),
Radius.circular(radius),
),
);
path = dashPath(path, dashArray: CircularIntervalList(dashPattern));
canvas.drawPath(path, _paintBorder);
} else {
canvas.drawCircle(
offset,
Offset.zero,
radius,
_paintBorder,
);
}
canvas.restore();
}

@override
Expand Down
8 changes: 8 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.1"
phased:
dependency: "direct main"
description:
name: phased
sha256: "4dc19d589fd059268b07357767ac96eef2861eb5e8ceedf9d23fb933f0128396"
url: "https://pub.dev"
source: hosted
version: "0.0.3"
pool:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies:
sdk: flutter
intl: ^0.18.0
path_drawing: ^1.0.1
phased: ^0.0.3
questions_repository:
path: packages/questions_repository

Expand Down
Loading

0 comments on commit 57d02af

Please sign in to comment.