Skip to content

Refatcor sync teh dashboard logic and UI with updated models #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
289e9d8
feat(bootstrap): migrate from Category to Topic model
fulleni Jul 12, 2025
eef30c5
refactor(data): Remove unnecessary map calls
fulleni Jul 12, 2025
39dada3
feat(bootstrap): migrate from AppConfig to RemoteConfig model
fulleni Jul 12, 2025
8963f1c
feat(bootstrap): migrate from AppConfig to RemoteConfig model
fulleni Jul 12, 2025
7a986fb
feat(app): update App widget with new repositories and providers
fulleni Jul 12, 2025
e500f56
refactor(app): remove AppStatus import conflict
fulleni Jul 12, 2025
b5f0ce1
feat(app): update AppBloc to use new models and logic
fulleni Jul 12, 2025
be5b355
refactor(content): migrate bloc from Category to Topic and update pag…
fulleni Jul 12, 2025
932e507
refactor(content): migrate state and events from Category to Topic
fulleni Jul 12, 2025
0fa16b6
refactor(content): migrate create_category bloc to create_topic
fulleni Jul 12, 2025
04e3484
refactor(content): migrate create_topic state and event files
fulleni Jul 12, 2025
742d5ba
fix(content): correct create_topic_state to use Topic model
fulleni Jul 12, 2025
6c09c72
refactor(content): migrate edit_category bloc to edit_topic
fulleni Jul 12, 2025
4a147aa
refactor(content): migrate edit_topic state and event files
fulleni Jul 12, 2025
0caa4ac
fix(content): refactor edit_topic_state to use Topic model
fulleni Jul 12, 2025
f30824a
refactor(headline): begin create_headline_bloc migration to Topic
fulleni Jul 12, 2025
a985c95
refactor(headline): migrate create_headline state and events to Topic
fulleni Jul 12, 2025
27aa2b9
refactor(headline): complete create_headline_bloc migration to new model
fulleni Jul 12, 2025
28e39e9
fix(headline): align create_headline_state with Headline model using …
fulleni Jul 12, 2025
325832f
fix(headline): synchronize create_headline event and bloc with excerp…
fulleni Jul 12, 2025
ad88779
fix(headline): fully synchronize create_headline_state with Headline …
fulleni Jul 12, 2025
276de33
feat(content): synchronize create headline bloc events
fulleni Jul 13, 2025
4946900
fix(content): resolve type errors in CreateHeadlineBloc
fulleni Jul 13, 2025
e95b9cf
fix(content): correct source creation logic in bloc
fulleni Jul 13, 2025
019ee3b
fix(content): align topic form validation with model
fulleni Jul 13, 2025
f7723fd
refactor(content): update edit headline events to match model
fulleni Jul 13, 2025
e84096e
refactor(content): update edit headline state to match model
fulleni Jul 13, 2025
4053ef0
refactor(content): update EditHeadlineBloc to match model
fulleni Jul 13, 2025
a0d072f
refactor(content): strengthen edit source form validation
fulleni Jul 13, 2025
fd66213
refactor(content): align EditSourceBloc with non-nullable model
fulleni Jul 13, 2025
6446eb0
fix(content): align edit topic form validation with model
fulleni Jul 13, 2025
df71f48
fix(content): align create form validation with models
fulleni Jul 13, 2025
d94fa9e
fix(content): synchronize content management bloc with topic model
fulleni Jul 13, 2025
352677a
refactor(content): complete migration of create category to topic UI
fulleni Jul 13, 2025
c893fb4
refactor(content): complete migration of create category to topic UI
fulleni Jul 13, 2025
2784739
refactor(content): migrate edit category page to use topic model
fulleni Jul 13, 2025
fd877b6
refactor(content): migrate create headline UI to use topic and country
fulleni Jul 13, 2025
7b58044
refactor(content): migrate edit headline UI to use topic and country
fulleni Jul 13, 2025
2308c31
fix(content): align headlines page with non-nullable headline model
fulleni Jul 13, 2025
887f3e3
fix(content): align sources page with non-nullable source model
fulleni Jul 13, 2025
513012a
refactor(content): migrate topics page to use topic model
fulleni Jul 13, 2025
0491ea4
fix(content): complete migration of content management page to use topic
fulleni Jul 13, 2025
91bdc3d
refactor(l10n): migrate category localization to topic
fulleni Jul 13, 2025
908c7a6
fix(l10n): complete migration of category to topic in descriptions
fulleni Jul 13, 2025
5d9fb53
refactor(l10n): migrate category localization to topic
fulleni Jul 13, 2025
3aadf18
fix(l10n): update translations for 'category' to 'topic'
fulleni Jul 13, 2025
6ae462e
feat(l10n): add excerpt and country fields
fulleni Jul 13, 2025
c7364ec
refactor(app_config): Rename AppConfig to RemoteConfig
fulleni Jul 13, 2025
3b70497
refactor(app_config): Rename AppConfig to RemoteConfig
fulleni Jul 13, 2025
4f588ca
refactor(app_config): Rename AppConfig to RemoteConfig
fulleni Jul 13, 2025
f8f0cdc
refactor(app_config): Rename AppConfig to RemoteConfig
fulleni Jul 13, 2025
9d4f14b
feat(l10n): add app status and update URLs
fulleni Jul 13, 2025
5e45dfb
refactor(app_config): Use string instead of enum for user roles
fulleni Jul 13, 2025
3c8e47e
refactor(app_config): remove redundant cases
fulleni Jul 13, 2025
f39f6aa
refactor(app_config): improve code style and structure
fulleni Jul 13, 2025
c3f4d94
docs: add docstrings to AppState class
fulleni Jul 13, 2025
44e68f8
fix(app): Handle anonymous and guest users
fulleni Jul 13, 2025
6dafaab
refactor(app_shell): remove unused _goBranch method
fulleni Jul 13, 2025
baf7d97
feat(app): add logger to AppBloc
fulleni Jul 13, 2025
2c97bbd
refactor(auth): improve authentication bloc
fulleni Jul 13, 2025
67b91d4
refactor(auth): improve import formatting
fulleni Jul 13, 2025
d172c21
fix(auth): Improve error handling and UI
fulleni Jul 13, 2025
cb86934
fix(auth): handle authentication failure
fulleni Jul 13, 2025
61af038
refactor(auth): remove unused import
fulleni Jul 13, 2025
c51b712
refactor(dashboard): update appConfig type
fulleni Jul 13, 2025
653ff60
refactor(dashboard): Replace AppConfig with RemoteConfig
fulleni Jul 13, 2025
1003268
refactor(dashboard): improve dashboard UI
fulleni Jul 13, 2025
ee066ed
docs: remove unused translation string
fulleni Jul 13, 2025
6e97d36
fix(settings): handle missing user settings
fulleni Jul 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 48 additions & 12 deletions lib/app/bloc/app_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:ht_auth_repository/ht_auth_repository.dart';
import 'package:ht_dashboard/app/config/config.dart' as local_config;
import 'package:ht_data_repository/ht_data_repository.dart';
import 'package:ht_shared/ht_shared.dart';
import 'package:logging/logging.dart';

part 'app_event.dart';
part 'app_state.dart';
Expand All @@ -16,11 +17,13 @@ class AppBloc extends Bloc<AppEvent, AppState> {
AppBloc({
required HtAuthRepository authenticationRepository,
required HtDataRepository<UserAppSettings> userAppSettingsRepository,
required HtDataRepository<AppConfig> appConfigRepository,
required HtDataRepository<RemoteConfig> appConfigRepository,
required local_config.AppEnvironment environment,
Logger? logger,
}) : _authenticationRepository = authenticationRepository,
_userAppSettingsRepository = userAppSettingsRepository,
_appConfigRepository = appConfigRepository,
_logger = logger ?? Logger('AppBloc'),
super(
AppState(environment: environment),
) {
Expand All @@ -35,7 +38,8 @@ class AppBloc extends Bloc<AppEvent, AppState> {

final HtAuthRepository _authenticationRepository;
final HtDataRepository<UserAppSettings> _userAppSettingsRepository;
final HtDataRepository<AppConfig> _appConfigRepository;
final HtDataRepository<RemoteConfig> _appConfigRepository;
final Logger _logger;
late final StreamSubscription<User?> _userSubscription;

/// Handles user changes and loads initial settings once user is available.
Expand All @@ -46,10 +50,15 @@ class AppBloc extends Bloc<AppEvent, AppState> {
final user = event.user;
final AppStatus status;

if (user != null &&
(user.roles.contains(UserRoles.admin) ||
user.roles.contains(UserRoles.publisher))) {
status = AppStatus.authenticated;
if (user != null) {
if (user.dashboardRole == DashboardUserRole.admin ||
user.dashboardRole == DashboardUserRole.publisher) {
status = AppStatus.authenticated;
} else if (user.appRole == AppUserRole.guestUser) {
status = AppStatus.anonymous;
} else {
status = AppStatus.unauthenticated;
}
} else {
status = AppStatus.unauthenticated;
}
Expand All @@ -66,20 +75,47 @@ class AppBloc extends Bloc<AppEvent, AppState> {
emit(state.copyWith(userAppSettings: userAppSettings));
} on NotFoundException {
// If settings not found, create default ones
final defaultSettings = UserAppSettings(id: user.id);
_logger.info(
'User app settings not found for user ${user.id}. Creating default.',
);
final defaultSettings = UserAppSettings(
id: user.id, // Use actual user ID for default settings
displaySettings: const DisplaySettings(
baseTheme: AppBaseTheme.system,
accentTheme: AppAccentTheme.defaultBlue,
fontFamily: 'SystemDefault',
textScaleFactor: AppTextScaleFactor.medium,
fontWeight: AppFontWeight.regular,
),
language: 'en',
feedPreferences: const FeedDisplayPreferences(
headlineDensity: HeadlineDensity.standard,
headlineImageStyle: HeadlineImageStyle.largeThumbnail,
showSourceInHeadlineFeed: true,
showPublishDateInHeadlineFeed: true,
),
);
await _userAppSettingsRepository.create(item: defaultSettings);
emit(state.copyWith(userAppSettings: defaultSettings));
} on HtHttpException catch (e) {
} on HtHttpException catch (e, s) {
// Handle HTTP exceptions during settings load
print('Error loading user app settings: ${e.message}');
_logger.severe(
'Error loading user app settings for user ${user.id}: ${e.message}',
e,
s,
);
emit(state.copyWith(clearUserAppSettings: true));
} catch (e) {
} catch (e, s) {
// Handle any other unexpected errors
print('Unexpected error loading user app settings: $e');
_logger.severe(
'Unexpected error loading user app settings for user ${user.id}: $e',
e,
s,
);
emit(state.copyWith(clearUserAppSettings: true));
}
} else {
// If user is unauthenticated, clear app settings
// If user is unauthenticated or anonymous, clear app settings
emit(state.copyWith(clearUserAppSettings: true));
}
}
Expand Down
4 changes: 4 additions & 0 deletions lib/app/bloc/app_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ enum AppStatus {
anonymous,
}

/// {@template app_state}
/// Represents the overall state of the application, including authentication
/// status, current user, environment, and user-specific settings.
/// {@endtemplate}
class AppState extends Equatable {
/// {@macro app_state}
const AppState({
Expand Down
48 changes: 26 additions & 22 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,43 @@ import 'package:ht_dashboard/router/router.dart';
import 'package:ht_dashboard/shared/theme/app_theme.dart';
import 'package:ht_data_repository/ht_data_repository.dart';
import 'package:ht_kv_storage_service/ht_kv_storage_service.dart';
import 'package:ht_shared/ht_shared.dart';
import 'package:ht_shared/ht_shared.dart' hide AppStatus;
import 'package:logging/logging.dart';

class App extends StatelessWidget {
const App({
required HtAuthRepository htAuthenticationRepository,
required HtDataRepository<Headline> htHeadlinesRepository,
required HtDataRepository<Category> htCategoriesRepository,
required HtDataRepository<Topic> htTopicsRepository,
required HtDataRepository<Country> htCountriesRepository,
required HtDataRepository<Source> htSourcesRepository,
required HtDataRepository<UserAppSettings> htUserAppSettingsRepository,
required HtDataRepository<UserContentPreferences>
htUserContentPreferencesRepository,
required HtDataRepository<AppConfig> htAppConfigRepository,
required HtDataRepository<UserContentPreferences> htUserContentPreferencesRepository,
required HtDataRepository<RemoteConfig> htRemoteConfigRepository,
required HtDataRepository<DashboardSummary> htDashboardSummaryRepository,
required HtKVStorageService kvStorageService,
required AppEnvironment environment,
super.key,
}) : _htAuthenticationRepository = htAuthenticationRepository,
_htHeadlinesRepository = htHeadlinesRepository,
_htCategoriesRepository = htCategoriesRepository,
_htTopicsRepository = htTopicsRepository,
_htCountriesRepository = htCountriesRepository,
_htSourcesRepository = htSourcesRepository,
_htUserAppSettingsRepository = htUserAppSettingsRepository,
_htUserContentPreferencesRepository = htUserContentPreferencesRepository,
_htAppConfigRepository = htAppConfigRepository,
_htRemoteConfigRepository = htRemoteConfigRepository,
_kvStorageService = kvStorageService,
_htDashboardSummaryRepository = htDashboardSummaryRepository,
_environment = environment;

final HtAuthRepository _htAuthenticationRepository;
final HtDataRepository<Headline> _htHeadlinesRepository;
final HtDataRepository<Category> _htCategoriesRepository;
final HtDataRepository<Topic> _htTopicsRepository;
final HtDataRepository<Country> _htCountriesRepository;
final HtDataRepository<Source> _htSourcesRepository;
final HtDataRepository<UserAppSettings> _htUserAppSettingsRepository;
final HtDataRepository<UserContentPreferences>
_htUserContentPreferencesRepository;
final HtDataRepository<AppConfig> _htAppConfigRepository;
final HtDataRepository<UserContentPreferences> _htUserContentPreferencesRepository;
final HtDataRepository<RemoteConfig> _htRemoteConfigRepository;
final HtDataRepository<DashboardSummary> _htDashboardSummaryRepository;
final HtKVStorageService _kvStorageService;
final AppEnvironment _environment;
Expand All @@ -66,12 +65,12 @@ class App extends StatelessWidget {
providers: [
RepositoryProvider.value(value: _htAuthenticationRepository),
RepositoryProvider.value(value: _htHeadlinesRepository),
RepositoryProvider.value(value: _htCategoriesRepository),
RepositoryProvider.value(value: _htTopicsRepository),
RepositoryProvider.value(value: _htCountriesRepository),
RepositoryProvider.value(value: _htSourcesRepository),
RepositoryProvider.value(value: _htUserAppSettingsRepository),
RepositoryProvider.value(value: _htUserContentPreferencesRepository),
RepositoryProvider.value(value: _htAppConfigRepository),
RepositoryProvider.value(value: _htRemoteConfigRepository),
RepositoryProvider.value(value: _htDashboardSummaryRepository),
RepositoryProvider.value(value: _kvStorageService),
],
Expand All @@ -80,10 +79,12 @@ class App extends StatelessWidget {
BlocProvider(
create: (context) => AppBloc(
authenticationRepository: context.read<HtAuthRepository>(),
userAppSettingsRepository: context
.read<HtDataRepository<UserAppSettings>>(),
appConfigRepository: context.read<HtDataRepository<AppConfig>>(),
userAppSettingsRepository:
context.read<HtDataRepository<UserAppSettings>>(),
appConfigRepository:
context.read<HtDataRepository<RemoteConfig>>(),
environment: _environment,
logger: Logger('AppBloc'),
),
),
BlocProvider(
Expand All @@ -93,21 +94,23 @@ class App extends StatelessWidget {
),
BlocProvider(
create: (context) => AppConfigurationBloc(
appConfigRepository: context.read<HtDataRepository<AppConfig>>(),
remoteConfigRepository:
context.read<HtDataRepository<RemoteConfig>>(),
),
),
BlocProvider(
create: (context) => ContentManagementBloc(
headlinesRepository: context.read<HtDataRepository<Headline>>(),
categoriesRepository: context.read<HtDataRepository<Category>>(),
topicsRepository: context.read<HtDataRepository<Topic>>(),
sourcesRepository: context.read<HtDataRepository<Source>>(),
),
),
BlocProvider(
create: (context) => DashboardBloc(
dashboardSummaryRepository: context
.read<HtDataRepository<DashboardSummary>>(),
appConfigRepository: context.read<HtDataRepository<AppConfig>>(),
dashboardSummaryRepository:
context.read<HtDataRepository<DashboardSummary>>(),
appConfigRepository:
context.read<HtDataRepository<RemoteConfig>>(),
headlinesRepository: context.read<HtDataRepository<Headline>>(),
),
),
Expand All @@ -122,6 +125,7 @@ class App extends StatelessWidget {
}

class _AppView extends StatefulWidget {
/// {@macro app_view}
const _AppView({
required this.htAuthenticationRepository,
required this.environment,
Expand Down Expand Up @@ -215,7 +219,7 @@ class _AppViewState extends State<_AppView> {
themeMode: switch (baseTheme) {
AppBaseTheme.light => ThemeMode.light,
AppBaseTheme.dark => ThemeMode.dark,
AppBaseTheme.system || null => ThemeMode.system,
_ => ThemeMode.system,
},
locale: language != null ? Locale(language) : null,
),
Expand Down
14 changes: 6 additions & 8 deletions lib/app/view/app_shell.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,6 @@ class AppShell extends StatelessWidget {
/// navigators in a stateful way.
final StatefulNavigationShell navigationShell;

void _goBranch(int index) {
navigationShell.goBranch(
index,
initialLocation: index == navigationShell.currentIndex,
);
}

@override
Widget build(BuildContext context) {
final l10n = context.l10n;
Expand Down Expand Up @@ -70,7 +63,12 @@ class AppShell extends StatelessWidget {
),
body: AdaptiveScaffold(
selectedIndex: navigationShell.currentIndex,
onSelectedIndexChange: _goBranch,
onSelectedIndexChange: (index) {
navigationShell.goBranch(
index,
initialLocation: index == navigationShell.currentIndex,
);
},
destinations: [
NavigationDestination(
icon: const Icon(Icons.dashboard_outlined),
Expand Down
28 changes: 14 additions & 14 deletions lib/app_configuration/bloc/app_configuration_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:ht_data_repository/ht_data_repository.dart';
import 'package:ht_shared/ht_shared.dart'; // Use AppConfig from ht_shared
import 'package:ht_shared/ht_shared.dart'; // Use RemoteConfig from ht_shared

part 'app_configuration_event.dart';
part 'app_configuration_state.dart';

class AppConfigurationBloc
extends Bloc<AppConfigurationEvent, AppConfigurationState> {
AppConfigurationBloc({
required HtDataRepository<AppConfig> appConfigRepository,
}) : _appConfigRepository = appConfigRepository,
required HtDataRepository<RemoteConfig> remoteConfigRepository,
}) : _remoteConfigRepository = remoteConfigRepository,
super(
const AppConfigurationState(),
) {
Expand All @@ -20,20 +20,20 @@ class AppConfigurationBloc
on<AppConfigurationDiscarded>(_onAppConfigurationDiscarded);
}

final HtDataRepository<AppConfig> _appConfigRepository;
final HtDataRepository<RemoteConfig> _remoteConfigRepository;

Future<void> _onAppConfigurationLoaded(
AppConfigurationLoaded event,
Emitter<AppConfigurationState> emit,
) async {
emit(state.copyWith(status: AppConfigurationStatus.loading));
try {
final appConfig = await _appConfigRepository.read(id: 'app_config');
final remoteConfig = await _remoteConfigRepository.read(id: 'app_config');
emit(
state.copyWith(
status: AppConfigurationStatus.success,
appConfig: appConfig,
originalAppConfig: appConfig, // Store the original config
remoteConfig: remoteConfig,
originalRemoteConfig: remoteConfig, // Store the original config
isDirty: false,
clearShowSaveSuccess:
true, // Clear any previous success snackbar flag
Expand Down Expand Up @@ -62,15 +62,15 @@ class AppConfigurationBloc
) async {
emit(state.copyWith(status: AppConfigurationStatus.loading));
try {
final updatedConfig = await _appConfigRepository.update(
id: event.appConfig.id,
item: event.appConfig,
final updatedConfig = await _remoteConfigRepository.update(
id: event.remoteConfig.id,
item: event.remoteConfig,
);
emit(
state.copyWith(
status: AppConfigurationStatus.success,
appConfig: updatedConfig,
originalAppConfig: updatedConfig, // Update original config on save
remoteConfig: updatedConfig,
originalRemoteConfig: updatedConfig, // Update original config on save
isDirty: false,
showSaveSuccess: true, // Set flag to show success snackbar
),
Expand Down Expand Up @@ -98,7 +98,7 @@ class AppConfigurationBloc
) {
emit(
state.copyWith(
appConfig: event.appConfig,
remoteConfig: event.remoteConfig,
isDirty: true,
clearErrorMessage: true, // Clear any previous error messages
clearShowSaveSuccess: true, // Clear success snackbar on field change
Expand All @@ -112,7 +112,7 @@ class AppConfigurationBloc
) {
emit(
state.copyWith(
appConfig: state.originalAppConfig, // Revert to original config
remoteConfig: state.originalRemoteConfig, // Revert to original config
isDirty: false,
clearErrorMessage: true, // Clear any previous error messages
clearShowSaveSuccess: true, // Clear success snackbar
Expand Down
14 changes: 7 additions & 7 deletions lib/app_configuration/bloc/app_configuration_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ class AppConfigurationLoaded extends AppConfigurationEvent {
/// {@endtemplate}
class AppConfigurationUpdated extends AppConfigurationEvent {
/// {@macro app_configuration_updated}
const AppConfigurationUpdated(this.appConfig);
const AppConfigurationUpdated(this.remoteConfig);

/// The updated application configuration.
final AppConfig appConfig;
final RemoteConfig remoteConfig;

@override
List<Object?> get props => [appConfig];
List<Object?> get props => [remoteConfig];
}

/// {@template app_configuration_discarded}
Expand All @@ -50,12 +50,12 @@ class AppConfigurationDiscarded extends AppConfigurationEvent {
class AppConfigurationFieldChanged extends AppConfigurationEvent {
/// {@macro app_configuration_field_changed}
const AppConfigurationFieldChanged({
this.appConfig,
this.remoteConfig,
});

/// The partially or fully updated AppConfig object.
final AppConfig? appConfig;
/// The partially or fully updated RemoteConfig object.
final RemoteConfig? remoteConfig;

@override
List<Object?> get props => [appConfig];
List<Object?> get props => [remoteConfig];
}
Loading
Loading