Skip to content

Commit 08791fe

Browse files
committed
feat(settings): add theme settings page
- Add base and accent theme options - Persist theme changes - Refresh app theme on change
1 parent 605a9f9 commit 08791fe

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:ht_main/app/bloc/app_bloc.dart'; // Import AppBloc and events
4+
import 'package:ht_main/l10n/l10n.dart';
5+
import 'package:ht_main/settings/bloc/settings_bloc.dart';
6+
import 'package:ht_main/shared/constants/app_spacing.dart';
7+
import 'package:ht_shared/ht_shared.dart' show AppBaseTheme, AppAccentTheme;
8+
9+
/// {@template theme_settings_page}
10+
/// A page for configuring theme-related settings like base and accent themes.
11+
/// {@endtemplate}
12+
class ThemeSettingsPage extends StatelessWidget {
13+
/// {@macro theme_settings_page}
14+
const ThemeSettingsPage({super.key});
15+
16+
// Helper to map AppBaseTheme enum to user-friendly strings
17+
String _baseThemeToString(AppBaseTheme mode, AppLocalizations l10n) {
18+
switch (mode) {
19+
case AppBaseTheme.light:
20+
return l10n.settingsAppearanceThemeModeLight;
21+
case AppBaseTheme.dark:
22+
return l10n.settingsAppearanceThemeModeDark;
23+
case AppBaseTheme.system:
24+
return l10n.settingsAppearanceThemeModeSystem;
25+
}
26+
}
27+
28+
// Helper to map AppAccentTheme enum to user-friendly strings
29+
String _accentThemeToString(AppAccentTheme name, AppLocalizations l10n) {
30+
switch (name) {
31+
case AppAccentTheme.newsRed:
32+
return l10n.settingsAppearanceThemeNameRed;
33+
case AppAccentTheme.defaultBlue:
34+
return l10n.settingsAppearanceThemeNameBlue;
35+
case AppAccentTheme.graphiteGray:
36+
return l10n.settingsAppearanceThemeNameGrey;
37+
}
38+
}
39+
40+
@override
41+
Widget build(BuildContext context) {
42+
final l10n = context.l10n;
43+
final settingsBloc = context.watch<SettingsBloc>();
44+
final state = settingsBloc.state;
45+
46+
// Ensure we have loaded state before building controls
47+
// This page should only be reached if settings are successfully loaded
48+
// by the parent ShellRoute providing SettingsBloc.
49+
if (state.status != SettingsStatus.success ||
50+
state.userAppSettings == null) {
51+
return Scaffold(
52+
appBar: AppBar(title: Text(l10n.settingsAppearanceTitle)),
53+
body: const Center(child: CircularProgressIndicator()),
54+
);
55+
}
56+
57+
return BlocListener<SettingsBloc, SettingsState>(
58+
listener: (context, settingsState) { // Renamed state to avoid conflict
59+
if (settingsState.status == SettingsStatus.success) {
60+
// Check if it's a successful update, not just initial load
61+
// A more robust check might involve comparing previous and current userAppSettings
62+
// For now, refreshing on any success after an interaction is reasonable.
63+
// Ensure AppBloc is available in context before reading
64+
context.read<AppBloc>().add(const AppSettingsRefreshed());
65+
}
66+
// Optionally, show a SnackBar for errors if not handled globally
67+
// if (settingsState.status == SettingsStatus.failure && settingsState.error != null) {
68+
// ScaffoldMessenger.of(context)
69+
// ..hideCurrentSnackBar()
70+
// ..showSnackBar(SnackBar(content: Text('Error: ${settingsState.error}')));
71+
// }
72+
},
73+
child: Scaffold(
74+
appBar: AppBar(title: Text(l10n.settingsAppearanceTitle)),
75+
body: ListView(
76+
padding: const EdgeInsets.all(AppSpacing.lg),
77+
children: [
78+
// --- Base Theme ---
79+
_buildDropdownSetting<AppBaseTheme>(
80+
context: context,
81+
title: l10n.settingsAppearanceThemeModeLabel,
82+
currentValue: state.userAppSettings!.displaySettings.baseTheme,
83+
items: AppBaseTheme.values,
84+
itemToString: (mode) => _baseThemeToString(mode, l10n),
85+
onChanged: (value) {
86+
if (value != null) {
87+
settingsBloc.add(SettingsAppThemeModeChanged(value));
88+
}
89+
},
90+
),
91+
const SizedBox(height: AppSpacing.lg),
92+
93+
// --- Accent Theme ---
94+
_buildDropdownSetting<AppAccentTheme>(
95+
context: context,
96+
title: l10n.settingsAppearanceThemeNameLabel,
97+
currentValue: state.userAppSettings!.displaySettings.accentTheme,
98+
items: AppAccentTheme.values,
99+
itemToString: (name) => _accentThemeToString(name, l10n),
100+
onChanged: (value) {
101+
if (value != null) {
102+
settingsBloc.add(SettingsAppThemeNameChanged(value));
103+
}
104+
},
105+
),
106+
],
107+
),
108+
), // Correctly close BlocListener's child Scaffold
109+
);
110+
}
111+
112+
/// Generic helper to build a setting row with a title and a dropdown.
113+
Widget _buildDropdownSetting<T>({
114+
required BuildContext context,
115+
required String title,
116+
required T currentValue,
117+
required List<T> items,
118+
required String Function(T) itemToString,
119+
required ValueChanged<T?> onChanged,
120+
}) {
121+
final textTheme = Theme.of(context).textTheme;
122+
return Column(
123+
crossAxisAlignment: CrossAxisAlignment.start,
124+
children: [
125+
Text(title, style: textTheme.titleMedium),
126+
const SizedBox(height: AppSpacing.sm),
127+
DropdownButtonFormField<T>(
128+
value: currentValue,
129+
items: items.map((T value) {
130+
return DropdownMenuItem<T>(
131+
value: value,
132+
child: Text(itemToString(value)),
133+
);
134+
}).toList(),
135+
onChanged: onChanged,
136+
decoration: const InputDecoration(
137+
border: OutlineInputBorder(),
138+
contentPadding: EdgeInsets.symmetric(
139+
horizontal: AppSpacing.md,
140+
vertical: AppSpacing.sm,
141+
),
142+
),
143+
),
144+
],
145+
);
146+
}
147+
}

0 commit comments

Comments
 (0)