Skip to content

Commit decbbd5

Browse files
authored
Merge pull request #210 from flutter-news-app-full-source-code/refactor/overhaul-headlines-saved-filter
Refactor/overhaul headlines saved filter
2 parents 7d43f3b + 7ca554b commit decbbd5

27 files changed

+1585
-759
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,20 @@ Explore the high-level domains below to see how.
2626
<details>
2727
<summary><strong>📰 Content Discovery & User Engagement</strong></summary>
2828

29-
### 📱 Dynamic, High-Performance News Feed
30-
A beautiful, infinitely scrolling feed serves as the core of the user experience. It's not just a list; it's an intelligent content delivery system.
29+
### 📱 Dynamic & Personalized News Feed
30+
A beautiful, infinitely scrolling feed serves as the core of the user experience. It's not just a list; it's an intelligent, user-driven content delivery system.
3131
- **Instantaneous Content Switching:** An intelligent, session-based cache pre-fetches and holds data, eliminating loading spinners when users switch between their preferred content views.
3232
- **Personalized Viewing:** Users control their experience with settings for information density and image presentation, adapting the feed to their reading style.
3333
- **Smart In-Feed Prompts:** The feed dynamically injects context-aware items like calls-to-action and content suggestions, driven by configurable rules to avoid user fatigue.
3434
> **Your Advantage:** You get a world-class, production-quality feed system out of the box. Skip the complex UI, state management, and performance optimization work.
3535
3636
---
3737

38-
### 🔎 Powerful & Intuitive Search
38+
### 🔎 Powerful Content Curation & Discovery
3939
Give users the tools to find exactly what they're looking for with a multi-faceted discovery system.
4040
- **Customizable Filter Bar:** A persistent, one-tap filter bar provides instant access to pre-defined and user-created content streams.
41-
- **Advanced Content Curation:** A dedicated UI allows users to construct and save highly specific news feeds by combining various content categories, publishers, and regions of interest.
41+
- **Advanced Content Curation:** A dedicated management UI allows users to construct, save, and manage highly specific news feeds. Users can save frequently used filters for on-demand use and pin them for one-tap access on the main feed.
42+
- **Proactive Notification Subscriptions:** When saving a filter, users can subscribe to receive push notifications—such as breaking news alerts or daily digests—for content that matches their specific criteria.
4243
- **Dedicated Discovery Hub:** Users can browse publishers by category in horizontally scrolling carousels, apply regional filters, and perform targeted searches.
4344
> **Your Advantage:** Deliver powerful content discovery tools that keep users engaged, increase session duration, and encourage return visits.
4445

lib/account/view/account_page.dart

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,6 @@ class AccountPage extends StatelessWidget {
199199
onTap: () => context.pushNamed(Routes.accountSavedHeadlinesName),
200200
),
201201
const Divider(),
202-
buildTile(
203-
icon: Icons.filter_alt_outlined,
204-
title: l10n.accountSavedFiltersTile,
205-
onTap: () => context.pushNamed(Routes.accountSavedFiltersName),
206-
),
207-
const Divider(),
208202
],
209203
);
210204
}

lib/account/view/saved_filters_page.dart

Lines changed: 0 additions & 152 deletions
This file was deleted.

lib/app/bloc/app_bloc.dart

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ class AppBloc extends Bloc<AppEvent, AppState> {
7575
on<AppPeriodicConfigFetchRequested>(_onAppPeriodicConfigFetchRequested);
7676
on<AppUserFeedDecoratorShown>(_onAppUserFeedDecoratorShown);
7777
on<AppUserContentPreferencesChanged>(_onAppUserContentPreferencesChanged);
78-
on<SavedFilterAdded>(_onSavedFilterAdded);
79-
on<SavedFilterUpdated>(_onSavedFilterUpdated);
80-
on<SavedFilterDeleted>(_onSavedFilterDeleted);
81-
on<SavedFiltersReordered>(_onSavedFiltersReordered);
78+
on<SavedHeadlineFilterAdded>(_onSavedHeadlineFilterAdded);
79+
on<SavedHeadlineFilterUpdated>(_onSavedHeadlineFilterUpdated);
80+
on<SavedHeadlineFilterDeleted>(_onSavedHeadlineFilterDeleted);
81+
on<SavedHeadlineFiltersReordered>(_onSavedHeadlineFiltersReordered);
8282
on<AppLogoutRequested>(_onLogoutRequested);
8383
}
8484

@@ -439,124 +439,135 @@ class AppBloc extends Bloc<AppEvent, AppState> {
439439
}
440440
}
441441

442-
/// Handles adding a new saved filter to the user's content preferences.
443-
Future<void> _onSavedFilterAdded(
444-
SavedFilterAdded event,
442+
/// Handles adding a new saved headline filter to the user's content
443+
/// preferences.
444+
Future<void> _onSavedHeadlineFilterAdded(
445+
SavedHeadlineFilterAdded event,
445446
Emitter<AppState> emit,
446447
) async {
447448
_logger.fine(
448-
'[AppBloc] SavedFilterAdded event received for filter: "${event.filter.name}".',
449+
'[AppBloc] SavedHeadlineFilterAdded event received for filter: '
450+
'"${event.filter.name}".',
449451
);
450452
// This method modifies the preferences in memory and then delegates the
451453
// persistence and final state update to the AppUserContentPreferencesChanged event.
452454
if (state.userContentPreferences == null) {
453455
_logger.warning(
454-
'[AppBloc] Skipping SavedFilterAdded: UserContentPreferences not loaded.',
456+
'[AppBloc] Skipping SavedHeadlineFilterAdded: UserContentPreferences '
457+
'not loaded.',
455458
);
456459
return;
457460
}
458461

459-
final updatedSavedFilters = List<SavedFilter>.from(
460-
state.userContentPreferences!.savedFilters,
462+
final updatedSavedFilters = List<SavedHeadlineFilter>.from(
463+
state.userContentPreferences!.savedHeadlineFilters,
461464
)..add(event.filter);
462465

463466
final updatedPreferences = state.userContentPreferences!.copyWith(
464-
savedFilters: updatedSavedFilters,
467+
savedHeadlineFilters: updatedSavedFilters,
465468
);
466469

467470
add(AppUserContentPreferencesChanged(preferences: updatedPreferences));
468471
}
469472

470-
/// Handles updating an existing saved filter (e.g., renaming it).
471-
Future<void> _onSavedFilterUpdated(
472-
SavedFilterUpdated event,
473+
/// Handles updating an existing saved headline filter (e.g., renaming it).
474+
Future<void> _onSavedHeadlineFilterUpdated(
475+
SavedHeadlineFilterUpdated event,
473476
Emitter<AppState> emit,
474477
) async {
475478
_logger.fine(
476-
'[AppBloc] SavedFilterUpdated event received for filter id: ${event.filter.id}.',
479+
'[AppBloc] SavedHeadlineFilterUpdated event received for filter id: '
480+
'${event.filter.id}.',
477481
);
478482
// This method modifies the preferences in memory and then delegates the
479483
// persistence and final state update to the AppUserContentPreferencesChanged event.
480484
if (state.userContentPreferences == null) {
481485
_logger.warning(
482-
'[AppBloc] Skipping SavedFilterUpdated: UserContentPreferences not loaded.',
486+
'[AppBloc] Skipping SavedHeadlineFilterUpdated: '
487+
'UserContentPreferences not loaded.',
483488
);
484489
return;
485490
}
486491

487-
final originalFilters = state.userContentPreferences!.savedFilters;
492+
final originalFilters = state.userContentPreferences!.savedHeadlineFilters;
488493
final index = originalFilters.indexWhere((f) => f.id == event.filter.id);
489494

490495
if (index == -1) {
491496
_logger.warning(
492-
'[AppBloc] Skipping SavedFilterUpdated: Filter with id ${event.filter.id} not found.',
497+
'[AppBloc] Skipping SavedHeadlineFilterUpdated: Filter with id '
498+
'${event.filter.id} not found.',
493499
);
494500
return;
495501
}
496502

497-
final updatedSavedFilters = List<SavedFilter>.from(originalFilters)
503+
final updatedSavedFilters = List<SavedHeadlineFilter>.from(originalFilters)
498504
..[index] = event.filter;
499505

500506
final updatedPreferences = state.userContentPreferences!.copyWith(
501-
savedFilters: updatedSavedFilters,
507+
savedHeadlineFilters: updatedSavedFilters,
502508
);
503509

504510
add(AppUserContentPreferencesChanged(preferences: updatedPreferences));
505511
}
506512

507-
/// Handles deleting a saved filter from the user's content preferences.
508-
Future<void> _onSavedFilterDeleted(
509-
SavedFilterDeleted event,
513+
/// Handles deleting a saved headline filter from the user's content
514+
/// preferences.
515+
Future<void> _onSavedHeadlineFilterDeleted(
516+
SavedHeadlineFilterDeleted event,
510517
Emitter<AppState> emit,
511518
) async {
512519
_logger.fine(
513-
'[AppBloc] SavedFilterDeleted event received for filter id: ${event.filterId}.',
520+
'[AppBloc] SavedHeadlineFilterDeleted event received for filter id: '
521+
'${event.filterId}.',
514522
);
515523
// This method modifies the preferences in memory and then delegates the
516524
// persistence and final state update to the AppUserContentPreferencesChanged event.
517525
if (state.userContentPreferences == null) {
518526
_logger.warning(
519-
'[AppBloc] Skipping SavedFilterDeleted: UserContentPreferences not loaded.',
527+
'[AppBloc] Skipping SavedHeadlineFilterDeleted: '
528+
'UserContentPreferences not loaded.',
520529
);
521530
return;
522531
}
523532

524-
final updatedSavedFilters = List<SavedFilter>.from(
525-
state.userContentPreferences!.savedFilters,
533+
final updatedSavedFilters = List<SavedHeadlineFilter>.from(
534+
state.userContentPreferences!.savedHeadlineFilters,
526535
)..removeWhere((f) => f.id == event.filterId);
527536

528537
if (updatedSavedFilters.length ==
529-
state.userContentPreferences!.savedFilters.length) {
538+
state.userContentPreferences!.savedHeadlineFilters.length) {
530539
_logger.warning(
531-
'[AppBloc] Skipping SavedFilterDeleted: Filter with id ${event.filterId} not found.',
540+
'[AppBloc] Skipping SavedHeadlineFilterDeleted: Filter with id '
541+
'${event.filterId} not found.',
532542
);
533543
return;
534544
}
535545

536546
final updatedPreferences = state.userContentPreferences!.copyWith(
537-
savedFilters: updatedSavedFilters,
547+
savedHeadlineFilters: updatedSavedFilters,
538548
);
539549

540550
add(AppUserContentPreferencesChanged(preferences: updatedPreferences));
541551
}
542552

543-
/// Handles reordering the list of saved filters.
544-
Future<void> _onSavedFiltersReordered(
545-
SavedFiltersReordered event,
553+
/// Handles reordering the list of saved headline filters.
554+
Future<void> _onSavedHeadlineFiltersReordered(
555+
SavedHeadlineFiltersReordered event,
546556
Emitter<AppState> emit,
547557
) async {
548-
_logger.fine('[AppBloc] SavedFiltersReordered event received.');
558+
_logger.fine('[AppBloc] SavedHeadlineFiltersReordered event received.');
549559
// This method modifies the preferences in memory and then delegates the
550560
// persistence and final state update to the AppUserContentPreferencesChanged event.
551561
if (state.userContentPreferences == null) {
552562
_logger.warning(
553-
'[AppBloc] Skipping SavedFiltersReordered: UserContentPreferences not loaded.',
563+
'[AppBloc] Skipping SavedHeadlineFiltersReordered: '
564+
'UserContentPreferences not loaded.',
554565
);
555566
return;
556567
}
557568

558569
final updatedPreferences = state.userContentPreferences!.copyWith(
559-
savedFilters: event.reorderedFilters,
570+
savedHeadlineFilters: event.reorderedFilters,
560571
);
561572

562573
add(AppUserContentPreferencesChanged(preferences: updatedPreferences));

0 commit comments

Comments
 (0)