Skip to content

Feature settings enahancement and bug fixe #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 22 commits into from
May 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ee1756e
refactor(router): Use ShellRoute for settings
fulleni May 31, 2025
605a9f9
feat: add font settings page
fulleni May 31, 2025
08791fe
feat(settings): add theme settings page
fulleni May 31, 2025
61694f9
chore(router): add theme and font settings routes
fulleni May 31, 2025
d35e2f8
chore(router): add appearance sub-routes
fulleni May 31, 2025
d7bae8c
refactor(settings): The "Appearance Settings" page (`AppearanceSettin…
fulleni May 31, 2025
02b3434
feat: Add locale support to the app
fulleni May 31, 2025
2f328bc
feat: Add font weight and locale to app theme
fulleni May 31, 2025
950c516
feat: add settings page titles
fulleni May 31, 2025
398649c
feat(router): Add language settings route
fulleni May 31, 2025
a78d2cc
feat(settings): add language change functionality
fulleni May 31, 2025
d63a0e9
feat(settings): add font selection options
fulleni May 31, 2025
58f1db5
feat(settings): add language settings page
fulleni May 31, 2025
738b7f5
refactor(settings): Use specific l10n keys for titles
fulleni May 31, 2025
cea66af
feat(settings): add language settings tile
fulleni May 31, 2025
af81459
feat(theme): add font weight customization
fulleni May 31, 2025
5f1f603
refactor: rebuild MaterialApp on locale change
fulleni May 31, 2025
811fe66
feat(settings): Add language settings tile
fulleni May 31, 2025
185e935
chore: add debug prints for font settings
fulleni May 31, 2025
6edce6a
refactor: themeing and font weight adjustments
fulleni May 31, 2025
d0a35ea
refactor(app): simplify font family mapping
fulleni May 31, 2025
0bf24ae
refactor(theme): Use switch for font selection
fulleni May 31, 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
37 changes: 18 additions & 19 deletions lib/app/bloc/app_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ class AppBloc extends Bloc<AppEvent, AppState> {
final newAppTextScaleFactor = _mapTextScaleFactor(
userAppSettings.displaySettings.textScaleFactor,
);
// Map language code to Locale
final newLocale = Locale(userAppSettings.language);

print('[AppBloc] _onAppSettingsRefreshed: userAppSettings.fontFamily: ${userAppSettings.displaySettings.fontFamily}');
print('[AppBloc] _onAppSettingsRefreshed: userAppSettings.fontWeight: ${userAppSettings.displaySettings.fontWeight}');
print('[AppBloc] _onAppSettingsRefreshed: newFontFamily mapped to: $newFontFamily');

emit(
state.copyWith(
Expand All @@ -109,6 +115,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
appTextScaleFactor: newAppTextScaleFactor,
fontFamily: newFontFamily,
settings: userAppSettings, // Store the fetched settings
locale: newLocale, // Store the new locale
),
);
} on NotFoundException {
Expand All @@ -120,6 +127,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
themeMode: ThemeMode.system,
flexScheme: FlexScheme.material,
appTextScaleFactor: AppTextScaleFactor.medium, // Default enum value
locale: const Locale('en'), // Default to English if settings not found
settings: UserAppSettings(
id: state.user!.id,
), // Provide default settings
Expand Down Expand Up @@ -243,26 +251,17 @@ class AppBloc extends Bloc<AppEvent, AppState> {
}
}

String? _mapFontFamily(String fontFamily) {
// Assuming 'SystemDefault' means use the theme's default font
if (fontFamily == 'SystemDefault') return null;

// Map specific font family names to GoogleFonts
switch (fontFamily) {
case 'Roboto':
return GoogleFonts.roboto().fontFamily;
case 'OpenSans':
return GoogleFonts.openSans().fontFamily;
case 'Lato':
return GoogleFonts.lato().fontFamily;
case 'Montserrat':
return GoogleFonts.montserrat().fontFamily;
case 'Merriweather':
return GoogleFonts.merriweather().fontFamily;
default:
// If an unknown font family is specified, fall back to theme default
return null;
String? _mapFontFamily(String fontFamilyString) {
// If the input is 'SystemDefault', return null so FlexColorScheme uses its default.
if (fontFamilyString == 'SystemDefault') {
print('[AppBloc] _mapFontFamily: Input is SystemDefault, returning null.');
return null;
}
// Otherwise, return the font family string directly.
// The GoogleFonts.xyz().fontFamily getters often return strings like "Roboto-Regular",
// but FlexColorScheme's fontFamily parameter or GoogleFonts.xyzTextTheme() expect simple names.
print('[AppBloc] _mapFontFamily: Input is $fontFamilyString, returning as is.');
return fontFamilyString;
}

// Map AppTextScaleFactor to AppTextScaleFactor (no change needed)
Expand Down
8 changes: 8 additions & 0 deletions lib/app/bloc/app_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class AppState extends Equatable {
this.fontFamily,
this.status = AppStatus.initial,
this.user, // User is now nullable and defaults to null
this.locale, // Added locale
});

/// The index of the currently selected item in the bottom navigation bar.
Expand Down Expand Up @@ -54,6 +55,9 @@ class AppState extends Equatable {
/// User-specific application settings.
final UserAppSettings settings; // Add settings property

/// The current application locale.
final Locale? locale; // Added locale

/// Creates a copy of the current state with updated values.
AppState copyWith({
int? selectedBottomNavigationIndex,
Expand All @@ -64,7 +68,9 @@ class AppState extends Equatable {
AppStatus? status,
User? user,
UserAppSettings? settings, // Add settings to copyWith
Locale? locale, // Added locale
bool clearFontFamily = false,
bool clearLocale = false, // Added to allow clearing locale
}) {
return AppState(
selectedBottomNavigationIndex:
Expand All @@ -76,6 +82,7 @@ class AppState extends Equatable {
status: status ?? this.status,
user: user ?? this.user,
settings: settings ?? this.settings, // Copy settings
locale: clearLocale ? null : locale ?? this.locale, // Added locale
);
}

Expand All @@ -89,5 +96,6 @@ class AppState extends Equatable {
status,
user,
settings, // Include settings in props
locale, // Added locale to props
];
}
11 changes: 9 additions & 2 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,13 @@ class _AppViewState extends State<_AppView> {
previous.themeMode != current.themeMode ||
previous.flexScheme != current.flexScheme ||
previous.fontFamily != current.fontFamily ||
previous.appTextScaleFactor !=
current.appTextScaleFactor, // Use text scale factor
previous.appTextScaleFactor != current.appTextScaleFactor ||
previous.locale != current.locale, // Added locale check
builder: (context, state) {
print('[_AppViewState] Building MaterialApp.router');
print('[_AppViewState] state.fontFamily: ${state.fontFamily}');
print('[_AppViewState] state.settings.displaySettings.fontFamily: ${state.settings.displaySettings.fontFamily}');
print('[_AppViewState] state.settings.displaySettings.fontWeight: ${state.settings.displaySettings.fontWeight}');
return MaterialApp.router(
debugShowCheckedModeBanner: false,
themeMode: state.themeMode,
Expand All @@ -192,15 +196,18 @@ class _AppViewState extends State<_AppView> {
scheme: state.flexScheme,
appTextScaleFactor:
state.settings.displaySettings.textScaleFactor,
appFontWeight: state.settings.displaySettings.fontWeight, // Added
fontFamily: state.settings.displaySettings.fontFamily,
),
darkTheme: darkTheme(
scheme: state.flexScheme,
appTextScaleFactor:
state.settings.displaySettings.textScaleFactor,
appFontWeight: state.settings.displaySettings.fontWeight, // Added
fontFamily: state.settings.displaySettings.fontFamily,
),
routerConfig: _router,
locale: state.locale, // Use locale from AppBloc state
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
);
Expand Down
12 changes: 12 additions & 0 deletions lib/l10n/arb/app_ar.arb
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,18 @@
"@settingsAppearanceFontFamilySystemDefault": {
"description": "Label for the system default font family option"
},
"settingsAppearanceThemeSubPageTitle": "إعدادات المظهر",
"@settingsAppearanceThemeSubPageTitle": {
"description": "Title for the theme settings sub-page under appearance"
},
"settingsAppearanceFontSubPageTitle": "إعدادات الخط",
"@settingsAppearanceFontSubPageTitle": {
"description": "Title for the font settings sub-page under appearance"
},
"settingsLanguageTitle": "اللغة",
"@settingsLanguageTitle": {
"description": "Title for the language settings page/section"
},
"emailCodeSentPageTitle": "أدخل الرمز",
"@emailCodeSentPageTitle": {
"description": "AppBar title for the email code verification page"
Expand Down
12 changes: 12 additions & 0 deletions lib/l10n/arb/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,18 @@
"@settingsAppearanceFontFamilySystemDefault": {
"description": "Label for the system default font family option"
},
"settingsAppearanceThemeSubPageTitle": "Theme Settings",
"@settingsAppearanceThemeSubPageTitle": {
"description": "Title for the theme settings sub-page under appearance"
},
"settingsAppearanceFontSubPageTitle": "Font Settings",
"@settingsAppearanceFontSubPageTitle": {
"description": "Title for the font settings sub-page under appearance"
},
"settingsLanguageTitle": "Language",
"@settingsLanguageTitle": {
"description": "Title for the language settings page/section"
},
"emailCodeSentPageTitle": "Enter Code",
"@emailCodeSentPageTitle": {
"description": "AppBar title for the email code verification page"
Expand Down
84 changes: 50 additions & 34 deletions lib/router/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ import 'package:ht_main/router/routes.dart';
import 'package:ht_main/settings/bloc/settings_bloc.dart'; // Added
import 'package:ht_main/settings/view/appearance_settings_page.dart'; // Added
import 'package:ht_main/settings/view/feed_settings_page.dart'; // Added
import 'package:ht_main/settings/view/font_settings_page.dart'; // Added for new page
import 'package:ht_main/settings/view/language_settings_page.dart'; // Added for new page
import 'package:ht_main/settings/view/notification_settings_page.dart'; // Added
import 'package:ht_main/settings/view/settings_page.dart'; // Added
import 'package:ht_main/settings/view/theme_settings_page.dart'; // Added for new page
import 'package:ht_shared/ht_shared.dart'; // Shared models, FromJson, ToJson, etc.

/// Creates and configures the GoRouter instance for the application.
Expand Down Expand Up @@ -526,22 +529,19 @@ GoRouter createRouter({
name: Routes.accountName,
builder: (context, state) => const AccountPage(),
routes: [
// Sub-route for settings
GoRoute(
path: Routes.settings, // Relative path 'settings'
name: Routes.settingsName,
builder: (context, state) {
// Provide SettingsBloc here for SettingsPage and its children
// Access AppBloc to get the current user ID
// ShellRoute for settings to provide SettingsBloc to children
ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
// This builder provides SettingsBloc to all routes within this ShellRoute.
// 'child' will be SettingsPage, AppearanceSettingsPage, etc.
final appBloc = context.read<AppBloc>();
final userId = appBloc.state.user?.id;

return BlocProvider(
return BlocProvider<SettingsBloc>(
create: (context) {
final settingsBloc = SettingsBloc(
userAppSettingsRepository:
context
.read<HtDataRepository<UserAppSettings>>(),
context.read<HtDataRepository<UserAppSettings>>(),
);
// Only load settings if a userId is available
if (userId != null) {
Expand All @@ -550,39 +550,55 @@ GoRouter createRouter({
);
} else {
// Handle case where user is unexpectedly null.
// This might involve logging or emitting an error state
// directly in SettingsBloc if it's designed to handle it,
// or simply not loading settings.
// For now, we'll assume router redirects prevent this.
print(
'Warning: User ID is null when creating SettingsBloc. Settings will not be loaded.',
'ShellRoute/SettingsBloc: User ID is null when creating SettingsBloc. Settings will not be loaded.',
);
}
return settingsBloc;
},
child: const SettingsPage(), // Use the actual page
child: child, // child is the actual page widget (SettingsPage, AppearanceSettingsPage, etc.)
);
},
// --- Settings Sub-Routes ---
routes: [
GoRoute(
path: Routes.settingsAppearance, // 'appearance'
name: Routes.settingsAppearanceName,
builder:
(context, state) => const AppearanceSettingsPage(),
// SettingsBloc is inherited from parent route
),
GoRoute(
path: Routes.settingsFeed, // 'feed'
name: Routes.settingsFeedName,
builder: (context, state) => const FeedSettingsPage(),
),
GoRoute(
path: Routes.settingsNotifications, // 'notifications'
name: Routes.settingsNotificationsName,
builder:
(context, state) =>
const NotificationSettingsPage(),
path: Routes.settings, // Relative path 'settings' from /account
name: Routes.settingsName,
builder: (context, state) => const SettingsPage(),
// --- Settings Sub-Routes ---
routes: [
GoRoute(
path: Routes.settingsAppearance, // 'appearance' relative to /account/settings
name: Routes.settingsAppearanceName,
builder: (context, state) => const AppearanceSettingsPage(),
routes: [ // Children of AppearanceSettingsPage
GoRoute(
path: Routes.settingsAppearanceTheme, // 'theme' relative to /account/settings/appearance
name: Routes.settingsAppearanceThemeName,
builder: (context, state) => const ThemeSettingsPage(),
),
GoRoute(
path: Routes.settingsAppearanceFont, // 'font' relative to /account/settings/appearance
name: Routes.settingsAppearanceFontName,
builder: (context, state) => const FontSettingsPage(),
),
],
),
GoRoute(
path: Routes.settingsFeed, // 'feed' relative to /account/settings
name: Routes.settingsFeedName,
builder: (context, state) => const FeedSettingsPage(),
),
GoRoute(
path: Routes.settingsNotifications, // 'notifications' relative to /account/settings
name: Routes.settingsNotificationsName,
builder: (context, state) => const NotificationSettingsPage(),
),
GoRoute(
path: Routes.settingsLanguage, // 'language' relative to /account/settings
name: Routes.settingsLanguageName,
builder: (context, state) => const LanguageSettingsPage(),
),
],
),
],
),
Expand Down
12 changes: 12 additions & 0 deletions lib/router/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,24 @@ abstract final class Routes {
// --- Settings Sub-Routes (relative to /account/settings) ---
static const settingsAppearance = 'appearance';
static const settingsAppearanceName = 'settingsAppearance';

// --- Appearance Sub-Routes (relative to /account/settings/appearance) ---
static const settingsAppearanceTheme = 'theme'; // Path: /account/settings/appearance/theme
static const settingsAppearanceThemeName = 'settingsAppearanceTheme';
static const settingsAppearanceFont = 'font'; // Path: /account/settings/appearance/font
static const settingsAppearanceFontName = 'settingsAppearanceFont';

static const settingsFeed = 'feed';
static const settingsFeedName = 'settingsFeed';
static const settingsArticle = 'article';
static const settingsArticleName = 'settingsArticle';
static const settingsNotifications = 'notifications';
static const settingsNotificationsName = 'settingsNotifications';

// --- Language Settings Sub-Route (relative to /account/settings) ---
static const settingsLanguage = 'language'; // Path: /account/settings/language
static const settingsLanguageName = 'settingsLanguage';

// Add names for notification sub-selection routes if needed later
// static const settingsNotificationCategories = 'categories';
// static const settingsNotificationCategoriesName = 'settingsNotificationCategories';
Expand Down
21 changes: 21 additions & 0 deletions lib/settings/bloc/settings_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
_onFeedTileTypeChanged,
transformer: sequential(),
);
on<SettingsLanguageChanged>(
_onLanguageChanged,
transformer: sequential(),
);
// SettingsNotificationsEnabledChanged event and handler removed.
}

Expand Down Expand Up @@ -156,12 +160,14 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
Emitter<SettingsState> emit,
) async {
if (state.userAppSettings == null) return;
print('[SettingsBloc] _onAppFontTypeChanged: Received event.fontType: ${event.fontType}');

final updatedSettings = state.userAppSettings!.copyWith(
displaySettings: state.userAppSettings!.displaySettings.copyWith(
fontFamily: event.fontType,
),
);
print('[SettingsBloc] _onAppFontTypeChanged: Updated settings.fontFamily: ${updatedSettings.displaySettings.fontFamily}');
emit(state.copyWith(userAppSettings: updatedSettings, clearError: true));
await _persistSettings(updatedSettings, emit);
}
Expand All @@ -171,12 +177,14 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
Emitter<SettingsState> emit,
) async {
if (state.userAppSettings == null) return;
print('[SettingsBloc] _onAppFontWeightChanged: Received event.fontWeight: ${event.fontWeight}');

final updatedSettings = state.userAppSettings!.copyWith(
displaySettings: state.userAppSettings!.displaySettings.copyWith(
fontWeight: event.fontWeight,
),
);
print('[SettingsBloc] _onAppFontWeightChanged: Updated settings.fontWeight: ${updatedSettings.displaySettings.fontWeight}');
emit(state.copyWith(userAppSettings: updatedSettings, clearError: true));
await _persistSettings(updatedSettings, emit);
}
Expand All @@ -195,4 +203,17 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> {
emit(state.copyWith(userAppSettings: updatedSettings, clearError: true));
await _persistSettings(updatedSettings, emit);
}

Future<void> _onLanguageChanged(
SettingsLanguageChanged event,
Emitter<SettingsState> emit,
) async {
if (state.userAppSettings == null) return;

final updatedSettings = state.userAppSettings!.copyWith(
language: event.languageCode,
);
emit(state.copyWith(userAppSettings: updatedSettings, clearError: true));
await _persistSettings(updatedSettings, emit);
}
}
Loading
Loading