Skip to content

Commit b49d5f4

Browse files
committed
feat: Migrate to generic data repositories
- Replaced specific repositories - Added UserAppSettings repository - Simplified settings loading - Updated dependencies
1 parent e0bd142 commit b49d5f4

File tree

4 files changed

+336
-216
lines changed

4 files changed

+336
-216
lines changed

lib/app/bloc/app_bloc.dart

Lines changed: 185 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,72 @@ import 'dart:async';
22

33
import 'package:bloc/bloc.dart';
44
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';
66
import 'package:flutter/material.dart';
77
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
1311

1412
part 'app_event.dart';
1513
part 'app_state.dart';
1614

1715
class AppBloc extends Bloc<AppEvent, AppState> {
1816
AppBloc({
19-
required HtAuthenticationRepository authenticationRepository,
20-
required HtPreferencesRepository preferencesRepository, // Added
17+
required HtAuthRepository authenticationRepository,
18+
required HtDataRepository<UserAppSettings> userAppSettingsRepository,
2119
}) : _authenticationRepository = authenticationRepository,
22-
_preferencesRepository = preferencesRepository, // Added
20+
_userAppSettingsRepository = userAppSettingsRepository,
2321
// 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+
) {
2529
on<AppUserChanged>(_onAppUserChanged);
26-
// Add handler for explicitly refreshing settings if needed later
2730
on<AppSettingsRefreshed>(_onAppSettingsRefreshed);
31+
on<AppLogoutRequested>(_onLogoutRequested);
32+
on<AppThemeModeChanged>(_onThemeModeChanged);
33+
on<AppFlexSchemeChanged>(_onFlexSchemeChanged);
34+
on<AppFontFamilyChanged>(_onFontFamilyChanged);
35+
on<AppTextScaleFactorChanged>(_onAppTextScaleFactorChanged);
2836

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
3240
);
3341
}
3442

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;
4046

4147
/// Handles user changes and loads initial settings once user is available.
4248
Future<void> _onAppUserChanged(
4349
AppUserChanged event,
4450
Emitter<AppState> emit,
4551
) async {
46-
// Determine the AppStatus based on the user's AuthenticationStatus
52+
// Determine the AppStatus based on the user object and its role
4753
final AppStatus status;
48-
switch (event.user.authenticationStatus) {
49-
case AuthenticationStatus.unauthenticated:
54+
switch (event.user?.role) {
55+
case null: // User is null (unauthenticated)
5056
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:
5858
status = AppStatus.authenticated;
59-
// Continue to load settings for authenticated
59+
// ignore: no_default_cases
60+
default:
61+
status = AppStatus.anonymous;
6062
}
6163

6264
// Emit user and status update first
6365
emit(state.copyWith(status: status, user: event.user));
6466

6567
// 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+
}
6871
}
6972

7073
/// Handles refreshing/loading app settings (theme, font).
@@ -73,100 +76,200 @@ class AppBloc extends Bloc<AppEvent, AppState> {
7376
Emitter<AppState> emit,
7477
) async {
7578
// 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+
}
7782

7883
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
8589
);
8690

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,
90100
);
91-
final newFlexScheme = _mapAppThemeName(
92-
themeSettings?.themeName ?? AppThemeName.grey, // Default
101+
final newAppTextScaleFactor = _mapTextScaleFactor(
102+
userAppSettings.displaySettings.textScaleFactor,
93103
);
94-
final newFontFamily = _mapAppFontType(appSettings?.appFontType);
95-
// Extract App Font Size
96-
final newAppFontSize =
97-
appSettings?.appFontSize ?? FontSize.medium; // Default
98104

99105
emit(
100106
state.copyWith(
101107
themeMode: newThemeMode,
102108
flexScheme: newFlexScheme,
103-
appFontSize: newAppFontSize, // Pass font size
109+
appTextScaleFactor: newAppTextScaleFactor,
104110
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
107126
),
108127
);
109128
} catch (e) {
110-
// Handle potential errors during settings fetch
129+
// Handle other potential errors during settings fetch
111130
// 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
114136
}
115137
}
116138

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));
127220
}
128221

129222
// --- Settings Mapping Helpers ---
130223

131-
ThemeMode _mapAppThemeMode(AppThemeMode mode) {
224+
ThemeMode _mapAppBaseTheme(AppBaseTheme mode) {
132225
switch (mode) {
133-
case AppThemeMode.light:
226+
case AppBaseTheme.light:
134227
return ThemeMode.light;
135-
case AppThemeMode.dark:
228+
case AppBaseTheme.dark:
136229
return ThemeMode.dark;
137-
case AppThemeMode.system:
230+
case AppBaseTheme.system:
138231
return ThemeMode.system;
139232
}
140233
}
141234

142-
FlexScheme _mapAppThemeName(AppThemeName name) {
235+
FlexScheme _mapAppAccentTheme(AppAccentTheme name) {
143236
switch (name) {
144-
case AppThemeName.red:
145-
return FlexScheme.red;
146-
case AppThemeName.blue:
237+
case AppAccentTheme.defaultBlue:
147238
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
150243
}
151244
}
152245

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;
155249

156-
switch (type) {
157-
case AppFontType.roboto:
250+
// Map specific font family names to GoogleFonts
251+
switch (fontFamily) {
252+
case 'Roboto':
158253
return GoogleFonts.roboto().fontFamily;
159-
case AppFontType.openSans:
254+
case 'OpenSans':
160255
return GoogleFonts.openSans().fontFamily;
161-
case AppFontType.lato:
256+
case 'Lato':
162257
return GoogleFonts.lato().fontFamily;
163-
case AppFontType.montserrat:
258+
case 'Montserrat':
164259
return GoogleFonts.montserrat().fontFamily;
165-
case AppFontType.merriweather:
260+
case 'Merriweather':
166261
return GoogleFonts.merriweather().fontFamily;
262+
default:
263+
// If an unknown font family is specified, fall back to theme default
264+
return null;
167265
}
168266
}
169267

268+
// Map AppTextScaleFactor to AppTextScaleFactor (no change needed)
269+
AppTextScaleFactor _mapTextScaleFactor(AppTextScaleFactor factor) {
270+
return factor;
271+
}
272+
170273
@override
171274
Future<void> close() {
172275
_userSubscription.cancel();

0 commit comments

Comments
 (0)