Skip to content

Feature developement entry point #37

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 10 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ Developed with best practices for a maintainable and scalable codebase:
* **GoRouter:** Well-structured and powerful navigation.
* **Benefit for you:** An easy-to-understand, extendable, and testable foundation for your project. 📈

#### ⚙️ **Flexible Environment Configuration**
Easily switch between development (in-memory data or local API) and production environments with a simple code change. This empowers rapid prototyping, robust testing, and seamless deployment.
* **Benefit for you:** Accelerate your development cycle and ensure your app is always ready for any deployment scenario. 🚀

#### 🌍 **Localization Ready**
Fully internationalized with working English and Arabic localizations (`.arb` files). Adding more languages is straightforward.
* **Benefit for you:** Easily adapt your application for a global audience. 🌐
Expand Down
49 changes: 29 additions & 20 deletions lib/account/bloc/account_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
AccountBloc({
required HtAuthRepository authenticationRepository,
required HtDataRepository<UserContentPreferences>
userContentPreferencesRepository,
}) : _authenticationRepository = authenticationRepository,
_userContentPreferencesRepository = userContentPreferencesRepository,
super(const AccountState()) {
userContentPreferencesRepository,
}) : _authenticationRepository = authenticationRepository,
_userContentPreferencesRepository = userContentPreferencesRepository,
super(const AccountState()) {
// Listen to user changes from HtAuthRepository
_userSubscription =
_authenticationRepository.authStateChanges.listen((user) {
_userSubscription = _authenticationRepository.authStateChanges.listen((
user,
) {
add(AccountUserChanged(user));
});

Expand All @@ -35,7 +36,7 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {

final HtAuthRepository _authenticationRepository;
final HtDataRepository<UserContentPreferences>
_userContentPreferencesRepository;
_userContentPreferencesRepository;
late StreamSubscription<User?> _userSubscription;

Future<void> _onAccountUserChanged(
Expand All @@ -47,7 +48,9 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
add(AccountLoadUserPreferences(userId: event.user!.id));
} else {
// Clear preferences if user is null (logged out)
emit(state.copyWith(clearPreferences: true, status: AccountStatus.initial));
emit(
state.copyWith(clearPreferences: true, status: AccountStatus.initial),
);
}
}

Expand Down Expand Up @@ -113,8 +116,9 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
emit(state.copyWith(status: AccountStatus.loading));

final currentPrefs = state.preferences!;
final isCurrentlySaved =
currentPrefs.savedHeadlines.any((h) => h.id == event.headline.id);
final isCurrentlySaved = currentPrefs.savedHeadlines.any(
(h) => h.id == event.headline.id,
);
final List<Headline> updatedSavedHeadlines;

if (isCurrentlySaved) {
Expand All @@ -125,8 +129,9 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
..add(event.headline);
}

final updatedPrefs =
currentPrefs.copyWith(savedHeadlines: updatedSavedHeadlines);
final updatedPrefs = currentPrefs.copyWith(
savedHeadlines: updatedSavedHeadlines,
);

try {
await _userContentPreferencesRepository.update(
Expand Down Expand Up @@ -163,8 +168,9 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
emit(state.copyWith(status: AccountStatus.loading));

final currentPrefs = state.preferences!;
final isCurrentlyFollowed = currentPrefs.followedCategories
.any((c) => c.id == event.category.id);
final isCurrentlyFollowed = currentPrefs.followedCategories.any(
(c) => c.id == event.category.id,
);
final List<Category> updatedFollowedCategories;

if (isCurrentlyFollowed) {
Expand All @@ -175,8 +181,9 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
..add(event.category);
}

final updatedPrefs =
currentPrefs.copyWith(followedCategories: updatedFollowedCategories);
final updatedPrefs = currentPrefs.copyWith(
followedCategories: updatedFollowedCategories,
);

try {
await _userContentPreferencesRepository.update(
Expand Down Expand Up @@ -213,8 +220,9 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
emit(state.copyWith(status: AccountStatus.loading));

final currentPrefs = state.preferences!;
final isCurrentlyFollowed =
currentPrefs.followedSources.any((s) => s.id == event.source.id);
final isCurrentlyFollowed = currentPrefs.followedSources.any(
(s) => s.id == event.source.id,
);
final List<Source> updatedFollowedSources;

if (isCurrentlyFollowed) {
Expand All @@ -225,8 +233,9 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
..add(event.source);
}

final updatedPrefs =
currentPrefs.copyWith(followedSources: updatedFollowedSources);
final updatedPrefs = currentPrefs.copyWith(
followedSources: updatedFollowedSources,
);

try {
await _userContentPreferencesRepository.update(
Expand Down
6 changes: 4 additions & 2 deletions lib/account/bloc/account_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ abstract class AccountEvent extends Equatable {
List<Object?> get props => [];
}

class AccountUserChanged extends AccountEvent { // Corrected name
class AccountUserChanged extends AccountEvent {
// Corrected name
const AccountUserChanged(this.user);
final User? user;

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

class AccountLoadUserPreferences extends AccountEvent { // Corrected name
class AccountLoadUserPreferences extends AccountEvent {
// Corrected name
const AccountLoadUserPreferences({required this.userId});
final String userId;

Expand Down
3 changes: 1 addition & 2 deletions lib/account/bloc/account_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ class AccountState extends Equatable {
return AccountState(
status: status ?? this.status,
user: clearUser ? null : user ?? this.user,
preferences:
clearPreferences ? null : preferences ?? this.preferences,
preferences: clearPreferences ? null : preferences ?? this.preferences,
errorMessage:
clearErrorMessage ? null : errorMessage ?? this.errorMessage,
);
Expand Down
50 changes: 36 additions & 14 deletions lib/account/view/account_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,17 @@ class AccountPage extends StatelessWidget {
),
),
body: ListView(
padding: const EdgeInsets.all(AppSpacing.paddingMedium), // Adjusted padding
padding: const EdgeInsets.all(
AppSpacing.paddingMedium,
), // Adjusted padding
children: [
_buildUserHeader(context, user, isAnonymous),
const SizedBox(height: AppSpacing.lg), // Adjusted spacing
ListTile(
leading: Icon(Icons.tune_outlined, color: theme.colorScheme.primary),
leading: Icon(
Icons.tune_outlined,
color: theme.colorScheme.primary,
),
title: Text(
l10n.accountContentPreferencesTile,
style: textTheme.titleMedium,
Expand All @@ -50,9 +55,15 @@ class AccountPage extends StatelessWidget {
context.goNamed(Routes.manageFollowedItemsName);
},
),
const Divider(indent: AppSpacing.paddingMedium, endIndent: AppSpacing.paddingMedium),
const Divider(
indent: AppSpacing.paddingMedium,
endIndent: AppSpacing.paddingMedium,
),
ListTile(
leading: Icon(Icons.bookmark_outline, color: theme.colorScheme.primary),
leading: Icon(
Icons.bookmark_outline,
color: theme.colorScheme.primary,
),
title: Text(
l10n.accountSavedHeadlinesTile,
style: textTheme.titleMedium,
Expand All @@ -62,9 +73,15 @@ class AccountPage extends StatelessWidget {
context.goNamed(Routes.accountSavedHeadlinesName);
},
),
const Divider(indent: AppSpacing.paddingMedium, endIndent: AppSpacing.paddingMedium),
const Divider(
indent: AppSpacing.paddingMedium,
endIndent: AppSpacing.paddingMedium,
),
_buildSettingsTile(context),
const Divider(indent: AppSpacing.paddingMedium, endIndent: AppSpacing.paddingMedium),
const Divider(
indent: AppSpacing.paddingMedium,
endIndent: AppSpacing.paddingMedium,
),
],
),
);
Expand All @@ -89,12 +106,14 @@ class AccountPage extends StatelessWidget {
displayName = l10n.accountAnonymousUser;
statusWidget = Padding(
padding: const EdgeInsets.only(top: AppSpacing.md), // Increased padding
child: ElevatedButton.icon( // Changed to ElevatedButton
child: ElevatedButton.icon(
// Changed to ElevatedButton
icon: const Icon(Icons.link_outlined),
label: Text(l10n.accountSignInPromptButton),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.lg, vertical: AppSpacing.sm,
horizontal: AppSpacing.lg,
vertical: AppSpacing.sm,
),
textStyle: textTheme.labelLarge,
),
Expand All @@ -111,7 +130,8 @@ class AccountPage extends StatelessWidget {
statusWidget = Column(
mainAxisSize: MainAxisSize.min, // To keep column tight
children: [
if (user?.role != null) ...[ // Show role only if available
if (user?.role != null) ...[
// Show role only if available
const SizedBox(height: AppSpacing.xs),
Text(
l10n.accountRoleLabel(user!.role.name),
Expand All @@ -122,21 +142,23 @@ class AccountPage extends StatelessWidget {
),
],
const SizedBox(height: AppSpacing.md), // Consistent spacing
OutlinedButton.icon( // Changed to OutlinedButton.icon
OutlinedButton.icon(
// Changed to OutlinedButton.icon
icon: Icon(Icons.logout, color: colorScheme.error),
label: Text(l10n.accountSignOutTile),
style: OutlinedButton.styleFrom(
foregroundColor: colorScheme.error,
side: BorderSide(color: colorScheme.error.withOpacity(0.5)),
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.lg, vertical: AppSpacing.sm,
horizontal: AppSpacing.lg,
vertical: AppSpacing.sm,
),
textStyle: textTheme.labelLarge,
),
onPressed: () {
context
.read<AuthenticationBloc>()
.add(const AuthenticationSignOutRequested());
context.read<AuthenticationBloc>().add(
const AuthenticationSignOutRequested(),
);
},
),
],
Expand Down
Loading
Loading