Skip to content

Commit 7d8a40d

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

File tree

9 files changed

+188
-87
lines changed

9 files changed

+188
-87
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/utils/theme/theme_provider.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class ThemeProvider extends ChangeNotifier {
1111
return isDarkMode ? ThemeMode.dark : ThemeMode.light;
1212
}
1313

14-
void toggleTheme() {
14+
Future<void> toggleTheme() async {
1515
final isDarkMode = pref.getBool('darkMode') ?? false;
1616
pref.setBool('darkMode', !isDarkMode);
1717
notifyListeners();

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: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
@override
16+
void paint(Canvas canvas, Size size) {
17+
_drawBackgroundSquares(canvas, size);
18+
}
19+
20+
@override
21+
bool shouldRepaint(covariant CustomPainter oldDelegate) {
22+
return false;
23+
}
24+
25+
void _drawBackgroundSquares(Canvas canvas, Size size) {
26+
final paint = Paint()
27+
..color = iconColor
28+
..style = PaintingStyle.fill;
29+
30+
final boxSize = size.width * .75;
31+
final radius = size.width * 0.30;
32+
final center = Offset(size.width / 2, size.height / 2);
33+
34+
// Create circular mask
35+
final circlePath = Path()
36+
..addOval(Rect.fromCircle(center: center, radius: radius));
37+
38+
canvas.save();
39+
canvas.translate(size.width / 2, size.height / 2);
40+
canvas.rotate(animation.value * pi);
41+
42+
canvas.drawPath(
43+
Path()
44+
..addRect(Rect.fromLTWH(-boxSize / 2, -boxSize / 2, boxSize, boxSize))
45+
..addPath(circlePath.shift(Offset(-size.width / 2, -size.height / 2)),
46+
Offset.zero)
47+
..fillType = PathFillType.evenOdd,
48+
paint,
49+
);
50+
51+
canvas.rotate(pi / 4);
52+
canvas.drawPath(
53+
Path()
54+
..addRect(Rect.fromLTWH(-boxSize / 2, -boxSize / 2, boxSize, boxSize))
55+
..addPath(circlePath.shift(Offset(-size.width / 2, -size.height / 2)),
56+
Offset.zero)
57+
..fillType = PathFillType.evenOdd,
58+
paint,
59+
);
60+
61+
canvas.restore();
62+
63+
final rad = radius - 2;
64+
65+
canvas.drawCircle(
66+
Offset(size.width / 2 + (animation.value * size.width * .10),
67+
size.height / 2 - (animation.value * size.height * .10)),
68+
rad,
69+
paint);
70+
}
71+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
23+
controller = AnimationController(
24+
vsync: this,
25+
duration: const Duration(milliseconds: 500),
26+
);
27+
28+
animation = CurvedAnimation(
29+
parent: controller,
30+
curve: Curves.easeInOut,
31+
);
32+
}
33+
34+
@override
35+
void dispose() {
36+
controller.dispose();
37+
super.dispose();
38+
}
39+
40+
@override
41+
void didChangeDependencies() {
42+
super.didChangeDependencies();
43+
44+
final isDark =
45+
context.read<ThemeProvider>().getThemeMode() == ThemeMode.dark;
46+
47+
if ((isDark && controller.status != AnimationStatus.completed) ||
48+
(!isDark && controller.status != AnimationStatus.dismissed)) {
49+
if (isDark) {
50+
controller.forward();
51+
} else {
52+
controller.reverse();
53+
}
54+
}
55+
}
56+
57+
@override
58+
Widget build(BuildContext context) {
59+
final iconSize = Theme.of(context).iconTheme.size ?? 24;
60+
61+
return Consumer<ThemeProvider>(
62+
builder: (context, provider, child) {
63+
final isDark = provider.getThemeMode() == ThemeMode.dark;
64+
65+
return IconButton(
66+
onPressed: () {
67+
provider.toggleTheme().then((_) {
68+
if (controller.status == AnimationStatus.completed) {
69+
controller.reverse();
70+
} else {
71+
controller.forward();
72+
}
73+
});
74+
},
75+
icon: CustomPaint(
76+
size: Size.square(iconSize),
77+
painter: ThemeSwitchPainter(
78+
animation: animation,
79+
iconThemeData: Theme.of(context).iconTheme,
80+
),
81+
),
82+
);
83+
},
84+
);
85+
}
86+
}

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

0 commit comments

Comments
 (0)