@@ -2,69 +2,72 @@ import 'dart:async';
2
2
3
3
import 'package:bloc/bloc.dart' ;
4
4
import 'package:equatable/equatable.dart' ;
5
- import 'package:flex_color_scheme/flex_color_scheme.dart' ; // Added
5
+ import 'package:flex_color_scheme/flex_color_scheme.dart' ;
6
6
import 'package:flutter/material.dart' ;
7
7
import 'package:google_fonts/google_fonts.dart' ;
8
- import 'package:ht_authentication_client/ht_authentication_client.dart' ;
9
- import 'package:ht_authentication_repository/ht_authentication_repository.dart' ;
10
- // Ensure full import for FontSize enum access
11
- import 'package:ht_preferences_client/ht_preferences_client.dart' ;
12
- import 'package:ht_preferences_repository/ht_preferences_repository.dart' ;
8
+ import 'package:ht_auth_repository/ht_auth_repository.dart' ;
9
+ import 'package:ht_data_repository/ht_data_repository.dart' ;
10
+ import 'package:ht_shared/ht_shared.dart' ; // Import shared models and exceptions
13
11
14
12
part 'app_event.dart' ;
15
13
part 'app_state.dart' ;
16
14
17
15
class AppBloc extends Bloc <AppEvent , AppState > {
18
16
AppBloc ({
19
- required HtAuthenticationRepository authenticationRepository,
20
- required HtPreferencesRepository preferencesRepository, // Added
17
+ required HtAuthRepository authenticationRepository,
18
+ required HtDataRepository < UserAppSettings > userAppSettingsRepository,
21
19
}) : _authenticationRepository = authenticationRepository,
22
- _preferencesRepository = preferencesRepository, // Added
20
+ _userAppSettingsRepository = userAppSettingsRepository,
23
21
// Initialize with default state, load settings after user is known
24
- super (AppState ()) {
22
+ // Provide a default UserAppSettings instance
23
+ super (
24
+ const AppState (
25
+ settings: UserAppSettings (id: 'default' ),
26
+ selectedBottomNavigationIndex: 0 ,
27
+ ),
28
+ ) {
25
29
on < AppUserChanged > (_onAppUserChanged);
26
- // Add handler for explicitly refreshing settings if needed later
27
30
on < AppSettingsRefreshed > (_onAppSettingsRefreshed);
31
+ on < AppLogoutRequested > (_onLogoutRequested);
32
+ on < AppThemeModeChanged > (_onThemeModeChanged);
33
+ on < AppFlexSchemeChanged > (_onFlexSchemeChanged);
34
+ on < AppFontFamilyChanged > (_onFontFamilyChanged);
35
+ on < AppTextScaleFactorChanged > (_onAppTextScaleFactorChanged);
28
36
29
- // Listen directly to the user stream
30
- _userSubscription = _authenticationRepository.user .listen (
31
- (User user) => add (AppUserChanged (user)), // Explicitly type user
37
+ // Listen directly to the auth state changes stream
38
+ _userSubscription = _authenticationRepository.authStateChanges .listen (
39
+ (User ? user) => add (AppUserChanged (user)), // Handle nullable user
32
40
);
33
41
}
34
42
35
- final HtAuthenticationRepository _authenticationRepository;
36
- final HtPreferencesRepository _preferencesRepository; // Added
37
- late final StreamSubscription <User > _userSubscription;
38
-
39
- // Removed _onAppThemeChanged
43
+ final HtAuthRepository _authenticationRepository;
44
+ final HtDataRepository <UserAppSettings > _userAppSettingsRepository;
45
+ late final StreamSubscription <User ?> _userSubscription;
40
46
41
47
/// Handles user changes and loads initial settings once user is available.
42
48
Future <void > _onAppUserChanged (
43
49
AppUserChanged event,
44
50
Emitter <AppState > emit,
45
51
) async {
46
- // Determine the AppStatus based on the user's AuthenticationStatus
52
+ // Determine the AppStatus based on the user object and its role
47
53
final AppStatus status;
48
- switch (event.user.authenticationStatus ) {
49
- case AuthenticationStatus .unauthenticated :
54
+ switch (event.user? .role ) {
55
+ case null : // User is null (unauthenticated)
50
56
status = AppStatus .unauthenticated;
51
- // Emit status change immediately for unauthenticated users
52
- emit (state.copyWith (status: status, user: event.user));
53
- return ; // Don't load settings for unauthenticated users
54
- case AuthenticationStatus .anonymous:
55
- status = AppStatus .anonymous;
56
- // Continue to load settings for anonymous
57
- case AuthenticationStatus .authenticated:
57
+ case UserRole .standardUser:
58
58
status = AppStatus .authenticated;
59
- // Continue to load settings for authenticated
59
+ // ignore: no_default_cases
60
+ default :
61
+ status = AppStatus .anonymous;
60
62
}
61
63
62
64
// Emit user and status update first
63
65
emit (state.copyWith (status: status, user: event.user));
64
66
65
67
// Load settings now that we have a user (anonymous or authenticated)
66
- // Use a separate event to avoid complexity within this handler
67
- add (const AppSettingsRefreshed ());
68
+ if (event.user != null ) {
69
+ add (const AppSettingsRefreshed ());
70
+ }
68
71
}
69
72
70
73
/// Handles refreshing/loading app settings (theme, font).
@@ -73,100 +76,200 @@ class AppBloc extends Bloc<AppEvent, AppState> {
73
76
Emitter <AppState > emit,
74
77
) async {
75
78
// Avoid loading if user is unauthenticated (shouldn't happen if logic is correct)
76
- if (state.status == AppStatus .unauthenticated) return ;
79
+ if (state.status == AppStatus .unauthenticated || state.user == null ) {
80
+ return ;
81
+ }
77
82
78
83
try {
79
- // Fetch relevant settings
80
- final themeSettings = await _tryFetch (
81
- _preferencesRepository.getThemeSettings,
82
- );
83
- final appSettings = await _tryFetch (
84
- _preferencesRepository.getAppSettings,
84
+ // Fetch relevant settings using the new generic repository
85
+ // Use the current user's ID to fetch user-specific settings
86
+ final userAppSettings = await _userAppSettingsRepository.read (
87
+ id: state.user! .id,
88
+ userId: state.user! .id, // Scope to the current user
85
89
);
86
90
87
- // Map settings to AppState properties
88
- final newThemeMode = _mapAppThemeMode (
89
- themeSettings? .themeMode ?? AppThemeMode .system, // Default
91
+ // Map settings from UserAppSettings to AppState properties
92
+ final newThemeMode = _mapAppBaseTheme (
93
+ userAppSettings.displaySettings.baseTheme,
94
+ );
95
+ final newFlexScheme = _mapAppAccentTheme (
96
+ userAppSettings.displaySettings.accentTheme,
97
+ );
98
+ final newFontFamily = _mapFontFamily (
99
+ userAppSettings.displaySettings.fontFamily,
90
100
);
91
- final newFlexScheme = _mapAppThemeName (
92
- themeSettings ? .themeName ?? AppThemeName .grey, // Default
101
+ final newAppTextScaleFactor = _mapTextScaleFactor (
102
+ userAppSettings.displaySettings.textScaleFactor,
93
103
);
94
- final newFontFamily = _mapAppFontType (appSettings? .appFontType);
95
- // Extract App Font Size
96
- final newAppFontSize =
97
- appSettings? .appFontSize ?? FontSize .medium; // Default
98
104
99
105
emit (
100
106
state.copyWith (
101
107
themeMode: newThemeMode,
102
108
flexScheme: newFlexScheme,
103
- appFontSize : newAppFontSize, // Pass font size
109
+ appTextScaleFactor : newAppTextScaleFactor,
104
110
fontFamily: newFontFamily,
105
- // Use clearFontFamily flag if appSettings was null and we want to reset
106
- clearFontFamily: appSettings == null ,
111
+ settings: userAppSettings, // Store the fetched settings
112
+ ),
113
+ );
114
+ } on NotFoundException {
115
+ // User settings not found (e.g., first time user), use defaults
116
+ print ('User app settings not found, using defaults.' );
117
+ // Emit state with default settings
118
+ emit (
119
+ state.copyWith (
120
+ themeMode: ThemeMode .system,
121
+ flexScheme: FlexScheme .material,
122
+ appTextScaleFactor: AppTextScaleFactor .medium, // Default enum value
123
+ settings: UserAppSettings (
124
+ id: state.user! .id,
125
+ ), // Provide default settings
107
126
),
108
127
);
109
128
} catch (e) {
110
- // Handle potential errors during settings fetch
129
+ // Handle other potential errors during settings fetch
111
130
// Optionally emit a failure state or log the error
112
- print ('Error loading app settings in AppBloc: $e ' );
113
- // Keep the existing theme/font state on error
131
+ print ('Error loading user app settings in AppBloc: $e ' );
132
+ // Keep the existing theme/font state on error, but ensure settings is not null
133
+ emit (
134
+ state.copyWith (settings: state.settings),
135
+ ); // Ensure settings is present
114
136
}
115
137
}
116
138
117
- /// Helper to fetch a setting and handle PreferenceNotFoundException gracefully.
118
- Future <T ?> _tryFetch <T >(Future <T > Function () fetcher) async {
119
- try {
120
- return await fetcher ();
121
- } on PreferenceNotFoundException {
122
- return null ; // Setting not found, return null to use default
123
- } catch (e) {
124
- // Rethrow other errors to be caught by the caller
125
- rethrow ;
126
- }
139
+ // Add handlers for settings changes (dispatching events from UI)
140
+ void _onLogoutRequested (AppLogoutRequested event, Emitter <AppState > emit) {
141
+ unawaited (_authenticationRepository.signOut ());
142
+ }
143
+
144
+ void _onThemeModeChanged (AppThemeModeChanged event, Emitter <AppState > emit) {
145
+ // Update settings and emit new state
146
+ final updatedSettings = state.settings.copyWith (
147
+ displaySettings: state.settings.displaySettings.copyWith (
148
+ baseTheme:
149
+ event.themeMode == ThemeMode .light
150
+ ? AppBaseTheme .light
151
+ : (event.themeMode == ThemeMode .dark
152
+ ? AppBaseTheme .dark
153
+ : AppBaseTheme .system),
154
+ ),
155
+ );
156
+ emit (state.copyWith (settings: updatedSettings, themeMode: event.themeMode));
157
+ // Optionally save settings to repository here
158
+ // unawaited(_userAppSettingsRepository.update(id: updatedSettings.id, item: updatedSettings));
159
+ }
160
+
161
+ void _onFlexSchemeChanged (
162
+ AppFlexSchemeChanged event,
163
+ Emitter <AppState > emit,
164
+ ) {
165
+ // Update settings and emit new state
166
+ final updatedSettings = state.settings.copyWith (
167
+ displaySettings: state.settings.displaySettings.copyWith (
168
+ accentTheme:
169
+ event.flexScheme == FlexScheme .blue
170
+ ? AppAccentTheme .defaultBlue
171
+ : (event.flexScheme == FlexScheme .red
172
+ ? AppAccentTheme .newsRed
173
+ : AppAccentTheme
174
+ .graphiteGray), // Mapping material to graphiteGray
175
+ ),
176
+ );
177
+ emit (
178
+ state.copyWith (settings: updatedSettings, flexScheme: event.flexScheme),
179
+ );
180
+ // Optionally save settings to repository here
181
+ // unawaited(_userAppSettingsRepository.update(id: updatedSettings.id, item: updatedSettings));
182
+ }
183
+
184
+ void _onFontFamilyChanged (
185
+ AppFontFamilyChanged event,
186
+ Emitter <AppState > emit,
187
+ ) {
188
+ // Update settings and emit new state
189
+ final updatedSettings = state.settings.copyWith (
190
+ displaySettings: state.settings.displaySettings.copyWith (
191
+ fontFamily:
192
+ event.fontFamily ?? 'SystemDefault' , // Map null to 'SystemDefault'
193
+ ),
194
+ );
195
+ emit (
196
+ state.copyWith (settings: updatedSettings, fontFamily: event.fontFamily),
197
+ );
198
+ // Optionally save settings to repository here
199
+ // unawaited(_userAppSettingsRepository.update(id: updatedSettings.id, item: updatedSettings));
200
+ }
201
+
202
+ void _onAppTextScaleFactorChanged (
203
+ AppTextScaleFactorChanged event,
204
+ Emitter <AppState > emit,
205
+ ) {
206
+ // Update settings and emit new state
207
+ final updatedSettings = state.settings.copyWith (
208
+ displaySettings: state.settings.displaySettings.copyWith (
209
+ textScaleFactor: event.appTextScaleFactor,
210
+ ),
211
+ );
212
+ emit (
213
+ state.copyWith (
214
+ settings: updatedSettings,
215
+ appTextScaleFactor: event.appTextScaleFactor,
216
+ ),
217
+ );
218
+ // Optionally save settings to repository here
219
+ // unawaited(_userAppSettingsRepository.update(id: updatedSettings.id, item: updatedSettings));
127
220
}
128
221
129
222
// --- Settings Mapping Helpers ---
130
223
131
- ThemeMode _mapAppThemeMode ( AppThemeMode mode) {
224
+ ThemeMode _mapAppBaseTheme ( AppBaseTheme mode) {
132
225
switch (mode) {
133
- case AppThemeMode .light:
226
+ case AppBaseTheme .light:
134
227
return ThemeMode .light;
135
- case AppThemeMode .dark:
228
+ case AppBaseTheme .dark:
136
229
return ThemeMode .dark;
137
- case AppThemeMode .system:
230
+ case AppBaseTheme .system:
138
231
return ThemeMode .system;
139
232
}
140
233
}
141
234
142
- FlexScheme _mapAppThemeName ( AppThemeName name) {
235
+ FlexScheme _mapAppAccentTheme ( AppAccentTheme name) {
143
236
switch (name) {
144
- case AppThemeName .red:
145
- return FlexScheme .red;
146
- case AppThemeName .blue:
237
+ case AppAccentTheme .defaultBlue:
147
238
return FlexScheme .blue;
148
- case AppThemeName .grey:
149
- return FlexScheme .material; // Default grey maps to material
239
+ case AppAccentTheme .newsRed:
240
+ return FlexScheme .red;
241
+ case AppAccentTheme .graphiteGray:
242
+ return FlexScheme .material; // Mapping graphiteGray to material for now
150
243
}
151
244
}
152
245
153
- String ? _mapAppFontType (AppFontType ? type) {
154
- if (type == null ) return null ; // Use theme default if null
246
+ String ? _mapFontFamily (String fontFamily) {
247
+ // Assuming 'SystemDefault' means use the theme's default font
248
+ if (fontFamily == 'SystemDefault' ) return null ;
155
249
156
- switch (type) {
157
- case AppFontType .roboto:
250
+ // Map specific font family names to GoogleFonts
251
+ switch (fontFamily) {
252
+ case 'Roboto' :
158
253
return GoogleFonts .roboto ().fontFamily;
159
- case AppFontType .openSans :
254
+ case 'OpenSans' :
160
255
return GoogleFonts .openSans ().fontFamily;
161
- case AppFontType .lato :
256
+ case 'Lato' :
162
257
return GoogleFonts .lato ().fontFamily;
163
- case AppFontType .montserrat :
258
+ case 'Montserrat' :
164
259
return GoogleFonts .montserrat ().fontFamily;
165
- case AppFontType .merriweather :
260
+ case 'Merriweather' :
166
261
return GoogleFonts .merriweather ().fontFamily;
262
+ default :
263
+ // If an unknown font family is specified, fall back to theme default
264
+ return null ;
167
265
}
168
266
}
169
267
268
+ // Map AppTextScaleFactor to AppTextScaleFactor (no change needed)
269
+ AppTextScaleFactor _mapTextScaleFactor (AppTextScaleFactor factor) {
270
+ return factor;
271
+ }
272
+
170
273
@override
171
274
Future <void > close () {
172
275
_userSubscription.cancel ();
0 commit comments