Skip to content

Commit 1059164

Browse files
committed
feature: settings
1 parent 458f3c8 commit 1059164

18 files changed

+1477
-92
lines changed

lib/app/bloc/app_bloc.dart

Lines changed: 136 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,172 @@
11
import 'dart:async';
22

3+
import 'dart:async';
4+
35
import 'package:bloc/bloc.dart';
46
import 'package:equatable/equatable.dart';
7+
import 'package:flex_color_scheme/flex_color_scheme.dart'; // Added
58
import 'package:flutter/material.dart';
9+
import 'package:google_fonts/google_fonts.dart'; // Added for font mapping
610
import 'package:ht_authentication_client/ht_authentication_client.dart';
711
import 'package:ht_authentication_repository/ht_authentication_repository.dart';
12+
import 'package:ht_preferences_client/ht_preferences_client.dart'; // Added
13+
import 'package:ht_preferences_repository/ht_preferences_repository.dart'; // Added
814

915
part 'app_event.dart';
1016
part 'app_state.dart';
1117

1218
class AppBloc extends Bloc<AppEvent, AppState> {
13-
AppBloc({required HtAuthenticationRepository authenticationRepository})
14-
: _authenticationRepository = authenticationRepository,
15-
super(AppState()) {
16-
on<AppThemeChanged>(_onAppThemeChanged);
19+
AppBloc({
20+
required HtAuthenticationRepository authenticationRepository,
21+
required HtPreferencesRepository preferencesRepository, // Added
22+
}) : _authenticationRepository = authenticationRepository,
23+
_preferencesRepository = preferencesRepository, // Added
24+
// Initialize with default state, load settings after user is known
25+
super(AppState()) {
1726
on<AppUserChanged>(_onAppUserChanged);
27+
// Add handler for explicitly refreshing settings if needed later
28+
on<AppSettingsRefreshed>(_onAppSettingsRefreshed);
1829

30+
// Listen directly to the user stream
1931
_userSubscription = _authenticationRepository.user.listen(
20-
(user) => add(AppUserChanged(user)),
32+
(User user) => add(AppUserChanged(user)), // Explicitly type user
2133
);
2234
}
2335

2436
final HtAuthenticationRepository _authenticationRepository;
37+
final HtPreferencesRepository _preferencesRepository; // Added
2538
late final StreamSubscription<User> _userSubscription;
2639

27-
void _onAppThemeChanged(AppThemeChanged event, Emitter<AppState> emit) {
28-
emit(
29-
state.copyWith(
30-
themeMode:
31-
state.themeMode == ThemeMode.system
32-
? ThemeMode.system
33-
: state.themeMode == ThemeMode.dark
34-
? ThemeMode.dark
35-
: ThemeMode.light,
36-
),
37-
);
38-
}
40+
// Removed _onAppThemeChanged
3941

40-
void _onAppUserChanged(AppUserChanged event, Emitter<AppState> emit) {
42+
/// Handles user changes and loads initial settings once user is available.
43+
Future<void> _onAppUserChanged(
44+
AppUserChanged event,
45+
Emitter<AppState> emit,
46+
) async {
4147
// Determine the AppStatus based on the user's AuthenticationStatus
4248
final AppStatus status;
4349
switch (event.user.authenticationStatus) {
4450
case AuthenticationStatus.unauthenticated:
4551
status = AppStatus.unauthenticated;
52+
// Emit status change immediately for unauthenticated users
53+
emit(state.copyWith(status: status, user: event.user));
54+
return; // Don't load settings for unauthenticated users
4655
case AuthenticationStatus.anonymous:
4756
status = AppStatus.anonymous;
57+
break; // Continue to load settings for anonymous
4858
case AuthenticationStatus.authenticated:
4959
status = AppStatus.authenticated;
50-
// Or handle as error
60+
break; // Continue to load settings for authenticated
5161
}
52-
// Emit the new state including both the updated status and the user object
62+
63+
// Emit user and status update first
5364
emit(state.copyWith(status: status, user: event.user));
65+
66+
// Load settings now that we have a user (anonymous or authenticated)
67+
// Use a separate event to avoid complexity within this handler
68+
add(const AppSettingsRefreshed());
69+
}
70+
71+
/// Handles refreshing/loading app settings (theme, font).
72+
Future<void> _onAppSettingsRefreshed(
73+
AppSettingsRefreshed event,
74+
Emitter<AppState> emit,
75+
) async {
76+
// Avoid loading if user is unauthenticated (shouldn't happen if logic is correct)
77+
if (state.status == AppStatus.unauthenticated) return;
78+
79+
try {
80+
// Fetch relevant settings
81+
final themeSettings =
82+
await _tryFetch(_preferencesRepository.getThemeSettings);
83+
final appSettings =
84+
await _tryFetch(_preferencesRepository.getAppSettings);
85+
86+
// Map settings to AppState properties
87+
final newThemeMode = _mapAppThemeMode(
88+
themeSettings?.themeMode ?? AppThemeMode.system, // Default
89+
);
90+
final newFlexScheme = _mapAppThemeName(
91+
themeSettings?.themeName ?? AppThemeName.grey, // Default
92+
);
93+
final newFontFamily = _mapAppFontType(
94+
appSettings?.appFontType, // Nullable, default handled in theme
95+
);
96+
97+
emit(
98+
state.copyWith(
99+
themeMode: newThemeMode,
100+
flexScheme: newFlexScheme,
101+
fontFamily: newFontFamily,
102+
// Use clearFontFamily flag if appSettings was null and we want to reset
103+
clearFontFamily: appSettings == null,
104+
),
105+
);
106+
} catch (e) {
107+
// Handle potential errors during settings fetch
108+
// Optionally emit a failure state or log the error
109+
print('Error loading app settings in AppBloc: $e');
110+
// Keep the existing theme/font state on error
111+
}
112+
}
113+
114+
/// Helper to fetch a setting and handle PreferenceNotFoundException gracefully.
115+
Future<T?> _tryFetch<T>(Future<T> Function() fetcher) async {
116+
try {
117+
return await fetcher();
118+
} on PreferenceNotFoundException {
119+
return null; // Setting not found, return null to use default
120+
} catch (e) {
121+
// Rethrow other errors to be caught by the caller
122+
rethrow;
123+
}
124+
}
125+
126+
// --- Settings Mapping Helpers ---
127+
128+
ThemeMode _mapAppThemeMode(AppThemeMode mode) {
129+
switch (mode) {
130+
case AppThemeMode.light:
131+
return ThemeMode.light;
132+
case AppThemeMode.dark:
133+
return ThemeMode.dark;
134+
case AppThemeMode.system:
135+
default:
136+
return ThemeMode.system;
137+
}
138+
}
139+
140+
FlexScheme _mapAppThemeName(AppThemeName name) {
141+
switch (name) {
142+
case AppThemeName.red:
143+
return FlexScheme.red;
144+
case AppThemeName.blue:
145+
return FlexScheme.blue;
146+
case AppThemeName.grey:
147+
default:
148+
return FlexScheme.material; // Default grey maps to material
149+
}
150+
}
151+
152+
String? _mapAppFontType(AppFontType? type) {
153+
if (type == null) return null; // Use theme default if null
154+
155+
switch (type) {
156+
case AppFontType.roboto:
157+
return GoogleFonts.roboto().fontFamily;
158+
case AppFontType.openSans:
159+
return GoogleFonts.openSans().fontFamily;
160+
case AppFontType.lato:
161+
return GoogleFonts.lato().fontFamily;
162+
case AppFontType.montserrat:
163+
return GoogleFonts.montserrat().fontFamily;
164+
case AppFontType.merriweather:
165+
return GoogleFonts.merriweather().fontFamily;
166+
// Add other fonts if necessary
167+
default:
168+
return null; // Fallback to theme default
169+
}
54170
}
55171

56172
@override

lib/app/bloc/app_event.dart

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
part of 'app_bloc.dart';
22

3-
sealed class AppEvent extends Equatable {
3+
abstract class AppEvent extends Equatable {
44
const AppEvent();
55

66
@override
77
List<Object> get props => [];
88
}
99

10-
/// {@template app_user_changed}
11-
/// Event triggered when the app theme changes.
12-
/// {@endtemplate}
13-
final class AppThemeChanged extends AppEvent {
10+
@Deprecated('Use SettingsBloc events instead')
11+
class AppThemeChanged extends AppEvent {
1412
const AppThemeChanged();
1513
}
1614

17-
/// {@template app_user_changed}
18-
/// Event triggered when the user's authentication state changes.
19-
/// {@endtemplate}
20-
final class AppUserChanged extends AppEvent {
21-
/// {@macro app_user_changed}
15+
class AppUserChanged extends AppEvent {
2216
const AppUserChanged(this.user);
2317

24-
/// The updated [User] object.
2518
final User user;
2619

2720
@override
2821
List<Object> get props => [user];
2922
}
23+
24+
/// {@template app_settings_refreshed}
25+
/// Internal event to trigger reloading of settings within AppBloc.
26+
/// Added when user changes or upon explicit request.
27+
/// {@endtemplate}
28+
class AppSettingsRefreshed extends AppEvent {
29+
/// {@macro app_settings_refreshed}
30+
const AppSettingsRefreshed();
31+
}

lib/app/bloc/app_state.dart

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,63 @@ enum AppStatus {
1616
}
1717

1818
class AppState extends Equatable {
19+
/// {@macro app_state}
1920
AppState({
2021
this.selectedBottomNavigationIndex = 0,
2122
this.themeMode = ThemeMode.system,
23+
this.flexScheme = FlexScheme.material, // Default scheme
24+
this.fontFamily, // Default font family (null means use FlexColorScheme default)
2225
this.status = AppStatus.initial, // Default to initial
2326
User? user,
24-
}) : user = user ?? User();
27+
}) : user = user ?? User(); // Use default constructor
2528

29+
/// The index of the currently selected item in the bottom navigation bar.
2630
final int selectedBottomNavigationIndex;
31+
32+
/// The overall theme mode (light, dark, system).
2733
final ThemeMode themeMode;
34+
35+
/// The active color scheme defined by FlexColorScheme.
36+
final FlexScheme flexScheme;
37+
38+
/// The active font family name (e.g., from Google Fonts).
39+
/// Null uses the default font family defined in the FlexColorScheme theme.
40+
final String? fontFamily;
41+
42+
/// The current authentication status of the application.
2843
final AppStatus status;
44+
45+
/// The current user details. Defaults to an empty user.
2946
final User user;
3047

48+
/// Creates a copy of the current state with updated values.
3149
AppState copyWith({
3250
int? selectedBottomNavigationIndex,
3351
ThemeMode? themeMode,
52+
FlexScheme? flexScheme,
53+
String? fontFamily,
3454
AppStatus? status,
3555
User? user,
56+
bool clearFontFamily = false, // Flag to explicitly clear font family
3657
}) {
3758
return AppState(
3859
selectedBottomNavigationIndex:
3960
selectedBottomNavigationIndex ?? this.selectedBottomNavigationIndex,
4061
themeMode: themeMode ?? this.themeMode,
62+
flexScheme: flexScheme ?? this.flexScheme,
63+
fontFamily: clearFontFamily ? null : fontFamily ?? this.fontFamily,
4164
status: status ?? this.status,
4265
user: user ?? this.user,
4366
);
4467
}
4568

4669
@override
4770
List<Object?> get props => [
48-
selectedBottomNavigationIndex,
49-
themeMode,
50-
status,
51-
user,
52-
];
71+
selectedBottomNavigationIndex,
72+
themeMode,
73+
flexScheme,
74+
fontFamily,
75+
status,
76+
user,
77+
];
5378
}

0 commit comments

Comments
 (0)