Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
d1161ad
build(deps): update core package reference
fulleni Nov 26, 2025
f72e3bf
refactor(app): align app events with core model updates
fulleni Nov 26, 2025
abeda00
refactor(app): adapt app state to new settings and remote config models
fulleni Nov 26, 2025
2c8bda7
refactor(app): align AppBloc with new settings and remote config stru…
fulleni Nov 26, 2025
9b0b097
refactor(app): update App widget for new settings and payload models
fulleni Nov 26, 2025
904068b
refactor(app): align AppInitializationPage with AppSettings model rename
fulleni Nov 26, 2025
d1fc38d
refactor(app): adapt AppInitializer to new settings and remote config
fulleni Nov 26, 2025
5ae1e2b
refactor(app): update demo initializer to use AppSettings model
fulleni Nov 26, 2025
2dd4bca
refactor(app): update demo migration to use AppSettings model
fulleni Nov 26, 2025
2bd7007
refactor(app): align bootstrap with new models and remote config
fulleni Nov 26, 2025
3620f53
fix(models): update InitializationSuccess field type
fulleni Nov 26, 2025
c023289
fix(router): update import and remove hide clause
fulleni Nov 26, 2025
1e23191
refactor(account): align saved headlines page with new settings model
fulleni Nov 26, 2025
fe99991
refactor(account): adapt notification center to new payload model
fulleni Nov 26, 2025
e96444f
refactor(account): update notification list item for new payload
fulleni Nov 26, 2025
7bc1cfd
refactor(entity_details): align details page with new models
fulleni Nov 26, 2025
a9d364c
refactor(entity_details): align details bloc with new models
fulleni Nov 26, 2025
bf19399
style: remove unnecessary hide directive in import
fulleni Nov 26, 2025
61e390a
refactor(ads): update demo_ad_provider for FeedItemImageStyle
fulleni Nov 26, 2025
a850b93
refactor(ads): update demo_ad_provider for FeedItemImageStyle
fulleni Nov 26, 2025
f22bfaa
refactor(ads): update admob_ad_provider for FeedItemImageStyle
fulleni Nov 26, 2025
c7efa74
refactor(ads): update ad_service for new AdConfig and remove in-artic…
fulleni Nov 26, 2025
3747669
refactor(ads): update interstitial_ad_manager for new AdConfig structure
fulleni Nov 26, 2025
5d83469
refactor(ads): update admob_inline_ad_widget for core model changes
fulleni Nov 26, 2025
511e092
refactor(ads): update demo_banner_ad_widget for core model changes
fulleni Nov 26, 2025
07fc17a
refactor(ads): update demo_native_ad_widget for FeedItemImageStyle
fulleni Nov 26, 2025
40923d6
refactor(ads): adapt feed_ad_loader_widget to new config structure
fulleni Nov 26, 2025
8c49e37
chore: delete absolete file
fulleni Nov 26, 2025
e87777f
refactor(notifications): adapt Firebase service to new PushNotificat…
fulleni Nov 27, 2025
ebb0367
refactor(notifications): adapt OneSignal service to new PushNotificat…
fulleni Nov 27, 2025
3da2f22
refactor(app): update RemoteConfig access path in AppInitializer
fulleni Nov 27, 2025
378444d
refactor(shared): adapt ContentLimitationService to new UserLimitsConfig
fulleni Nov 27, 2025
bc8ec91
refactor(feed_decorators): update RemoteConfig path in FeedDecoratorS…
fulleni Nov 27, 2025
415ccda
refactor(feed_decorators): update RemoteConfig path in FeedDecoratorL…
fulleni Nov 27, 2025
070099a
refactor(headlines-feed): adapt HeadlinesFeedBloc to new core models
fulleni Nov 27, 2025
4fd82a9
refactor(entity_details): improve remote config handling and naming
fulleni Nov 27, 2025
fd1cc62
refactor(entity-details): update ad placeholder injection method calls
fulleni Nov 27, 2025
01c51dd
refactor(headlines-feed): update image style and ad config handling
fulleni Nov 27, 2025
8060736
refactor(headlines-feed): improve context handling and remote config …
fulleni Nov 27, 2025
6d54d72
fix(data): correct model name for AppSettings API
fulleni Nov 27, 2025
a3653b7
refactor(settings): update settings bloc and repository
fulleni Nov 27, 2025
8d57ba8
refactor(settings): improve naming consistency in settings bloc
fulleni Nov 27, 2025
4ff51dd
refactor(settings): update state class and remove user context
fulleni Nov 27, 2025
7cd8661
refactor(settings): update feed settings page
fulleni Nov 27, 2025
14abb02
fix(settings): update font settings page state checks and event dispa…
fulleni Nov 27, 2025
0c632d5
refactor(settings): update language settings page
fulleni Nov 27, 2025
57c4dd9
fix(settings): adjust theme settings page for potential null state
fulleni Nov 27, 2025
a0abe56
chore: rename an event
fulleni Nov 27, 2025
ff762d2
style: clean comments
fulleni Nov 27, 2025
ab8cc41
feat(l10n): add new AR and EN translations for app settings and actions
fulleni Nov 27, 2025
d8f1bf5
build(l10n): generation
fulleni Nov 27, 2025
8359cfd
feat(ads): implement dual counters for internal and external navigation
fulleni Nov 27, 2025
2e05521
feat(ads): add countdown timer to demo interstitial ad
fulleni Nov 27, 2025
1465d56
refactor(feed): pivot HeadlineTapHandler to open external URLs
fulleni Nov 27, 2025
3cb6ab5
feat(settings): add event for feed item click behavior
fulleni Nov 27, 2025
25baf34
feat(settings): implement logic for changing click behavior
fulleni Nov 27, 2025
2620e2f
feat(feed): add ellipsis menu for headline actions
fulleni Nov 27, 2025
ba005b8
feat(feed): integrate headline actions into feed tiles
fulleni Nov 27, 2025
4e7f22d
refactor(account): update SavedHeadlinesPage to use external navigation
fulleni Nov 27, 2025
c336138
refactor(search): align search results with external navigation
fulleni Nov 27, 2025
9812a57
refactor(navigation): remove all routes for HeadlineDetailsPage
fulleni Nov 27, 2025
5cdc380
refactor(notifications): update notification tap to use HeadlineTapHa…
fulleni Nov 27, 2025
bd516dd
refactor(navigation): remove route name constants for HeadlineDetails
fulleni Nov 27, 2025
1f981c0
feat(settings): add feed item click behavior preference
fulleni Nov 27, 2025
efdd875
chore: deleted absolete feature
fulleni Nov 27, 2025
0520d02
style: format
fulleni Nov 27, 2025
d2ae959
refactor(entity_details): move ad-related imports to a constant
fulleni Nov 27, 2025
93179f4
feat(HeadlineTapHandler): add method for handling system notification…
fulleni Nov 27, 2025
4d97b76
feat(shared): add headline actions bottom sheet
fulleni Nov 27, 2025
d481734
style: format
fulleni Nov 27, 2025
956707b
refactor(app): improve push notification handling and code structure
fulleni Nov 27, 2025
bf2d5c7
feat(HeadlineTapHandler): add notification ID handling
fulleni Nov 27, 2025
edfce76
feat(app): add AppNotificationTapped event
fulleni Nov 27, 2025
593d487
feat(app): handle notification tap in app bloc
fulleni Nov 27, 2025
ca7abaf
feat(notification): handle notificationId in system notifications
fulleni Nov 27, 2025
9534e11
style: format
fulleni Nov 27, 2025
3156182
refactor(app): ignore already read notifications
fulleni Nov 27, 2025
d4095b8
fix(app): check if mounted before adding events in subscriptions
fulleni Nov 27, 2025
75cbf6e
ci(analysis_options): ignore asynchronous use of context
fulleni Nov 27, 2025
f63c06f
style: format
fulleni Nov 27, 2025
b045ff8
fix(feed_core): improve loading dialog resilience in HeadlineTapHandler
fulleni Nov 27, 2025
0dfd936
refactor(account): remove duplicate trailing button code
fulleni Nov 27, 2025
481a6f4
feat(headline): replace date with actions sheet
fulleni Nov 27, 2025
c3064bd
refactor(headlines-feed): remove unnecessary variable and simplify code
fulleni Nov 27, 2025
343d9df
refactor(headline_tiles): remove trailing widget from headline tiles
fulleni Nov 27, 2025
ba6b10c
fix(widgets): wrap user avatar in Directionality to resolve layout is…
fulleni Nov 27, 2025
5eca8b1
feat(feed_decorators): improve layout and accessibility for popup menu
fulleni Nov 27, 2025
ad0fea9
fix(notification_indicator): adjust indicator position and remove red…
fulleni Nov 27, 2025
35666d4
feat(account): change login icon to sync for better anonymity emphasis
fulleni Nov 27, 2025
4cb665e
refactor(localization): adjust feed display titles in settings
fulleni Nov 27, 2025
6b83981
build(l10n): generate
fulleni Nov 27, 2025
12105be
fix(firebase): prevent Firebase initialization in demo environment
fulleni Nov 27, 2025
14e4224
refactor(ads): remove interstitial ad dialog button
fulleni Nov 27, 2025
9b6f046
feat(app): check for unread notifications on app start
fulleni Nov 27, 2025
60d662f
feat(deps): add flutter_inappwebview dependency
fulleni Nov 27, 2025
2975ffa
feat(shared): add InAppBrowser widget for modal web page display
fulleni Nov 27, 2025
707f237
refactor(shared): replace url_launcher with InAppBrowser for internal…
fulleni Nov 27, 2025
6ebade7
fix(widgets): correct row height misalignment in headline source
fulleni Nov 27, 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
1 change: 1 addition & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ analyzer:
flutter_style_todos: ignore
lines_longer_than_80_chars: ignore
prefer_asserts_with_message: ignore
use_build_context_synchronously: ignore
use_if_null_to_convert_nulls_to_bools: ignore
include: package:very_good_analysis/analysis_options.7.0.0.yaml
linter:
Expand Down
2 changes: 1 addition & 1 deletion lib/account/bloc/in_app_notification_center_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class InAppNotificationCenterState extends Equatable {
breakingNewsCursor ?? Object(),
digestHasMore,
digestCursor ?? Object(),
error ?? Object(), // Include error in props, handle nullability
error ?? Object(),
];

/// Creates a copy of this state with the given fields replaced with the new
Expand Down
10 changes: 4 additions & 6 deletions lib/account/view/account_page.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:core/core.dart' hide AppStatus;
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
Expand Down Expand Up @@ -44,20 +44,18 @@ class AccountPage extends StatelessWidget {
// This declutters the main content card and follows common UI patterns.
if (isAnonymous)
IconButton(
icon: const Icon(Icons.login),
icon: const Icon(Icons.sync),
tooltip: l10n.anonymousLimitButton,
onPressed: () => context.goNamed(Routes.accountLinkingName),
)
else
IconButton(
icon: const Icon(Icons.logout), // Non-directional icon for logout
icon: const Icon(Icons.logout),
tooltip: l10n.accountSignOutTile,
onPressed: () =>
context.read<AppBloc>().add(const AppLogoutRequested()),
),
const SizedBox(
width: AppSpacing.lg,
), // Consistent right padding for the AppBar actions
const SizedBox(width: AppSpacing.lg),
],
),
body: SingleChildScrollView(
Expand Down
23 changes: 8 additions & 15 deletions lib/account/view/in_app_notification_center_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/account/bloc/in_app_notification_center_bloc.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/account/widgets/in_app_notification_list_item.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/ads/services/interstitial_ad_manager.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/router/routes.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/shared/widgets/feed_core/headline_tap_handler.dart';
import 'package:ui_kit/ui_kit.dart';

/// {@template in_app_notification_center_page}
Expand Down Expand Up @@ -295,19 +293,14 @@ class _NotificationListState extends State<_NotificationList> {
);

final payload = notification.payload;
final contentType = payload.data['contentType'] as String?;
final id = payload.data['headlineId'] as String?;
final contentType = payload.contentType;
final contentId = payload.contentId;

if (contentType == 'headline' && id != null) {
await context
.read<InterstitialAdManager>()
.onPotentialAdTrigger();

if (!context.mounted) return;

await context.pushNamed(
Routes.globalArticleDetailsName,
pathParameters: {'id': id},
if (contentType == ContentType.headline && contentId.isNotEmpty) {
// Use the handler to fetch the headline by ID and open it.
await HeadlineTapHandler.handleHeadlineTapById(
context,
contentId,
);
}
},
Expand Down
60 changes: 11 additions & 49 deletions lib/account/view/saved_headlines_page.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/ads/services/interstitial_ad_manager.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/app/models/app_life_cycle_status.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/router/routes.dart';
import 'package:flutter_news_app_mobile_client_full_source_code/shared/widgets/feed_core/feed_core.dart';
import 'package:go_router/go_router.dart';
import 'package:ui_kit/ui_kit.dart';
Expand All @@ -25,7 +23,6 @@ class SavedHeadlinesPage extends StatelessWidget {
final l10n = AppLocalizationsX(context).l10n;
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final colorScheme = theme.colorScheme;

return Scaffold(
appBar: AppBar(
Expand Down Expand Up @@ -74,21 +71,6 @@ class SavedHeadlinesPage extends StatelessWidget {
);
}

Future<void> onHeadlineTap(Headline headline) async {
// Await for the ad to be shown and dismissed.
await context.read<InterstitialAdManager>().onPotentialAdTrigger();

// Check if the widget is still in the tree before navigating.
if (!context.mounted) return;

// Proceed with navigation after the ad is closed.
await context.pushNamed(
Routes.accountArticleDetailsName,
pathParameters: {'id': headline.id},
extra: headline,
);
}

return ListView.separated(
padding: const EdgeInsets.symmetric(
vertical: AppSpacing.paddingSmall,
Expand All @@ -102,48 +84,28 @@ class SavedHeadlinesPage extends StatelessWidget {
itemBuilder: (context, index) {
final headline = savedHeadlines[index];
final imageStyle =
appState.settings?.feedPreferences.headlineImageStyle ??
HeadlineImageStyle.smallThumbnail;

final trailingButton = IconButton(
icon: Icon(Icons.delete_outline, color: colorScheme.error),
tooltip: l10n.headlineDetailsRemoveFromSavedTooltip,
onPressed: () {
final updatedSavedHeadlines = List<Headline>.from(
savedHeadlines,
)..removeWhere((h) => h.id == headline.id);

final updatedPreferences = userContentPreferences.copyWith(
savedHeadlines: updatedSavedHeadlines,
);

context.read<AppBloc>().add(
AppUserContentPreferencesChanged(
preferences: updatedPreferences,
),
);
},
);
appState.settings?.feedSettings.feedItemImageStyle ??
FeedItemImageStyle.smallThumbnail;

Widget tile;
switch (imageStyle) {
case HeadlineImageStyle.hidden:
case FeedItemImageStyle.hidden:
tile = HeadlineTileTextOnly(
headline: headline,
onHeadlineTap: () => onHeadlineTap(headline),
trailing: trailingButton,
onHeadlineTap: () =>
HeadlineTapHandler.handleHeadlineTap(context, headline),
);
case HeadlineImageStyle.smallThumbnail:
case FeedItemImageStyle.smallThumbnail:
tile = HeadlineTileImageStart(
headline: headline,
onHeadlineTap: () => onHeadlineTap(headline),
trailing: trailingButton,
onHeadlineTap: () =>
HeadlineTapHandler.handleHeadlineTap(context, headline),
);
case HeadlineImageStyle.largeThumbnail:
case FeedItemImageStyle.largeThumbnail:
tile = HeadlineTileImageTop(
headline: headline,
onHeadlineTap: () => onHeadlineTap(headline),
trailing: trailingButton,
onHeadlineTap: () =>
HeadlineTapHandler.handleHeadlineTap(context, headline),
);
}
return tile;
Expand Down
20 changes: 4 additions & 16 deletions lib/account/widgets/in_app_notification_list_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:ui_kit/ui_kit.dart';
/// {@template in_app_notification_list_item}
/// A widget that displays a single in-app notification in a list.
///
/// It shows the notification's title, body, and the time it was received.
/// It shows the notification's title and the time it was received.
/// Unread notifications are visually distinguished with a leading dot and
/// a bolder title.
/// {@endtemplate}
Expand Down Expand Up @@ -50,23 +50,11 @@ class InAppNotificationListItem extends StatelessWidget {
fontWeight: isUnread ? FontWeight.bold : FontWeight.normal,
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
notification.payload.body,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: AppSpacing.xs),
Text(
timeago.format(notification.createdAt),
style: textTheme.bodySmall,
),
],
subtitle: Text(
timeago.format(notification.createdAt),
style: textTheme.bodySmall,
),
onTap: onTap,
isThreeLine: true,
);
}
}
8 changes: 4 additions & 4 deletions lib/ads/providers/ad_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ abstract class AdProvider {
/// The [adPlatformIdentifiers] provides the platform-specific ad unit IDs.
/// The [adId] is the specific identifier for the ad slot (e.g., native ad unit ID).
/// The [adThemeStyle] provides UI-agnostic theme properties for ad styling.
/// The [headlineImageStyle] provides the user's preference for feed layout,
/// The [feedItemImageStyle] provides the user's preference for feed layout,
/// which can be used to request an appropriately sized ad.
Future<NativeAd?> loadNativeAd({
required AdPlatformIdentifiers adPlatformIdentifiers,
required String? adId,
required AdThemeStyle adThemeStyle,
HeadlineImageStyle? headlineImageStyle,
FeedItemImageStyle? feedItemImageStyle,
});

/// Loads an inline banner ad.
Expand All @@ -48,13 +48,13 @@ abstract class AdProvider {
/// The [adPlatformIdentifiers] provides the platform-specific ad unit IDs.
/// The [adId] is the specific identifier for the ad slot (e.g., banner ad unit ID).
/// The [adThemeStyle] provides UI-agnostic theme properties for ad styling.
/// The [headlineImageStyle] provides the user's preference for feed layout,
/// The [feedItemImageStyle] provides the user's preference for feed layout,
/// which can be used to request an appropriately sized ad.
Future<BannerAd?> loadBannerAd({
required AdPlatformIdentifiers adPlatformIdentifiers,
required String? adId,
required AdThemeStyle adThemeStyle,
HeadlineImageStyle? headlineImageStyle,
FeedItemImageStyle? feedItemImageStyle,
});

/// Loads a full-screen interstitial ad.
Expand Down
8 changes: 4 additions & 4 deletions lib/ads/providers/admob_ad_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class AdMobAdProvider implements AdProvider {
required AdPlatformIdentifiers adPlatformIdentifiers,
required String? adId,
required AdThemeStyle adThemeStyle,
HeadlineImageStyle? headlineImageStyle,
FeedItemImageStyle? feedItemImageStyle,
}) async {
_logger.info('AdMobAdProvider: loadNativeAd called for adId: $adId');
if (adId == null || adId.isEmpty) {
Expand All @@ -65,7 +65,7 @@ class AdMobAdProvider implements AdProvider {
);

// Determine the template type based on the user's feed style preference.
final templateType = headlineImageStyle == HeadlineImageStyle.largeThumbnail
final templateType = feedItemImageStyle == FeedItemImageStyle.largeThumbnail
? NativeAdTemplateType.medium
: NativeAdTemplateType.small;

Expand Down Expand Up @@ -149,7 +149,7 @@ class AdMobAdProvider implements AdProvider {
required AdPlatformIdentifiers adPlatformIdentifiers,
required String? adId,
required AdThemeStyle adThemeStyle,
HeadlineImageStyle? headlineImageStyle,
FeedItemImageStyle? feedItemImageStyle,
}) async {
_logger.info('AdMobAdProvider: loadBannerAd called for adId: $adId');
if (adId == null || adId.isEmpty) {
Expand All @@ -164,7 +164,7 @@ class AdMobAdProvider implements AdProvider {
);

// Determine the ad size based on the user's feed style preference.
final adSize = headlineImageStyle == HeadlineImageStyle.largeThumbnail
final adSize = feedItemImageStyle == FeedItemImageStyle.largeThumbnail
? admob.AdSize.mediumRectangle
: admob.AdSize.banner;

Expand Down
6 changes: 3 additions & 3 deletions lib/ads/providers/demo_ad_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class DemoAdProvider implements AdProvider {
required AdPlatformIdentifiers adPlatformIdentifiers,
required String? adId,
required AdThemeStyle adThemeStyle,
HeadlineImageStyle? headlineImageStyle,
FeedItemImageStyle? feedItemImageStyle,
}) async {
_logger.info('Simulating native ad load for demo environment.');
// Simulate a delay for loading.
Expand All @@ -45,7 +45,7 @@ class DemoAdProvider implements AdProvider {
id: _uuid.v4(),
provider: AdPlatformType.demo,
adObject: Object(),
templateType: headlineImageStyle == HeadlineImageStyle.largeThumbnail
templateType: feedItemImageStyle == FeedItemImageStyle.largeThumbnail
? NativeAdTemplateType.medium
: NativeAdTemplateType.small,
);
Expand All @@ -56,7 +56,7 @@ class DemoAdProvider implements AdProvider {
required AdPlatformIdentifiers adPlatformIdentifiers,
required String? adId,
required AdThemeStyle adThemeStyle,
HeadlineImageStyle? headlineImageStyle,
FeedItemImageStyle? feedItemImageStyle,
}) async {
_logger.info('Simulating banner ad load for demo environment.');
// Simulate a delay for loading.
Expand Down
Loading
Loading