Skip to content

Commit a3e1bca

Browse files
authored
Merge pull request #220 from flutter-news-app-full-source-code/refactor/app-identity-pivot-into-a-news-aggregator
Refactor/app identity pivot into a news aggregator
2 parents ae35c9c + 6ebade7 commit a3e1bca

File tree

77 files changed

+1224
-2127
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1224
-2127
lines changed

analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ analyzer:
99
flutter_style_todos: ignore
1010
lines_longer_than_80_chars: ignore
1111
prefer_asserts_with_message: ignore
12+
use_build_context_synchronously: ignore
1213
use_if_null_to_convert_nulls_to_bools: ignore
1314
include: package:very_good_analysis/analysis_options.7.0.0.yaml
1415
linter:

lib/account/bloc/in_app_notification_center_state.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class InAppNotificationCenterState extends Equatable {
9292
breakingNewsCursor ?? Object(),
9393
digestHasMore,
9494
digestCursor ?? Object(),
95-
error ?? Object(), // Include error in props, handle nullability
95+
error ?? Object(),
9696
];
9797

9898
/// Creates a copy of this state with the given fields replaced with the new

lib/account/view/account_page.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import 'package:core/core.dart' hide AppStatus;
1+
import 'package:core/core.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_bloc/flutter_bloc.dart';
44
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
@@ -44,20 +44,18 @@ class AccountPage extends StatelessWidget {
4444
// This declutters the main content card and follows common UI patterns.
4545
if (isAnonymous)
4646
IconButton(
47-
icon: const Icon(Icons.login),
47+
icon: const Icon(Icons.sync),
4848
tooltip: l10n.anonymousLimitButton,
4949
onPressed: () => context.goNamed(Routes.accountLinkingName),
5050
)
5151
else
5252
IconButton(
53-
icon: const Icon(Icons.logout), // Non-directional icon for logout
53+
icon: const Icon(Icons.logout),
5454
tooltip: l10n.accountSignOutTile,
5555
onPressed: () =>
5656
context.read<AppBloc>().add(const AppLogoutRequested()),
5757
),
58-
const SizedBox(
59-
width: AppSpacing.lg,
60-
), // Consistent right padding for the AppBar actions
58+
const SizedBox(width: AppSpacing.lg),
6159
],
6260
),
6361
body: SingleChildScrollView(

lib/account/view/in_app_notification_center_page.dart

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ import 'package:flutter/material.dart';
33
import 'package:flutter_bloc/flutter_bloc.dart';
44
import 'package:flutter_news_app_mobile_client_full_source_code/account/bloc/in_app_notification_center_bloc.dart';
55
import 'package:flutter_news_app_mobile_client_full_source_code/account/widgets/in_app_notification_list_item.dart';
6-
import 'package:flutter_news_app_mobile_client_full_source_code/ads/services/interstitial_ad_manager.dart';
76
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
8-
import 'package:flutter_news_app_mobile_client_full_source_code/router/routes.dart';
9-
import 'package:go_router/go_router.dart';
7+
import 'package:flutter_news_app_mobile_client_full_source_code/shared/widgets/feed_core/headline_tap_handler.dart';
108
import 'package:ui_kit/ui_kit.dart';
119

1210
/// {@template in_app_notification_center_page}
@@ -295,19 +293,14 @@ class _NotificationListState extends State<_NotificationList> {
295293
);
296294

297295
final payload = notification.payload;
298-
final contentType = payload.data['contentType'] as String?;
299-
final id = payload.data['headlineId'] as String?;
296+
final contentType = payload.contentType;
297+
final contentId = payload.contentId;
300298

301-
if (contentType == 'headline' && id != null) {
302-
await context
303-
.read<InterstitialAdManager>()
304-
.onPotentialAdTrigger();
305-
306-
if (!context.mounted) return;
307-
308-
await context.pushNamed(
309-
Routes.globalArticleDetailsName,
310-
pathParameters: {'id': id},
299+
if (contentType == ContentType.headline && contentId.isNotEmpty) {
300+
// Use the handler to fetch the headline by ID and open it.
301+
await HeadlineTapHandler.handleHeadlineTapById(
302+
context,
303+
contentId,
311304
);
312305
}
313306
},

lib/account/view/saved_headlines_page.dart

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import 'package:core/core.dart';
22
import 'package:flutter/material.dart';
33
import 'package:flutter_bloc/flutter_bloc.dart';
4-
import 'package:flutter_news_app_mobile_client_full_source_code/ads/services/interstitial_ad_manager.dart';
54
import 'package:flutter_news_app_mobile_client_full_source_code/app/bloc/app_bloc.dart';
65
import 'package:flutter_news_app_mobile_client_full_source_code/app/models/app_life_cycle_status.dart';
76
import 'package:flutter_news_app_mobile_client_full_source_code/l10n/l10n.dart';
8-
import 'package:flutter_news_app_mobile_client_full_source_code/router/routes.dart';
97
import 'package:flutter_news_app_mobile_client_full_source_code/shared/widgets/feed_core/feed_core.dart';
108
import 'package:go_router/go_router.dart';
119
import 'package:ui_kit/ui_kit.dart';
@@ -25,7 +23,6 @@ class SavedHeadlinesPage extends StatelessWidget {
2523
final l10n = AppLocalizationsX(context).l10n;
2624
final theme = Theme.of(context);
2725
final textTheme = theme.textTheme;
28-
final colorScheme = theme.colorScheme;
2926

3027
return Scaffold(
3128
appBar: AppBar(
@@ -74,21 +71,6 @@ class SavedHeadlinesPage extends StatelessWidget {
7471
);
7572
}
7673

77-
Future<void> onHeadlineTap(Headline headline) async {
78-
// Await for the ad to be shown and dismissed.
79-
await context.read<InterstitialAdManager>().onPotentialAdTrigger();
80-
81-
// Check if the widget is still in the tree before navigating.
82-
if (!context.mounted) return;
83-
84-
// Proceed with navigation after the ad is closed.
85-
await context.pushNamed(
86-
Routes.accountArticleDetailsName,
87-
pathParameters: {'id': headline.id},
88-
extra: headline,
89-
);
90-
}
91-
9274
return ListView.separated(
9375
padding: const EdgeInsets.symmetric(
9476
vertical: AppSpacing.paddingSmall,
@@ -102,48 +84,28 @@ class SavedHeadlinesPage extends StatelessWidget {
10284
itemBuilder: (context, index) {
10385
final headline = savedHeadlines[index];
10486
final imageStyle =
105-
appState.settings?.feedPreferences.headlineImageStyle ??
106-
HeadlineImageStyle.smallThumbnail;
107-
108-
final trailingButton = IconButton(
109-
icon: Icon(Icons.delete_outline, color: colorScheme.error),
110-
tooltip: l10n.headlineDetailsRemoveFromSavedTooltip,
111-
onPressed: () {
112-
final updatedSavedHeadlines = List<Headline>.from(
113-
savedHeadlines,
114-
)..removeWhere((h) => h.id == headline.id);
115-
116-
final updatedPreferences = userContentPreferences.copyWith(
117-
savedHeadlines: updatedSavedHeadlines,
118-
);
119-
120-
context.read<AppBloc>().add(
121-
AppUserContentPreferencesChanged(
122-
preferences: updatedPreferences,
123-
),
124-
);
125-
},
126-
);
87+
appState.settings?.feedSettings.feedItemImageStyle ??
88+
FeedItemImageStyle.smallThumbnail;
12789

12890
Widget tile;
12991
switch (imageStyle) {
130-
case HeadlineImageStyle.hidden:
92+
case FeedItemImageStyle.hidden:
13193
tile = HeadlineTileTextOnly(
13294
headline: headline,
133-
onHeadlineTap: () => onHeadlineTap(headline),
134-
trailing: trailingButton,
95+
onHeadlineTap: () =>
96+
HeadlineTapHandler.handleHeadlineTap(context, headline),
13597
);
136-
case HeadlineImageStyle.smallThumbnail:
98+
case FeedItemImageStyle.smallThumbnail:
13799
tile = HeadlineTileImageStart(
138100
headline: headline,
139-
onHeadlineTap: () => onHeadlineTap(headline),
140-
trailing: trailingButton,
101+
onHeadlineTap: () =>
102+
HeadlineTapHandler.handleHeadlineTap(context, headline),
141103
);
142-
case HeadlineImageStyle.largeThumbnail:
104+
case FeedItemImageStyle.largeThumbnail:
143105
tile = HeadlineTileImageTop(
144106
headline: headline,
145-
onHeadlineTap: () => onHeadlineTap(headline),
146-
trailing: trailingButton,
107+
onHeadlineTap: () =>
108+
HeadlineTapHandler.handleHeadlineTap(context, headline),
147109
);
148110
}
149111
return tile;

lib/account/widgets/in_app_notification_list_item.dart

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'package:ui_kit/ui_kit.dart';
66
/// {@template in_app_notification_list_item}
77
/// A widget that displays a single in-app notification in a list.
88
///
9-
/// It shows the notification's title, body, and the time it was received.
9+
/// It shows the notification's title and the time it was received.
1010
/// Unread notifications are visually distinguished with a leading dot and
1111
/// a bolder title.
1212
/// {@endtemplate}
@@ -50,23 +50,11 @@ class InAppNotificationListItem extends StatelessWidget {
5050
fontWeight: isUnread ? FontWeight.bold : FontWeight.normal,
5151
),
5252
),
53-
subtitle: Column(
54-
crossAxisAlignment: CrossAxisAlignment.start,
55-
children: [
56-
Text(
57-
notification.payload.body,
58-
maxLines: 2,
59-
overflow: TextOverflow.ellipsis,
60-
),
61-
const SizedBox(height: AppSpacing.xs),
62-
Text(
63-
timeago.format(notification.createdAt),
64-
style: textTheme.bodySmall,
65-
),
66-
],
53+
subtitle: Text(
54+
timeago.format(notification.createdAt),
55+
style: textTheme.bodySmall,
6756
),
6857
onTap: onTap,
69-
isThreeLine: true,
7058
);
7159
}
7260
}

lib/ads/providers/ad_provider.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ abstract class AdProvider {
3030
/// The [adPlatformIdentifiers] provides the platform-specific ad unit IDs.
3131
/// The [adId] is the specific identifier for the ad slot (e.g., native ad unit ID).
3232
/// The [adThemeStyle] provides UI-agnostic theme properties for ad styling.
33-
/// The [headlineImageStyle] provides the user's preference for feed layout,
33+
/// The [feedItemImageStyle] provides the user's preference for feed layout,
3434
/// which can be used to request an appropriately sized ad.
3535
Future<NativeAd?> loadNativeAd({
3636
required AdPlatformIdentifiers adPlatformIdentifiers,
3737
required String? adId,
3838
required AdThemeStyle adThemeStyle,
39-
HeadlineImageStyle? headlineImageStyle,
39+
FeedItemImageStyle? feedItemImageStyle,
4040
});
4141

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

6060
/// Loads a full-screen interstitial ad.

lib/ads/providers/admob_ad_provider.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class AdMobAdProvider implements AdProvider {
5050
required AdPlatformIdentifiers adPlatformIdentifiers,
5151
required String? adId,
5252
required AdThemeStyle adThemeStyle,
53-
HeadlineImageStyle? headlineImageStyle,
53+
FeedItemImageStyle? feedItemImageStyle,
5454
}) async {
5555
_logger.info('AdMobAdProvider: loadNativeAd called for adId: $adId');
5656
if (adId == null || adId.isEmpty) {
@@ -65,7 +65,7 @@ class AdMobAdProvider implements AdProvider {
6565
);
6666

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

@@ -149,7 +149,7 @@ class AdMobAdProvider implements AdProvider {
149149
required AdPlatformIdentifiers adPlatformIdentifiers,
150150
required String? adId,
151151
required AdThemeStyle adThemeStyle,
152-
HeadlineImageStyle? headlineImageStyle,
152+
FeedItemImageStyle? feedItemImageStyle,
153153
}) async {
154154
_logger.info('AdMobAdProvider: loadBannerAd called for adId: $adId');
155155
if (adId == null || adId.isEmpty) {
@@ -164,7 +164,7 @@ class AdMobAdProvider implements AdProvider {
164164
);
165165

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

lib/ads/providers/demo_ad_provider.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class DemoAdProvider implements AdProvider {
3535
required AdPlatformIdentifiers adPlatformIdentifiers,
3636
required String? adId,
3737
required AdThemeStyle adThemeStyle,
38-
HeadlineImageStyle? headlineImageStyle,
38+
FeedItemImageStyle? feedItemImageStyle,
3939
}) async {
4040
_logger.info('Simulating native ad load for demo environment.');
4141
// Simulate a delay for loading.
@@ -45,7 +45,7 @@ class DemoAdProvider implements AdProvider {
4545
id: _uuid.v4(),
4646
provider: AdPlatformType.demo,
4747
adObject: Object(),
48-
templateType: headlineImageStyle == HeadlineImageStyle.largeThumbnail
48+
templateType: feedItemImageStyle == FeedItemImageStyle.largeThumbnail
4949
? NativeAdTemplateType.medium
5050
: NativeAdTemplateType.small,
5151
);
@@ -56,7 +56,7 @@ class DemoAdProvider implements AdProvider {
5656
required AdPlatformIdentifiers adPlatformIdentifiers,
5757
required String? adId,
5858
required AdThemeStyle adThemeStyle,
59-
HeadlineImageStyle? headlineImageStyle,
59+
FeedItemImageStyle? feedItemImageStyle,
6060
}) async {
6161
_logger.info('Simulating banner ad load for demo environment.');
6262
// Simulate a delay for loading.

0 commit comments

Comments
 (0)