Skip to content

Commit 646b421

Browse files
authored
Merge pull request #35 from headlines-toolkit/fix_remote_config
Fix remote config
2 parents 7a10ca4 + a24fd8f commit 646b421

File tree

4 files changed

+143
-48
lines changed

4 files changed

+143
-48
lines changed

lib/app/bloc/app_bloc.dart

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
3131
) {
3232
on<AppUserChanged>(_onAppUserChanged);
3333
on<AppSettingsRefreshed>(_onAppSettingsRefreshed);
34-
on<_AppConfigFetchRequested>(_onAppConfigFetchRequested);
34+
on<AppConfigFetchRequested>(_onAppConfigFetchRequested);
3535
on<AppUserAccountActionShown>(_onAppUserAccountActionShown); // Added
3636
on<AppLogoutRequested>(_onLogoutRequested);
3737
on<AppThemeModeChanged>(_onThemeModeChanged);
@@ -70,14 +70,17 @@ class AppBloc extends Bloc<AppEvent, AppState> {
7070
// Emit user and status update first
7171
emit(state.copyWith(status: status, user: event.user));
7272

73-
// Load settings now that we have a user (anonymous or authenticated)
7473
if (event.user != null) {
75-
add(const AppSettingsRefreshed());
74+
// User is present (authenticated or anonymous)
75+
add(const AppSettingsRefreshed()); // Load user-specific settings
76+
add(const AppConfigFetchRequested()); // Now attempt to fetch AppConfig
77+
} else {
78+
// User is null (unauthenticated or logged out)
79+
// Clear appConfig if user is logged out, as it might be tied to auth context
80+
// or simply to ensure fresh fetch on next login.
81+
// Also ensure status is unauthenticated.
82+
emit(state.copyWith(appConfig: null, clearAppConfig: true, status: AppStatus.unauthenticated));
7683
}
77-
// Fetch AppConfig regardless of user, as it's global config
78-
// Or fetch it once at BLoC initialization if it doesn't depend on user at all.
79-
// For now, fetching after user ensures some app state is set.
80-
add(const _AppConfigFetchRequested());
8184
}
8285

8386
/// Handles refreshing/loading app settings (theme, font).
@@ -298,31 +301,46 @@ class AppBloc extends Bloc<AppEvent, AppState> {
298301
}
299302

300303
Future<void> _onAppConfigFetchRequested(
301-
_AppConfigFetchRequested event,
304+
AppConfigFetchRequested event,
302305
Emitter<AppState> emit,
303306
) async {
304-
// Avoid refetching if already loaded, unless a refresh mechanism is added
305-
if (state.appConfig != null && state.status != AppStatus.initial) return;
307+
// Guard: Only fetch if a user (authenticated or anonymous) is present.
308+
if (state.user == null) {
309+
print('[AppBloc] User is null. Skipping AppConfig fetch because it requires authentication.');
310+
// If AppConfig was somehow present without a user, clear it.
311+
// And ensure status isn't stuck on configFetching if this event was dispatched erroneously.
312+
if (state.appConfig != null || state.status == AppStatus.configFetching) {
313+
emit(state.copyWith(appConfig: null, clearAppConfig: true, status: AppStatus.unauthenticated));
314+
}
315+
return;
316+
}
317+
318+
// Avoid refetching if already loaded for the current user session, unless explicitly trying to recover from a failed state.
319+
if (state.appConfig != null && state.status != AppStatus.configFetchFailed) {
320+
print('[AppBloc] AppConfig already loaded for user ${state.user?.id} and not in a failed state. Skipping fetch.');
321+
return;
322+
}
323+
324+
print('[AppBloc] Attempting to fetch AppConfig for user: ${state.user!.id}...');
325+
emit(state.copyWith(status: AppStatus.configFetching, appConfig: null, clearAppConfig: true));
306326

307327
try {
308-
final appConfig = await _appConfigRepository.read(id: 'app_config');
309-
emit(state.copyWith(appConfig: appConfig));
310-
} on NotFoundException {
311-
// If AppConfig is not found on the backend, use a local default.
312-
// The AppConfig model has default values for its nested configurations.
313-
emit(state.copyWith(appConfig: const AppConfig(id: 'app_config')));
314-
// Optionally, one might want to log this or attempt to create it on backend.
315-
print(
316-
'[AppBloc] AppConfig not found on backend, using local default.',
317-
);
328+
final appConfig = await _appConfigRepository.read(id: 'app_config'); // API requires auth, so token will be used
329+
print('[AppBloc] AppConfig fetched successfully. ID: ${appConfig.id} for user: ${state.user!.id}');
330+
331+
// Determine the correct status based on the existing user's role.
332+
// This ensures that successfully fetching config doesn't revert auth status to 'initial'.
333+
final newStatusBasedOnUser = state.user!.role == UserRole.standardUser
334+
? AppStatus.authenticated
335+
: AppStatus.anonymous;
336+
emit(state.copyWith(appConfig: appConfig, status: newStatusBasedOnUser));
318337
} on HtHttpException catch (e) {
319-
// Failed to fetch AppConfig, log error. App might be partially functional.
320-
print('[AppBloc] Failed to fetch AppConfig: ${e.message}');
321-
// Emit state with null appConfig or keep existing if partially loaded before
322-
emit(state.copyWith(appConfig: null, clearAppConfig: true));
323-
} catch (e) {
324-
print('[AppBloc] Unexpected error fetching AppConfig: $e');
325-
emit(state.copyWith(appConfig: null, clearAppConfig: true));
338+
print('[AppBloc] Failed to fetch AppConfig (HtHttpException) for user ${state.user?.id}: ${e.runtimeType} - ${e.message}');
339+
emit(state.copyWith(status: AppStatus.configFetchFailed, appConfig: null, clearAppConfig: true));
340+
} catch (e, s) {
341+
print('[AppBloc] Unexpected error fetching AppConfig for user ${state.user?.id}: $e');
342+
print('[AppBloc] Stacktrace: $s');
343+
emit(state.copyWith(status: AppStatus.configFetchFailed, appConfig: null, clearAppConfig: true));
326344
}
327345
}
328346

lib/app/bloc/app_event.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,12 @@ class AppTextScaleFactorChanged extends AppEvent {
9292
List<Object?> get props => [appTextScaleFactor];
9393
}
9494

95-
/// {@template _app_config_fetch_requested}
96-
/// Internal event to trigger fetching of the global AppConfig.
95+
/// {@template app_config_fetch_requested}
96+
/// Event to trigger fetching of the global AppConfig.
9797
/// {@endtemplate}
98-
class _AppConfigFetchRequested extends AppEvent {
99-
/// {@macro _app_config_fetch_requested}
100-
const _AppConfigFetchRequested();
98+
class AppConfigFetchRequested extends AppEvent {
99+
/// {@macro app_config_fetch_requested}
100+
const AppConfigFetchRequested();
101101
}
102102

103103
/// {@template app_user_account_action_shown}

lib/app/bloc/app_state.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ enum AppStatus {
1313

1414
/// The user is anonymous (signed in using an anonymous provider).
1515
anonymous,
16+
17+
/// Fetching the essential AppConfig.
18+
configFetching,
19+
20+
/// Fetching the essential AppConfig failed.
21+
configFetchFailed,
1622
}
1723

1824
class AppState extends Equatable {

lib/app/view/app.dart

Lines changed: 87 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//
22
// ignore_for_file: deprecated_member_use
33

4+
import 'package:flex_color_scheme/flex_color_scheme.dart'; // Added
45
import 'package:flutter/material.dart';
56
import 'package:flutter_bloc/flutter_bloc.dart';
67
import 'package:go_router/go_router.dart';
@@ -12,6 +13,8 @@ import 'package:ht_main/authentication/bloc/authentication_bloc.dart';
1213
import 'package:ht_main/l10n/l10n.dart';
1314
import 'package:ht_main/router/router.dart';
1415
import 'package:ht_main/shared/theme/app_theme.dart';
16+
import 'package:ht_main/shared/widgets/failure_state_widget.dart'; // Added
17+
import 'package:ht_main/shared/widgets/loading_state_widget.dart'; // Added
1518
import 'package:ht_shared/ht_shared.dart'; // Shared models, FromJson, ToJson, etc.
1619

1720
class App extends StatelessWidget {
@@ -168,24 +171,93 @@ class _AppViewState extends State<_AppView> {
168171
// (specifically for updating the ValueNotifier) with a BlocListener.
169172
// The BlocBuilder remains for theme changes.
170173
return BlocListener<AppBloc, AppState>(
171-
// Only listen when the status actually changes
174+
// Listen for status changes to update the GoRouter's ValueNotifier
172175
listenWhen: (previous, current) => previous.status != current.status,
173176
listener: (context, state) {
174-
// Directly update the ValueNotifier when the AppBloc status changes.
175-
// This triggers the GoRouter's refreshListenable.
176177
_statusNotifier.value = state.status;
177178
},
178179
child: BlocBuilder<AppBloc, AppState>(
179-
// Build when theme-related properties change (including text scale factor)
180-
buildWhen:
181-
(previous, current) =>
182-
previous.themeMode != current.themeMode ||
183-
previous.flexScheme != current.flexScheme ||
184-
previous.fontFamily != current.fontFamily ||
185-
previous.appTextScaleFactor != current.appTextScaleFactor ||
186-
previous.locale != current.locale ||
187-
previous.settings != current.settings, // Added settings check
180+
// Rebuild the UI based on AppBloc's state (theme, locale, and critical app statuses)
188181
builder: (context, state) {
182+
// Defer l10n access until inside a MaterialApp context
183+
184+
// Handle critical AppConfig loading states globally
185+
if (state.status == AppStatus.configFetching) {
186+
return MaterialApp(
187+
debugShowCheckedModeBanner: false,
188+
theme: lightTheme(
189+
scheme: FlexScheme.material, // Default scheme
190+
appTextScaleFactor: AppTextScaleFactor.medium, // Default
191+
appFontWeight: AppFontWeight.regular, // Default
192+
fontFamily: null, // System default font
193+
),
194+
darkTheme: darkTheme(
195+
scheme: FlexScheme.material, // Default scheme
196+
appTextScaleFactor: AppTextScaleFactor.medium, // Default
197+
appFontWeight: AppFontWeight.regular, // Default
198+
fontFamily: null, // System default font
199+
),
200+
themeMode: state.themeMode, // Still respect light/dark if available from system
201+
localizationsDelegates: AppLocalizations.localizationsDelegates,
202+
supportedLocales: AppLocalizations.supportedLocales,
203+
home: Scaffold(
204+
body: Builder( // Use Builder to get context under MaterialApp
205+
builder: (innerContext) {
206+
final l10n = innerContext.l10n;
207+
return LoadingStateWidget(
208+
icon: Icons.settings_applications_outlined,
209+
headline: l10n.headlinesFeedLoadingHeadline, // "Loading..."
210+
subheadline: l10n.pleaseWait, // "Please wait..."
211+
);
212+
},
213+
),
214+
),
215+
);
216+
}
217+
218+
if (state.status == AppStatus.configFetchFailed) {
219+
return MaterialApp(
220+
debugShowCheckedModeBanner: false,
221+
theme: lightTheme(
222+
scheme: FlexScheme.material, // Default scheme
223+
appTextScaleFactor: AppTextScaleFactor.medium, // Default
224+
appFontWeight: AppFontWeight.regular, // Default
225+
fontFamily: null, // System default font
226+
),
227+
darkTheme: darkTheme(
228+
scheme: FlexScheme.material, // Default scheme
229+
appTextScaleFactor: AppTextScaleFactor.medium, // Default
230+
appFontWeight: AppFontWeight.regular, // Default
231+
fontFamily: null, // System default font
232+
),
233+
themeMode: state.themeMode,
234+
localizationsDelegates: AppLocalizations.localizationsDelegates,
235+
supportedLocales: AppLocalizations.supportedLocales,
236+
home: Scaffold(
237+
body: Builder( // Use Builder to get context under MaterialApp
238+
builder: (innerContext) {
239+
final l10n = innerContext.l10n;
240+
return FailureStateWidget(
241+
message: l10n.unknownError, // "An unknown error occurred."
242+
retryButtonText: "Retry", // Hardcoded for now
243+
onRetry: () {
244+
// Use outer context for BLoC access
245+
context
246+
.read<AppBloc>()
247+
.add(const AppConfigFetchRequested());
248+
},
249+
);
250+
},
251+
),
252+
),
253+
);
254+
}
255+
256+
// If config is loaded (or not in a failed/fetching state for config), proceed with main app UI
257+
// It's safe to access l10n here if needed for print statements,
258+
// as this path implies we are about to build the main MaterialApp.router
259+
// which provides localizations.
260+
// final l10n = context.l10n;
189261
print('[_AppViewState] Building MaterialApp.router');
190262
print('[_AppViewState] state.fontFamily: ${state.fontFamily}');
191263
print(
@@ -197,23 +269,22 @@ class _AppViewState extends State<_AppView> {
197269
return MaterialApp.router(
198270
debugShowCheckedModeBanner: false,
199271
themeMode: state.themeMode,
200-
// Pass scheme and font family from state to theme functions
201272
theme: lightTheme(
202273
scheme: state.flexScheme,
203274
appTextScaleFactor:
204275
state.settings.displaySettings.textScaleFactor,
205-
appFontWeight: state.settings.displaySettings.fontWeight, // Added
276+
appFontWeight: state.settings.displaySettings.fontWeight,
206277
fontFamily: state.settings.displaySettings.fontFamily,
207278
),
208279
darkTheme: darkTheme(
209280
scheme: state.flexScheme,
210281
appTextScaleFactor:
211282
state.settings.displaySettings.textScaleFactor,
212-
appFontWeight: state.settings.displaySettings.fontWeight, // Added
283+
appFontWeight: state.settings.displaySettings.fontWeight,
213284
fontFamily: state.settings.displaySettings.fontFamily,
214285
),
215286
routerConfig: _router,
216-
locale: state.locale, // Use locale from AppBloc state
287+
locale: state.locale,
217288
localizationsDelegates: AppLocalizations.localizationsDelegates,
218289
supportedLocales: AppLocalizations.supportedLocales,
219290
);

0 commit comments

Comments
 (0)