Skip to content

Commit 60ebc9f

Browse files
Implement theme switching functionality with light/dark modes
Co-authored-by: Jason-chen-coder <[email protected]>
1 parent 890d6d6 commit 60ebc9f

File tree

6 files changed

+214
-40
lines changed

6 files changed

+214
-40
lines changed

lib/main.dart

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@
33
import 'package:flutter/material.dart';
44
import 'package:flutter_easy_flow/pages/custom_flow_chart.dart';
55
import 'package:flutter_easy_flow/pages/default_flow_chart.dart';
6+
import 'package:flutter_easy_flow/services/theme_service.dart';
7+
import 'package:provider/provider.dart';
68

79
void main() {
8-
runApp(const MyApp());
10+
runApp(
11+
ChangeNotifierProvider(
12+
create: (_) => ThemeService(),
13+
child: const MyApp(),
14+
),
15+
);
916
}
1017

1118
class MyApp extends StatelessWidget {
@@ -14,22 +21,17 @@ class MyApp extends StatelessWidget {
1421
// This widget is the root of your application.
1522
@override
1623
Widget build(BuildContext context) {
17-
return MaterialApp(
18-
debugShowCheckedModeBanner: false,
19-
title: 'Flutter Flow',
20-
theme: ThemeData(
21-
hintColor: Colors.orange,
22-
chipTheme: ChipThemeData(
23-
backgroundColor: Colors.blueAccent, // 设置ActionChip的背景色
24-
labelStyle: TextStyle(color: Colors.white), // 设置ActionChip的文本样式
25-
),
26-
listTileTheme: ListTileThemeData(
27-
selectedTileColor: Colors.blue[50],
28-
selectedColor: Colors.blue,
29-
),
30-
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue),
31-
),
32-
home: const MyHomePage(),
24+
return Consumer<ThemeService>(
25+
builder: (context, themeService, child) {
26+
return MaterialApp(
27+
debugShowCheckedModeBanner: false,
28+
title: 'Flutter Flow',
29+
theme: ThemeService.lightTheme,
30+
darkTheme: ThemeService.darkTheme,
31+
themeMode: themeService.themeMode,
32+
home: const MyHomePage(),
33+
);
34+
},
3335
);
3436
}
3537
}
@@ -60,6 +62,19 @@ class _MyHomePageState extends State<MyHomePage> {
6062
return Scaffold(
6163
appBar: AppBar(
6264
title: Text(_titles[_currentIndex]),
65+
actions: [
66+
Consumer<ThemeService>(
67+
builder: (context, themeService, child) {
68+
return IconButton(
69+
icon: Icon(
70+
themeService.isDarkMode ? Icons.light_mode : Icons.dark_mode,
71+
),
72+
onPressed: () => themeService.toggleTheme(),
73+
tooltip: themeService.isDarkMode ? '切换到浅色主题' : '切换到深色主题',
74+
);
75+
},
76+
),
77+
],
6378
),
6479
// drawer: Drawer(
6580
// child: ListView(

lib/pages/code_editor_theme.dart

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/pages/custom_flow_chart.dart

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,11 @@ class _CustomFlowChartState extends State<CustomFlowChart> {
132132
width: 480,
133133
height: double.infinity,
134134
decoration: BoxDecoration(
135-
color: Color(0xffffffff),
135+
color: Theme.of(context).cardColor,
136136
),
137137
child: Container(
138138
width: 480,
139-
color: Colors.white,
139+
color: Theme.of(context).cardColor,
140140
child: JsonEditor(
141141
key: _jsonEditorKey,
142142
onChanged: (value) {
@@ -147,6 +147,7 @@ class _CustomFlowChartState extends State<CustomFlowChart> {
147147
enableValueEdit: false,
148148
enableKeyEdit: false,
149149
json: jsonData,
150+
themeColor: Theme.of(context).primaryColor,
150151
),
151152
)),
152153
Expanded(
@@ -192,7 +193,7 @@ class _CustomFlowChartState extends State<CustomFlowChart> {
192193
child: ElevatedButton(
193194
style: ElevatedButton.styleFrom(
194195
padding: EdgeInsets.zero,
195-
backgroundColor: Color(0xFFffffff),
196+
backgroundColor: Theme.of(context).cardColor,
196197
shape: RoundedRectangleBorder(
197198
borderRadius: BorderRadius.circular(5),
198199
),
@@ -203,7 +204,7 @@ class _CustomFlowChartState extends State<CustomFlowChart> {
203204
},
204205
child: Icon(
205206
Icons.cleaning_services_outlined,
206-
color: const Color(0xFF8D8C8D),
207+
color: Theme.of(context).iconTheme.color,
207208
size: 20,
208209
),
209210
),
@@ -217,7 +218,7 @@ class _CustomFlowChartState extends State<CustomFlowChart> {
217218
child: ElevatedButton(
218219
style: ElevatedButton.styleFrom(
219220
padding: EdgeInsets.zero,
220-
backgroundColor: Color(0xFFffffff),
221+
backgroundColor: Theme.of(context).cardColor,
221222
shape: RoundedRectangleBorder(
222223
borderRadius: BorderRadius.circular(5),
223224
),
@@ -226,9 +227,9 @@ class _CustomFlowChartState extends State<CustomFlowChart> {
226227
dashboard.setZoomFactor(
227228
1.5 * dashboard.zoomFactor);
228229
},
229-
child: const Icon(
230+
child: Icon(
230231
Icons.add,
231-
color: Color(0xFF8D8C8D),
232+
color: Theme.of(context).iconTheme.color,
232233
size: 20,
233234
),
234235
),
@@ -242,7 +243,7 @@ class _CustomFlowChartState extends State<CustomFlowChart> {
242243
child: ElevatedButton(
243244
style: ElevatedButton.styleFrom(
244245
padding: EdgeInsets.zero,
245-
backgroundColor: Color(0xFFffffff),
246+
backgroundColor: Theme.of(context).cardColor,
246247
shape: RoundedRectangleBorder(
247248
borderRadius: BorderRadius.circular(5),
248249
),
@@ -251,9 +252,9 @@ class _CustomFlowChartState extends State<CustomFlowChart> {
251252
dashboard.setZoomFactor(
252253
dashboard.zoomFactor / 1.5);
253254
},
254-
child: const Icon(
255+
child: Icon(
255256
Icons.remove,
256-
color: Color(0xFF8D8C8D),
257+
color: Theme.of(context).iconTheme.color,
257258
size: 20,
258259
),
259260
),
@@ -267,15 +268,15 @@ class _CustomFlowChartState extends State<CustomFlowChart> {
267268
child: ElevatedButton(
268269
style: ElevatedButton.styleFrom(
269270
padding: EdgeInsets.zero,
270-
backgroundColor: Color(0xFFffffff),
271+
backgroundColor: Theme.of(context).cardColor,
271272
shape: RoundedRectangleBorder(
272273
borderRadius: BorderRadius.circular(5),
273274
),
274275
),
275276
onPressed: dashboard.setFullView,
276-
child: const Icon(
277+
child: Icon(
277278
Icons.fullscreen,
278-
color: Color(0xFF8D8C8D),
279+
color: Theme.of(context).iconTheme.color,
279280
size: 20,
280281
),
281282
),

lib/services/theme_service.dart

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:shared_preferences/shared_preferences.dart';
3+
4+
class ThemeService extends ChangeNotifier {
5+
static const String _themeKey = 'theme_mode';
6+
7+
ThemeMode _themeMode = ThemeMode.light;
8+
9+
ThemeMode get themeMode => _themeMode;
10+
11+
bool get isDarkMode => _themeMode == ThemeMode.dark;
12+
13+
ThemeService() {
14+
_loadTheme();
15+
}
16+
17+
Future<void> _loadTheme() async {
18+
final prefs = await SharedPreferences.getInstance();
19+
final isDark = prefs.getBool(_themeKey) ?? false;
20+
_themeMode = isDark ? ThemeMode.dark : ThemeMode.light;
21+
notifyListeners();
22+
}
23+
24+
Future<void> toggleTheme() async {
25+
_themeMode = _themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
26+
final prefs = await SharedPreferences.getInstance();
27+
await prefs.setBool(_themeKey, _themeMode == ThemeMode.dark);
28+
notifyListeners();
29+
}
30+
31+
static ThemeData get lightTheme {
32+
return ThemeData(
33+
brightness: Brightness.light,
34+
primarySwatch: Colors.blue,
35+
hintColor: Colors.orange,
36+
chipTheme: const ChipThemeData(
37+
backgroundColor: Colors.blueAccent,
38+
labelStyle: TextStyle(color: Colors.white),
39+
),
40+
listTileTheme: ListTileThemeData(
41+
selectedTileColor: Colors.blue[50],
42+
selectedColor: Colors.blue,
43+
),
44+
colorScheme: ColorScheme.fromSwatch(
45+
primarySwatch: Colors.blue,
46+
brightness: Brightness.light,
47+
),
48+
appBarTheme: const AppBarTheme(
49+
backgroundColor: Colors.blue,
50+
foregroundColor: Colors.white,
51+
elevation: 4,
52+
),
53+
scaffoldBackgroundColor: Colors.white,
54+
cardColor: Colors.white,
55+
iconTheme: const IconThemeData(
56+
color: Color(0xFF8D8C8D),
57+
),
58+
);
59+
}
60+
61+
static ThemeData get darkTheme {
62+
return ThemeData(
63+
brightness: Brightness.dark,
64+
primarySwatch: Colors.blue,
65+
hintColor: Colors.orangeAccent,
66+
chipTheme: const ChipThemeData(
67+
backgroundColor: Colors.blueAccent,
68+
labelStyle: TextStyle(color: Colors.white),
69+
),
70+
listTileTheme: ListTileThemeData(
71+
selectedTileColor: Colors.blue[900],
72+
selectedColor: Colors.blueAccent,
73+
),
74+
colorScheme: ColorScheme.fromSwatch(
75+
primarySwatch: Colors.blue,
76+
brightness: Brightness.dark,
77+
),
78+
appBarTheme: const AppBarTheme(
79+
backgroundColor: Color(0xFF1E1E1E),
80+
foregroundColor: Colors.white,
81+
elevation: 4,
82+
),
83+
scaffoldBackgroundColor: const Color(0xFF121212),
84+
cardColor: const Color(0xFF1E1E1E),
85+
iconTheme: const IconThemeData(
86+
color: Colors.white70,
87+
),
88+
);
89+
}
90+
}

pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ dependencies:
4242
flutter_svg: ^2.0.5
4343
highlight: 0.7.0
4444
flutter_highlight: ^0.7.0
45+
shared_preferences: ^2.2.2
46+
provider: ^6.1.1
4547
dev_dependencies:
4648
flutter_test:
4749
sdk: flutter

test/widget_test.dart

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,47 @@
77

88
import 'package:flutter/material.dart';
99
import 'package:flutter_test/flutter_test.dart';
10+
import 'package:provider/provider.dart';
1011

1112
import 'package:flutter_easy_flow/main.dart';
13+
import 'package:flutter_easy_flow/services/theme_service.dart';
1214

1315
void main() {
14-
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
16+
testWidgets('Theme switching test', (WidgetTester tester) async {
1517
// Build our app and trigger a frame.
16-
await tester.pumpWidget(const MyApp());
18+
await tester.pumpWidget(
19+
ChangeNotifierProvider(
20+
create: (_) => ThemeService(),
21+
child: const MyApp(),
22+
),
23+
);
1724

18-
// Verify that our counter starts at 0.
19-
expect(find.text('0'), findsOneWidget);
20-
expect(find.text('1'), findsNothing);
25+
// Verify that the app starts with light theme
26+
final materialApp = tester.widget<MaterialApp>(find.byType(MaterialApp));
27+
expect(materialApp.themeMode, ThemeMode.light);
2128

22-
// Tap the '+' icon and trigger a frame.
23-
await tester.tap(find.byIcon(Icons.add));
29+
// Find and tap the theme toggle button
30+
final themeToggleButton = find.byIcon(Icons.dark_mode);
31+
expect(themeToggleButton, findsOneWidget);
32+
33+
await tester.tap(themeToggleButton);
2434
await tester.pump();
2535

26-
// Verify that our counter has incremented.
27-
expect(find.text('0'), findsNothing);
28-
expect(find.text('1'), findsOneWidget);
36+
// Verify the icon changed to light mode icon (indicating dark theme is active)
37+
expect(find.byIcon(Icons.light_mode), findsOneWidget);
38+
});
39+
40+
testWidgets('App title and navigation test', (WidgetTester tester) async {
41+
// Build our app and trigger a frame.
42+
await tester.pumpWidget(
43+
ChangeNotifierProvider(
44+
create: (_) => ThemeService(),
45+
child: const MyApp(),
46+
),
47+
);
48+
49+
// Verify that the app title shows correctly
50+
expect(find.text('Flutter-EasyFlow'), findsOneWidget);
51+
expect(find.byType(AppBar), findsOneWidget);
2952
});
3053
}

0 commit comments

Comments
 (0)