Skip to content

Commit a7c7f18

Browse files
author
Vivek Chib
committed
added animated theme toggle button
1 parent 1345645 commit a7c7f18

File tree

8 files changed

+180
-86
lines changed

8 files changed

+180
-86
lines changed

lib/utils/theme/theme.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,10 +341,7 @@ class MaterialTheme {
341341
useMaterial3: true,
342342
brightness: colorScheme.brightness,
343343
colorScheme: colorScheme,
344-
snackBarTheme: SnackBarThemeData(
345-
behavior: SnackBarBehavior.floating,
346-
showCloseIcon: true,
347-
),
344+
snackBarTheme: SnackBarThemeData(showCloseIcon: true),
348345
textTheme: textTheme.apply(
349346
fontFamily: "Outfit",
350347
bodyColor: colorScheme.onSurface,

lib/views/home_page.dart

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import 'package:provider/provider.dart';
1010
import 'widgets/animated_box/animated_boxes.dart';
1111
import 'widgets/animated_box/provider.dart';
1212
import 'widgets/code_block.dart';
13-
import 'widgets/cubic_curve_input_widget.dart';
1413

1514
class HomePage extends StatefulWidget {
1615
const HomePage({super.key});
@@ -27,34 +26,21 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
2726
late String selectedCategory;
2827
late CurveModel selectedCurve;
2928

30-
late List<TextEditingController> customCubicControllers;
31-
3229
late int animationTime;
3330

3431
@override
3532
void initState() {
3633
super.initState();
3734

38-
customCubicControllers = List.generate(4, (index) {
39-
final value = switch (index) {
40-
0 => "0.5",
41-
1 => "0",
42-
2 => "0.75",
43-
3 => "1",
44-
int() => throw UnimplementedError(),
45-
};
46-
47-
return TextEditingController(text: value);
48-
});
49-
5035
selectedCategory = CurveModel.list.keys.first;
5136
selectedCurve = CurveModel.list.values.first.first;
5237

5338
animationTime = 2;
5439

5540
playPauseController = AnimationController(
5641
vsync: this,
57-
duration: Duration(milliseconds: 250),
42+
duration: 100.ms,
43+
reverseDuration: 100.ms,
5844
)..forward();
5945

6046
controller = AnimationController(
@@ -70,10 +56,6 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
7056

7157
@override
7258
void dispose() {
73-
for (final controller in customCubicControllers) {
74-
controller.dispose();
75-
}
76-
7759
playPauseController.dispose();
7860
controller.dispose();
7961
super.dispose();
@@ -207,11 +189,11 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
207189
Flexible(
208190
flex: 2,
209191
child: DropdownMenuWidget<CurveModel>(
210-
title: "Type",
192+
title: "Curve",
211193
value: selectedCurve,
212194
items: CurveModel.list[selectedCategory]!.toList(),
213195
onChanged: (value) => updateCurve(value!),
214-
childBuilder: (context, value, textStyle) {
196+
builder: (context, value, textStyle) {
215197
return Text(value.name.toString(), style: textStyle);
216198
},
217199
),
@@ -224,24 +206,6 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
224206
animationTime: animationTime,
225207
onChanged: updateAnimationTime,
226208
),
227-
228-
// TODO : TextField have some issues with current flutter version, waiting it to be fixed
229-
Visibility(
230-
visible: false,
231-
child: AnimatedSize(
232-
curve: Curves.fastOutSlowIn,
233-
duration: 200.ms,
234-
reverseDuration: 200.ms,
235-
child: (selectedCurve.isCustom)
236-
? CubicCurveInputWidget(
237-
controllers: customCubicControllers,
238-
onApply: (curve) {
239-
updateCurve(selectedCurve.copyWith(curve: curve));
240-
},
241-
)
242-
: const SizedBox.shrink(),
243-
),
244-
)
245209
],
246210
),
247211
);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import 'dart:math';
2+
import 'package:flutter/material.dart';
3+
4+
class ThemeSwitchPainter extends CustomPainter {
5+
final IconThemeData iconThemeData;
6+
final Animation<double> animation;
7+
8+
ThemeSwitchPainter({
9+
required this.iconThemeData,
10+
required this.animation,
11+
}) : super(repaint: animation);
12+
13+
Color get iconColor => iconThemeData.color ?? Colors.white;
14+
15+
Paint get canvasPaint => Paint()
16+
..color = iconColor
17+
..style = PaintingStyle.fill;
18+
19+
@override
20+
void paint(Canvas canvas, Size size) {
21+
final boxSize = size.width * .70;
22+
final radius = size.width * 0.30;
23+
final center = Offset(size.width / 2, size.height / 2);
24+
25+
// Circular mask for center
26+
final circlePath = Path()
27+
..addOval(Rect.fromCircle(center: center, radius: radius));
28+
29+
// save canvas
30+
canvas.save();
31+
32+
// move canvas to center
33+
canvas.translate(center.dx, center.dy);
34+
35+
// rotate canvas with animation value
36+
canvas.rotate(1 - animation.value * pi);
37+
38+
// draw a box with an additional path to clip center circle
39+
canvas.drawPath(
40+
Path()
41+
..addRect(Rect.fromLTWH(-boxSize / 2, -boxSize / 2, boxSize, boxSize))
42+
..addPath(circlePath.shift(-center), Offset.zero)
43+
..fillType = PathFillType.evenOdd,
44+
canvasPaint,
45+
);
46+
47+
// rotate canvas to rotate the next box 45 degrees
48+
canvas.rotate(pi / 4);
49+
50+
// draw a box with an additional path to clip center circle
51+
canvas.drawPath(
52+
Path()
53+
..addRect(Rect.fromLTWH(-boxSize / 2, -boxSize / 2, boxSize, boxSize))
54+
..addPath(circlePath.shift(-center), Offset.zero)
55+
..fillType = PathFillType.evenOdd,
56+
canvasPaint,
57+
);
58+
59+
// restore canvas
60+
canvas.restore();
61+
62+
// now draw a circle at the center
63+
// and translate it with animation to form a moon shape from center to top-right
64+
final moonRadius = radius - 2;
65+
66+
final translateX = size.width / 2 + (animation.value * size.width * .10);
67+
final translateY = size.height / 2 - (animation.value * size.height * .10);
68+
69+
canvas.drawCircle(Offset(translateX, translateY), moonRadius, canvasPaint);
70+
}
71+
72+
@override
73+
bool shouldRepaint(covariant CustomPainter oldDelegate) {
74+
return false;
75+
}
76+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_curve_visualizer/utils/theme/theme_provider.dart';
3+
import 'package:flutter_curve_visualizer/views/widgets/animated_theme_switch/painter.dart';
4+
import 'package:provider/provider.dart';
5+
6+
class AnimatedThemeSwitcher extends StatefulWidget {
7+
const AnimatedThemeSwitcher({super.key});
8+
9+
@override
10+
State<AnimatedThemeSwitcher> createState() => _AnimatedThemeSwitcherState();
11+
}
12+
13+
class _AnimatedThemeSwitcherState extends State<AnimatedThemeSwitcher>
14+
with TickerProviderStateMixin {
15+
late AnimationController controller;
16+
17+
late Animation<double> animation;
18+
19+
@override
20+
void initState() {
21+
super.initState();
22+
controller = AnimationController(
23+
vsync: this,
24+
duration: const Duration(milliseconds: 500),
25+
);
26+
27+
animation = CurvedAnimation(
28+
parent: controller,
29+
curve: Curves.easeInOutCubic,
30+
);
31+
}
32+
33+
@override
34+
void dispose() {
35+
controller.dispose();
36+
super.dispose();
37+
}
38+
39+
@override
40+
void didChangeDependencies() {
41+
super.didChangeDependencies();
42+
final isDark =
43+
context.read<ThemeProvider>().getThemeMode() == ThemeMode.dark;
44+
45+
if ((isDark && controller.status != AnimationStatus.completed) ||
46+
(!isDark && controller.status != AnimationStatus.dismissed)) {
47+
if (isDark) {
48+
controller.forward();
49+
} else {
50+
controller.reverse();
51+
}
52+
}
53+
}
54+
55+
@override
56+
Widget build(BuildContext context) {
57+
final iconTheme = Theme.of(context).iconTheme;
58+
59+
return Consumer<ThemeProvider>(
60+
builder: (context, provider, child) {
61+
return IconButton(
62+
onPressed: provider.toggleTheme,
63+
icon: CustomPaint(
64+
size: Size.square(iconTheme.size ?? 24),
65+
painter: ThemeSwitchPainter(
66+
animation: animation,
67+
iconThemeData: iconTheme,
68+
),
69+
),
70+
);
71+
},
72+
);
73+
}
74+
}

lib/views/widgets/appbar.dart

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_curve_visualizer/utils/theme/theme_provider.dart';
3+
import 'package:flutter_curve_visualizer/views/widgets/animated_theme_switch/widget.dart';
34
import 'package:flutter_svg/flutter_svg.dart';
45
import 'package:provider/provider.dart';
56
import 'package:url_launcher/url_launcher.dart';
@@ -11,11 +12,13 @@ class HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
1112
Widget build(BuildContext context) {
1213
final theme = Theme.of(context);
1314

15+
final actionPadding = const EdgeInsets.only(right: 12.0);
16+
1417
return AppBar(
1518
title: const Text('Flutter Curve Visualizer'),
1619
actions: [
1720
Padding(
18-
padding: const EdgeInsets.only(right: 12.0),
21+
padding: actionPadding,
1922
child: IconButton(
2023
onPressed: () {
2124
launchUrl(Uri.parse(
@@ -33,19 +36,8 @@ class HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
3336
),
3437
),
3538
Padding(
36-
padding: const EdgeInsets.only(right: 12.0),
37-
child: Consumer<ThemeProvider>(
38-
builder: (context, value, child) {
39-
final iconData = value.getThemeMode() == ThemeMode.dark
40-
? Icons.light_mode
41-
: Icons.dark_mode;
42-
43-
return IconButton(
44-
onPressed: () => value.toggleTheme(),
45-
icon: Icon(iconData),
46-
);
47-
},
48-
),
39+
padding: actionPadding,
40+
child: AnimatedThemeSwitcher(),
4941
),
5042
],
5143
);

lib/views/widgets/code_block.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ class CodeBlock extends StatelessWidget {
1414

1515
ScaffoldMessenger.of(context)
1616
..clearSnackBars()
17-
..showSnackBar(
18-
const SnackBar(content: Text('Copied to clipboard')),
19-
);
17+
..showSnackBar(const SnackBar(content: Text('Copied to clipboard')));
2018
}
2119

2220
@override

lib/views/widgets/dropdown_menu.dart

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
import 'package:flutter/material.dart';
22

33
class DropdownMenuWidget<T> extends StatelessWidget {
4-
final double? width;
54
final String title;
65
final T? value;
76
final List<T> items;
87
final Widget Function(BuildContext context, T value, TextStyle? textStyle)?
9-
childBuilder;
8+
builder;
109
final void Function(T?)? onChanged;
1110

1211
const DropdownMenuWidget({
1312
super.key,
1413
required this.title,
1514
required this.items,
16-
this.width,
17-
this.childBuilder,
15+
this.builder,
1816
this.onChanged,
1917
this.value,
2018
});
@@ -25,39 +23,32 @@ class DropdownMenuWidget<T> extends StatelessWidget {
2523

2624
final childTextStyle = Theme.of(context).textTheme.titleMedium;
2725

28-
final borderRadius = BorderRadius.circular(10);
29-
3026
return PhysicalModel(
31-
color: Colors.transparent,
27+
color: Theme.of(context).colorScheme.onPrimaryFixed,
3228
shadowColor: Theme.of(context).colorScheme.shadow,
3329
elevation: 1.0,
34-
borderRadius: borderRadius,
35-
child: Container(
36-
width: width,
37-
padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
38-
decoration: BoxDecoration(
39-
color: Theme.of(context).colorScheme.onPrimaryFixed,
40-
borderRadius: borderRadius,
41-
),
30+
borderRadius: BorderRadius.circular(10),
31+
child: Padding(
32+
padding: EdgeInsets.fromLTRB(16, 8, 16, 0.0),
4233
child: Column(
4334
crossAxisAlignment: CrossAxisAlignment.start,
4435
children: [
4536
Text(title, style: titleStyle),
4637
DropdownButton<T>(
4738
value: value,
48-
underline: SizedBox.shrink(),
39+
onChanged: onChanged,
40+
underline: const SizedBox.shrink(),
4941
isExpanded: true,
42+
isDense: false,
5043
items: items.map(
51-
(e) {
44+
(item) {
5245
return DropdownMenuItem<T>(
53-
value: e,
54-
child: childBuilder == null
55-
? Text(e.toString(), style: childTextStyle)
56-
: childBuilder!(context, e, childTextStyle),
46+
value: item,
47+
child: builder?.call(context, item, childTextStyle) ??
48+
Text(item.toString(), style: childTextStyle),
5749
);
5850
},
5951
).toList(),
60-
onChanged: onChanged,
6152
),
6253
],
6354
),

0 commit comments

Comments
 (0)