@@ -9,6 +9,7 @@ import 'package:ht_main/entity_details/models/entity_type.dart';
9
9
import 'package:ht_main/l10n/l10n.dart' ;
10
10
import 'package:ht_main/router/routes.dart' ; // Added
11
11
import 'package:ht_main/shared/constants/app_spacing.dart' ;
12
+ import 'package:ht_main/shared/services/feed_injector_service.dart' ; // Added
12
13
import 'package:ht_main/shared/widgets/widgets.dart' ;
13
14
import 'package:ht_shared/ht_shared.dart' ;
14
15
@@ -40,21 +41,27 @@ class EntityDetailsPage extends StatelessWidget {
40
41
41
42
@override
42
43
Widget build (BuildContext context) {
43
- return BlocProvider (
44
- create:
45
- (context) => EntityDetailsBloc (
46
- headlinesRepository: context.read <HtDataRepository <Headline >>(),
47
- categoryRepository: context.read <HtDataRepository <Category >>(),
48
- sourceRepository: context.read <HtDataRepository <Source >>(),
49
- accountBloc: context.read <AccountBloc >(),
50
- )..add (
51
- EntityDetailsLoadRequested (
52
- entityId: args.entityId,
53
- entityType: args.entityType,
54
- entity: args.entity,
55
- ),
44
+ return BlocProvider <EntityDetailsBloc >( // Explicitly type BlocProvider
45
+ create: (context) {
46
+ final feedInjectorService = FeedInjectorService ();
47
+ final entityDetailsBloc = EntityDetailsBloc (
48
+ headlinesRepository: context.read <HtDataRepository <Headline >>(),
49
+ categoryRepository: context.read <HtDataRepository <Category >>(),
50
+ sourceRepository: context.read <HtDataRepository <Source >>(),
51
+ accountBloc: context.read <AccountBloc >(),
52
+ appBloc: context.read <AppBloc >(),
53
+ feedInjectorService: feedInjectorService,
54
+ );
55
+ entityDetailsBloc.add (
56
+ EntityDetailsLoadRequested (
57
+ entityId: args.entityId,
58
+ entityType: args.entityType,
59
+ entity: args.entity,
56
60
),
57
- child: EntityDetailsView (args: args), // Pass args
61
+ );
62
+ return entityDetailsBloc;
63
+ },
64
+ child: EntityDetailsView (args: args),
58
65
);
59
66
}
60
67
}
@@ -112,17 +119,17 @@ class _EntityDetailsViewState extends State<EntityDetailsView> {
112
119
if (state.status == EntityDetailsStatus .initial ||
113
120
(state.status == EntityDetailsStatus .loading &&
114
121
state.entity == null )) {
115
- return const LoadingStateWidget (
116
- icon: Icons .info_outline, // Or a more specific icon
117
- headline: 'Loading Details' , // Replace with l10n
118
- subheadline: 'Please wait...' , // Replace with l10n
122
+ return LoadingStateWidget (
123
+ icon: Icons .info_outline,
124
+ headline: l10n.headlineDetailsLoadingHeadline , // Used generic loading
125
+ subheadline: l10n.pleaseWait , // Used generic please wait
119
126
);
120
127
}
121
128
122
129
if (state.status == EntityDetailsStatus .failure &&
123
130
state.entity == null ) {
124
131
return FailureStateWidget (
125
- message: state.errorMessage ?? 'Failed to load details.' , // l10n
132
+ message: state.errorMessage ?? l10n.unknownError , // Used generic error
126
133
onRetry:
127
134
() => context.read <EntityDetailsBloc >().add (
128
135
EntityDetailsLoadRequested (
@@ -248,7 +255,7 @@ class _EntityDetailsViewState extends State<EntityDetailsView> {
248
255
),
249
256
const SizedBox (height: AppSpacing .lg),
250
257
],
251
- if (state.headlines .isNotEmpty ||
258
+ if (state.feedItems .isNotEmpty || // Changed
252
259
state.headlinesStatus ==
253
260
EntityHeadlinesStatus .loadingMore) ...[
254
261
Text (
@@ -261,12 +268,11 @@ class _EntityDetailsViewState extends State<EntityDetailsView> {
261
268
),
262
269
),
263
270
),
264
- if (state.headlines .isEmpty &&
271
+ if (state.feedItems .isEmpty && // Changed
265
272
state.headlinesStatus != EntityHeadlinesStatus .initial &&
266
273
state.headlinesStatus != EntityHeadlinesStatus .loadingMore &&
267
274
state.status == EntityDetailsStatus .success)
268
275
SliverFillRemaining (
269
- // Use SliverFillRemaining for empty state
270
276
child: Center (
271
277
child: Text (
272
278
l10n.noHeadlinesFoundMessage,
@@ -278,89 +284,161 @@ class _EntityDetailsViewState extends State<EntityDetailsView> {
278
284
SliverList (
279
285
delegate: SliverChildBuilderDelegate (
280
286
(context, index) {
281
- if (index >= state.headlines .length) {
282
- return state.hasMoreHeadlines &&
287
+ if (index >= state.feedItems .length) { // Changed
288
+ return state.hasMoreHeadlines && // hasMoreHeadlines still refers to original headlines
283
289
state.headlinesStatus ==
284
290
EntityHeadlinesStatus .loadingMore
285
291
? const Center (
286
- child: Padding (
287
- padding: EdgeInsets .all (AppSpacing .md),
288
- child: CircularProgressIndicator (),
289
- ),
290
- )
292
+ child: Padding (
293
+ padding: EdgeInsets .all (AppSpacing .md),
294
+ child: CircularProgressIndicator (),
295
+ ),
296
+ )
291
297
: const SizedBox .shrink ();
292
298
}
293
- final headline = state.headlines[index];
294
- final imageStyle =
295
- context
296
- .watch <AppBloc >()
297
- .state
298
- .settings
299
- .feedPreferences
300
- .headlineImageStyle;
299
+ final item = state.feedItems[index]; // Changed
301
300
302
- Widget tile;
303
- switch (imageStyle) {
304
- case HeadlineImageStyle .hidden:
305
- tile = HeadlineTileTextOnly (
306
- headline: headline,
307
- onHeadlineTap:
308
- () => context.pushNamed (
309
- Routes
310
- .globalArticleDetailsName, // Use new global route
311
- pathParameters: {'id' : headline.id},
312
- extra: headline,
313
- ),
314
- currentContextEntityType: state.entityType,
315
- currentContextEntityId:
316
- state.entity is Category
317
- ? (state.entity as Category ).id
318
- : state.entity is Source
319
- ? (state.entity as Source ).id
320
- : null ,
321
- );
322
- case HeadlineImageStyle .smallThumbnail:
323
- tile = HeadlineTileImageStart (
324
- headline: headline,
325
- onHeadlineTap:
326
- () => context.pushNamed (
327
- Routes
328
- .globalArticleDetailsName, // Use new global route
329
- pathParameters: {'id' : headline.id},
330
- extra: headline,
301
+ if (item is Headline ) {
302
+ final imageStyle = context
303
+ .watch <AppBloc >()
304
+ .state
305
+ .settings
306
+ .feedPreferences
307
+ .headlineImageStyle;
308
+ Widget tile;
309
+ switch (imageStyle) {
310
+ case HeadlineImageStyle .hidden:
311
+ tile = HeadlineTileTextOnly (
312
+ headline: item,
313
+ onHeadlineTap: () => context.pushNamed (
314
+ Routes .globalArticleDetailsName,
315
+ pathParameters: {'id' : item.id},
316
+ extra: item,
317
+ ),
318
+ currentContextEntityType: state.entityType,
319
+ currentContextEntityId: state.entity is Category
320
+ ? (state.entity as Category ).id
321
+ : state.entity is Source
322
+ ? (state.entity as Source ).id
323
+ : null ,
324
+ );
325
+ break ;
326
+ case HeadlineImageStyle .smallThumbnail:
327
+ tile = HeadlineTileImageStart (
328
+ headline: item,
329
+ onHeadlineTap: () => context.pushNamed (
330
+ Routes .globalArticleDetailsName,
331
+ pathParameters: {'id' : item.id},
332
+ extra: item,
333
+ ),
334
+ currentContextEntityType: state.entityType,
335
+ currentContextEntityId: state.entity is Category
336
+ ? (state.entity as Category ).id
337
+ : state.entity is Source
338
+ ? (state.entity as Source ).id
339
+ : null ,
340
+ );
341
+ break ;
342
+ case HeadlineImageStyle .largeThumbnail:
343
+ tile = HeadlineTileImageTop (
344
+ headline: item,
345
+ onHeadlineTap: () => context.pushNamed (
346
+ Routes .globalArticleDetailsName,
347
+ pathParameters: {'id' : item.id},
348
+ extra: item,
349
+ ),
350
+ currentContextEntityType: state.entityType,
351
+ currentContextEntityId: state.entity is Category
352
+ ? (state.entity as Category ).id
353
+ : state.entity is Source
354
+ ? (state.entity as Source ).id
355
+ : null ,
356
+ );
357
+ break ;
358
+ }
359
+ return tile;
360
+ } else if (item is Ad ) {
361
+ return Card (
362
+ margin: const EdgeInsets .symmetric (
363
+ horizontal: AppSpacing .paddingMedium,
364
+ vertical: AppSpacing .xs,
365
+ ),
366
+ color: theme.colorScheme.surfaceContainerHighest,
367
+ child: Padding (
368
+ padding: const EdgeInsets .all (AppSpacing .md),
369
+ child: Column (
370
+ children: [
371
+ if (item.imageUrl.isNotEmpty)
372
+ Image .network (
373
+ item.imageUrl,
374
+ height: 100 ,
375
+ errorBuilder: (ctx, err, st) =>
376
+ const Icon (Icons .broken_image, size: 50 ),
377
+ ),
378
+ const SizedBox (height: AppSpacing .sm),
379
+ Text (
380
+ 'Placeholder Ad: ${item .adType ?.name ?? 'Generic' }' ,
381
+ style: theme.textTheme.titleSmall,
331
382
),
332
- currentContextEntityType: state.entityType,
333
- currentContextEntityId:
334
- state.entity is Category
335
- ? (state.entity as Category ).id
336
- : state.entity is Source
337
- ? (state.entity as Source ).id
338
- : null ,
339
- );
340
- case HeadlineImageStyle .largeThumbnail:
341
- tile = HeadlineTileImageTop (
342
- headline: headline,
343
- onHeadlineTap:
344
- () => context.pushNamed (
345
- Routes
346
- .globalArticleDetailsName, // Use new global route
347
- pathParameters: {'id' : headline.id},
348
- extra: headline,
383
+ Text (
384
+ 'Placement: ${item .placement ?.name ?? 'Default' }' ,
385
+ style: theme.textTheme.bodySmall,
349
386
),
350
- currentContextEntityType: state.entityType,
351
- currentContextEntityId:
352
- state.entity is Category
353
- ? (state.entity as Category ).id
354
- : state.entity is Source
355
- ? (state.entity as Source ).id
356
- : null ,
357
- );
387
+ ],
388
+ ),
389
+ ),
390
+ );
391
+ } else if (item is AccountAction ) {
392
+ return Card (
393
+ margin: const EdgeInsets .symmetric (
394
+ horizontal: AppSpacing .paddingMedium,
395
+ vertical: AppSpacing .xs,
396
+ ),
397
+ color: theme.colorScheme.secondaryContainer,
398
+ child: ListTile (
399
+ leading: Icon (
400
+ item.accountActionType == AccountActionType .linkAccount
401
+ ? Icons .link
402
+ : Icons .upgrade,
403
+ color: theme.colorScheme.onSecondaryContainer,
404
+ ),
405
+ title: Text (
406
+ item.title,
407
+ style: theme.textTheme.titleMedium? .copyWith (
408
+ color: theme.colorScheme.onSecondaryContainer,
409
+ fontWeight: FontWeight .bold,
410
+ ),
411
+ ),
412
+ subtitle: item.description != null
413
+ ? Text (
414
+ item.description! ,
415
+ style: theme.textTheme.bodySmall? .copyWith (
416
+ color: theme.colorScheme.onSecondaryContainer.withOpacity (0.8 ),
417
+ ),
418
+ )
419
+ : null ,
420
+ trailing: item.callToActionText != null
421
+ ? ElevatedButton (
422
+ style: ElevatedButton .styleFrom (
423
+ backgroundColor: theme.colorScheme.secondary,
424
+ foregroundColor: theme.colorScheme.onSecondary,
425
+ ),
426
+ onPressed: () {
427
+ if (item.callToActionUrl != null ) {
428
+ context.push (item.callToActionUrl! );
429
+ }
430
+ },
431
+ child: Text (item.callToActionText! ),
432
+ )
433
+ : null ,
434
+ isThreeLine: item.description != null && item.description! .length > 50 ,
435
+ ),
436
+ );
358
437
}
359
- return tile;
438
+ return const SizedBox . shrink (); // Should not happen
360
439
},
361
- childCount:
362
- state.headlines.length +
363
- (state.hasMoreHeadlines &&
440
+ childCount: state.feedItems.length + // Changed
441
+ (state.hasMoreHeadlines && // hasMoreHeadlines still refers to original headlines
364
442
state.headlinesStatus ==
365
443
EntityHeadlinesStatus .loadingMore
366
444
? 1
@@ -369,7 +447,7 @@ class _EntityDetailsViewState extends State<EntityDetailsView> {
369
447
),
370
448
// Error display for headline loading specifically
371
449
if (state.headlinesStatus == EntityHeadlinesStatus .failure &&
372
- state.headlines .isNotEmpty)
450
+ state.feedItems .isNotEmpty) // Changed
373
451
SliverToBoxAdapter (
374
452
child: Padding (
375
453
padding: const EdgeInsets .all (AppSpacing .md),
0 commit comments