Skip to content

Commit bacf05a

Browse files
committed
feat: switch dark/light theme
1 parent 6555ec5 commit bacf05a

27 files changed

Lines changed: 770 additions & 308 deletions

src/lib/core/theme/auth_layout_template.dart

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'package:flutter/material.dart';
2-
import 'app_colors.dart';
32

43
/// The master layout template for all authentication screens.
54
/// Handles the UI skeleton, loading states, backgrounds, and responsive scrolling.
@@ -36,13 +35,16 @@ class AuthLayoutTemplate extends StatelessWidget {
3635
@override
3736
Widget build(BuildContext context) {
3837
return Scaffold(
39-
backgroundColor: AppColors.background,
38+
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
4039
appBar: AppBar(
4140
backgroundColor: Colors.transparent,
4241
elevation: 0,
4342
leading: Navigator.canPop(context)
4443
? IconButton(
45-
icon: const Icon(Icons.arrow_back, color: AppColors.primary),
44+
icon: Icon(
45+
Icons.arrow_back,
46+
color: Theme.of(context).colorScheme.primary,
47+
),
4648
onPressed: () => Navigator.pop(context),
4749
)
4850
: null,
@@ -54,9 +56,11 @@ class AuthLayoutTemplate extends StatelessWidget {
5456
child: Column(
5557
mainAxisAlignment: MainAxisAlignment.center,
5658
children: [
57-
_buildHeader(),
59+
_buildHeader(context),
5860
const SizedBox(height: 32),
59-
useCard ? _buildCardContainer() : _buildTransparentContainer(),
61+
useCard
62+
? _buildCardContainer(context)
63+
: _buildTransparentContainer(context),
6064
const SizedBox(height: 32),
6165
if (footerContent != null) footerContent!,
6266
],
@@ -67,73 +71,78 @@ class AuthLayoutTemplate extends StatelessWidget {
6771
);
6872
}
6973

70-
Widget _buildHeader() {
74+
Widget _buildHeader(BuildContext context) {
7175
return Column(
7276
children: [
7377
customHeaderIcon ??
7478
Container(
7579
width: 80,
7680
height: 80,
7781
decoration: BoxDecoration(
78-
color: AppColors.white,
82+
color: Theme.of(context).colorScheme.surface,
7983
borderRadius: BorderRadius.circular(24),
8084
boxShadow: [
8185
BoxShadow(
82-
color: AppColors.primary.withOpacity(0.1),
86+
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
8387
blurRadius: 20,
8488
offset: const Offset(0, 10),
8589
),
8690
],
8791
),
88-
child: const Center(
89-
child: Icon(Icons.task_alt, size: 48, color: AppColors.primary),
92+
child: Center(
93+
child: Icon(
94+
Icons.task_alt,
95+
size: 48,
96+
color: Theme.of(context).colorScheme.primary,
97+
),
9098
),
9199
),
92100
const SizedBox(height: 24),
93101
Text(
94102
title,
95-
style: const TextStyle(
103+
style: TextStyle(
96104
fontSize: 28,
97105
fontWeight: FontWeight.w800,
98-
color: AppColors.textDark,
106+
color: Theme.of(context).colorScheme.onSurface,
99107
letterSpacing: -0.5,
100108
),
101109
),
102110
const SizedBox(height: 8),
103111
Text(
104112
subtitle,
105-
style: const TextStyle(
113+
style: TextStyle(
106114
fontSize: 14,
107115
fontWeight: FontWeight.w500,
108-
color: AppColors.textSecondary,
116+
color: Theme.of(context).colorScheme.onSurfaceVariant,
109117
),
110118
textAlign: TextAlign.center,
111119
),
112120
],
113121
);
114122
}
115123

116-
Widget _buildCardContainer() {
124+
Widget _buildCardContainer(BuildContext context) {
117125
return Container(
118126
padding: const EdgeInsets.all(32),
119127
decoration: BoxDecoration(
120-
color: AppColors.white,
128+
color: Theme.of(context).colorScheme.surface,
121129
borderRadius: BorderRadius.circular(32),
122130
boxShadow: [
123131
BoxShadow(
124-
color: AppColors.primary.withOpacity(0.08),
132+
color: Theme.of(context).colorScheme.primary.withOpacity(0.08),
125133
blurRadius: 30,
126134
offset: const Offset(0, 10),
127135
),
128136
],
129137
),
130-
child: _buildFormElements(),
138+
child: _buildFormElements(context),
131139
);
132140
}
133141

134-
Widget _buildTransparentContainer() => _buildFormElements();
142+
Widget _buildTransparentContainer(BuildContext context) =>
143+
_buildFormElements(context);
135144

136-
Widget _buildFormElements() {
145+
Widget _buildFormElements(BuildContext context) {
137146
return Column(
138147
crossAxisAlignment: CrossAxisAlignment.stretch,
139148
children: [
@@ -143,20 +152,21 @@ class AuthLayoutTemplate extends StatelessWidget {
143152
// Disable button if loading
144153
onPressed: isLoading ? null : onSubmit,
145154
style: ElevatedButton.styleFrom(
146-
backgroundColor: AppColors.primary,
147-
disabledBackgroundColor: AppColors.primary.withOpacity(0.6),
155+
backgroundColor: Theme.of(context).colorScheme.primary,
156+
disabledBackgroundColor:
157+
Theme.of(context).colorScheme.primary.withOpacity(0.6),
148158
padding: const EdgeInsets.symmetric(vertical: 20),
149159
shape: RoundedRectangleBorder(
150160
borderRadius: BorderRadius.circular(16),
151161
),
152162
elevation: isLoading ? 0 : 4,
153163
),
154164
child: isLoading
155-
? const SizedBox(
165+
? SizedBox(
156166
height: 20,
157167
width: 20,
158168
child: CircularProgressIndicator(
159-
color: AppColors.white,
169+
color: Theme.of(context).colorScheme.surface,
160170
strokeWidth: 2,
161171
),
162172
)
@@ -165,38 +175,38 @@ class AuthLayoutTemplate extends StatelessWidget {
165175
children: [
166176
Text(
167177
submitText,
168-
style: const TextStyle(
178+
style: TextStyle(
169179
fontSize: 16,
170180
fontWeight: FontWeight.bold,
171-
color: AppColors.white,
181+
color: Theme.of(context).colorScheme.surface,
172182
),
173183
),
174184
const SizedBox(width: 8),
175-
const Icon(
185+
Icon(
176186
Icons.arrow_forward,
177-
color: AppColors.white,
187+
color: Theme.of(context).colorScheme.surface,
178188
size: 20,
179189
),
180190
],
181191
),
182192
),
183193
if (showSocial) ...[
184194
const SizedBox(height: 32),
185-
const Row(
195+
Row(
186196
children: [
187-
Expanded(child: Divider(color: AppColors.border)),
188-
Padding(
197+
Expanded(
198+
child: Divider(color: Theme.of(context).colorScheme.outline),
199+
),
200+
const Padding(
189201
padding: EdgeInsets.symmetric(horizontal: 16),
190202
child: Text(
191203
'OR',
192-
style: TextStyle(
193-
fontSize: 10,
194-
fontWeight: FontWeight.bold,
195-
color: AppColors.textSecondary,
196-
),
204+
style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
197205
),
198206
),
199-
Expanded(child: Divider(color: AppColors.border)),
207+
Expanded(
208+
child: Divider(color: Theme.of(context).colorScheme.outline),
209+
),
200210
],
201211
),
202212
const SizedBox(height: 24),

src/lib/core/theme/custom_text_field.dart

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'package:flutter/material.dart';
2-
import 'app_colors.dart';
32

43
/// A highly reusable text input field component.
54
class CustomTextField extends StatelessWidget {
@@ -36,29 +35,29 @@ class CustomTextField extends StatelessWidget {
3635
style: TextStyle(
3736
fontSize: 14,
3837
fontWeight: FontWeight.bold,
39-
color: AppColors.textDark.withOpacity(0.6),
38+
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
4039
letterSpacing: 1,
4140
),
4241
),
4342
),
4443
TextFormField(
4544
controller: controller,
4645
obscureText: obscureText,
47-
style: const TextStyle(
48-
color: AppColors.textDark,
46+
style: TextStyle(
47+
color: Theme.of(context).colorScheme.onSurface,
4948
fontWeight: FontWeight.w600,
5049
fontSize: 16,
5150
),
5251
decoration: InputDecoration(
5352
hintText: hint,
5453
hintStyle: TextStyle(
55-
color: AppColors.textDark.withOpacity(0.3),
54+
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.3),
5655
fontWeight: FontWeight.w400,
5756
fontSize: 16,
5857
),
5958
filled: true,
60-
fillColor: AppColors.inputBackground,
61-
prefixIcon: Icon(icon, color: AppColors.primary),
59+
fillColor: Theme.of(context).colorScheme.surface,
60+
prefixIcon: Icon(icon, color: Theme.of(context).colorScheme.primary),
6261
suffixIcon: isPassword
6362
? IconButton(
6463
icon: Icon(
@@ -78,12 +77,12 @@ class CustomTextField extends StatelessWidget {
7877
),
7978
enabledBorder: OutlineInputBorder(
8079
borderRadius: BorderRadius.circular(16),
81-
borderSide: const BorderSide(color: AppColors.border),
80+
borderSide: BorderSide(color: Theme.of(context).colorScheme.outline),
8281
),
8382
focusedBorder: OutlineInputBorder(
8483
borderRadius: BorderRadius.circular(16),
85-
borderSide: const BorderSide(
86-
color: AppColors.primary,
84+
borderSide: BorderSide(
85+
color: Theme.of(context).colorScheme.primary,
8786
width: 2,
8887
),
8988
),
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:shared_preferences/shared_preferences.dart';
3+
4+
class ThemeProvider extends ChangeNotifier {
5+
6+
static const String _themeKey = "theme_mode";
7+
8+
ThemeMode _themeMode = ThemeMode.light;
9+
10+
ThemeMode get themeMode => _themeMode;
11+
12+
ThemeProvider() {
13+
_loadThemeFromPrefs();
14+
}
15+
16+
// func change theme
17+
void updateTheme(String appearance) {
18+
final normalized = appearance.trim().toLowerCase();
19+
20+
if (normalized == 'dark') {
21+
_themeMode = ThemeMode.dark;
22+
} else if (normalized == 'light') {
23+
_themeMode = ThemeMode.light;
24+
} else {
25+
_themeMode = ThemeMode.system;
26+
}
27+
28+
_saveThemeToPrefs(
29+
_themeMode == ThemeMode.dark
30+
? 'Dark'
31+
: _themeMode == ThemeMode.light
32+
? 'Light'
33+
: 'System',
34+
);
35+
notifyListeners();
36+
}
37+
38+
Future<void> _saveThemeToPrefs(String appearance) async {
39+
final prefs = await SharedPreferences.getInstance();
40+
await prefs.setString(_themeKey, appearance);
41+
}
42+
43+
// load theme when user open app
44+
Future<void> _loadThemeFromPrefs() async {
45+
final prefs = await SharedPreferences.getInstance();
46+
final appearance = (prefs.getString(_themeKey) ?? 'Light').trim().toLowerCase();
47+
48+
if (appearance == 'dark') {
49+
_themeMode = ThemeMode.dark;
50+
} else if (appearance == 'system') {
51+
_themeMode = ThemeMode.system;
52+
} else {
53+
_themeMode = ThemeMode.light;
54+
}
55+
notifyListeners();
56+
}
57+
}

src/lib/core/widgets/custom_input_field.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'package:flutter/material.dart';
2-
import '../theme/app_colors.dart';
32

43
class CustomInputField extends StatelessWidget {
54
final String label;
@@ -32,8 +31,11 @@ class CustomInputField extends StatelessWidget {
3231
contentPadding: EdgeInsets.zero,
3332
enabledBorder: const UnderlineInputBorder(
3433
borderSide: BorderSide(color: Colors.black26)),
35-
focusedBorder: const UnderlineInputBorder(
36-
borderSide: BorderSide(color: AppColors.primaryBlue)),
34+
focusedBorder: UnderlineInputBorder(
35+
borderSide: BorderSide(
36+
color: Theme.of(context).colorScheme.primary,
37+
),
38+
),
3739
),
3840
),
3941
],

0 commit comments

Comments
 (0)