@@ -16,8 +16,7 @@ import 'package:ht_main/l10n/l10n.dart';
16
16
import 'package:ht_main/router/routes.dart' ;
17
17
import 'package:ht_main/shared/constants/app_spacing.dart' ;
18
18
import 'package:ht_main/shared/shared.dart' ; // Imports new headline tiles
19
- // Adjusted imports to only include what's necessary after country removal
20
- import 'package:ht_shared/ht_shared.dart' show Category, Headline, HeadlineImageStyle, SearchModelType, Source;
19
+ import 'package:ht_shared/ht_shared.dart' ; // Changed to general import
21
20
22
21
/// Page widget responsible for providing the BLoC for the headlines search feature.
23
22
class HeadlinesSearchPage extends StatelessWidget {
@@ -240,7 +239,7 @@ class _HeadlinesSearchViewState extends State<_HeadlinesSearchView> {
240
239
'Searching ${state .selectedModelType .displayName .toLowerCase ()}...' ,
241
240
),
242
241
HeadlinesSearchSuccess (
243
- results: final results,
242
+ items : final items, // Changed from results: final results
244
243
hasMore: final hasMore,
245
244
errorMessage: final errorMessage,
246
245
lastSearchTerm: final lastSearchTerm,
@@ -256,75 +255,156 @@ class _HeadlinesSearchViewState extends State<_HeadlinesSearchView> {
256
255
),
257
256
),
258
257
)
259
- : results .isEmpty
258
+ : items .isEmpty
260
259
? FailureStateWidget (
261
260
message:
262
261
'${l10n .headlinesSearchNoResultsHeadline } for "$lastSearchTerm " in ${resultsModelType .displayName .toLowerCase ()}.\n ${l10n .headlinesSearchNoResultsSubheadline }' ,
263
262
)
264
263
: ListView .separated (
265
264
controller: _scrollController,
266
265
padding: const EdgeInsets .all (AppSpacing .paddingMedium),
267
- itemCount: hasMore ? results.length + 1 : results.length,
268
- separatorBuilder:
269
- (context, index) => const SizedBox (height: AppSpacing .md),
266
+ itemCount: hasMore ? items.length + 1 : items.length,
267
+ separatorBuilder: (context, index) {
268
+ // Add a bit more space if the next item is an Ad or AccountAction
269
+ if (index < items.length - 1 ) {
270
+ final currentItem = items[index];
271
+ final nextItem = items[index+ 1 ];
272
+ if ((currentItem is Headline && (nextItem is Ad || nextItem is AccountAction )) ||
273
+ ((currentItem is Ad || currentItem is AccountAction ) && nextItem is Headline )) {
274
+ return const SizedBox (height: AppSpacing .md);
275
+ }
276
+ }
277
+ return const SizedBox (height: AppSpacing .md);
278
+ },
270
279
itemBuilder: (context, index) {
271
- if (index >= results .length) {
280
+ if (index >= items .length) {
272
281
return const Padding (
273
282
padding: EdgeInsets .symmetric (vertical: AppSpacing .lg),
274
283
child: Center (child: CircularProgressIndicator ()),
275
284
);
276
285
}
277
- final item = results[index];
278
- // The switch is now exhaustive for the remaining SearchModelType values
279
- switch (resultsModelType) {
280
- case SearchModelType .headline:
281
- final headline = item as Headline ;
282
- final imageStyle =
283
- context
284
- .watch <AppBloc >()
285
- .state
286
- .settings
287
- .feedPreferences
288
- .headlineImageStyle;
289
- Widget tile;
290
- switch (imageStyle) {
291
- case HeadlineImageStyle .hidden:
292
- tile = HeadlineTileTextOnly (
293
- headline: headline,
294
- onHeadlineTap:
295
- () => context.goNamed (
296
- Routes .searchArticleDetailsName,
297
- pathParameters: {'id' : headline.id},
298
- extra: headline,
299
- ),
300
- );
301
- case HeadlineImageStyle .smallThumbnail:
302
- tile = HeadlineTileImageStart (
303
- headline: headline,
304
- onHeadlineTap:
305
- () => context.goNamed (
306
- Routes .searchArticleDetailsName,
307
- pathParameters: {'id' : headline.id},
308
- extra: headline,
286
+ final feedItem = items[index];
287
+
288
+ if (feedItem is Headline ) {
289
+ final imageStyle = context
290
+ .watch <AppBloc >()
291
+ .state
292
+ .settings
293
+ .feedPreferences
294
+ .headlineImageStyle;
295
+ Widget tile;
296
+ switch (imageStyle) {
297
+ case HeadlineImageStyle .hidden:
298
+ tile = HeadlineTileTextOnly (
299
+ headline: feedItem,
300
+ onHeadlineTap: () => context.goNamed (
301
+ Routes .searchArticleDetailsName,
302
+ pathParameters: {'id' : feedItem.id},
303
+ extra: feedItem,
304
+ ),
305
+ );
306
+ break ;
307
+ case HeadlineImageStyle .smallThumbnail:
308
+ tile = HeadlineTileImageStart (
309
+ headline: feedItem,
310
+ onHeadlineTap: () => context.goNamed (
311
+ Routes .searchArticleDetailsName,
312
+ pathParameters: {'id' : feedItem.id},
313
+ extra: feedItem,
314
+ ),
315
+ );
316
+ break ;
317
+ case HeadlineImageStyle .largeThumbnail:
318
+ tile = HeadlineTileImageTop (
319
+ headline: feedItem,
320
+ onHeadlineTap: () => context.goNamed (
321
+ Routes .searchArticleDetailsName,
322
+ pathParameters: {'id' : feedItem.id},
323
+ extra: feedItem,
324
+ ),
325
+ );
326
+ break ;
327
+ }
328
+ return tile;
329
+ } else if (feedItem is Category ) {
330
+ return CategoryItemWidget (category: feedItem);
331
+ } else if (feedItem is Source ) {
332
+ return SourceItemWidget (source: feedItem);
333
+ } else if (feedItem is Ad ) {
334
+ // Placeholder UI for Ad
335
+ return Card (
336
+ margin: const EdgeInsets .symmetric (vertical: AppSpacing .xs),
337
+ color: colorScheme.surfaceContainerHighest,
338
+ child: Padding (
339
+ padding: const EdgeInsets .all (AppSpacing .md),
340
+ child: Column (
341
+ children: [
342
+ if (feedItem.imageUrl.isNotEmpty)
343
+ Image .network (
344
+ feedItem.imageUrl,
345
+ height: 100 ,
346
+ errorBuilder: (ctx, err, st) =>
347
+ const Icon (Icons .broken_image, size: 50 ),
348
+ ),
349
+ const SizedBox (height: AppSpacing .sm),
350
+ Text (
351
+ 'Placeholder Ad: ${feedItem .adType ?.name ?? 'Generic' }' ,
352
+ style: theme.textTheme.titleSmall,
353
+ ),
354
+ Text (
355
+ 'Placement: ${feedItem .placement ?.name ?? 'Default' }' ,
356
+ style: theme.textTheme.bodySmall,
357
+ ),
358
+ ],
359
+ ),
360
+ ),
361
+ );
362
+ } else if (feedItem is AccountAction ) {
363
+ // Placeholder UI for AccountAction
364
+ return Card (
365
+ margin: const EdgeInsets .symmetric (vertical: AppSpacing .xs),
366
+ color: colorScheme.secondaryContainer,
367
+ child: ListTile (
368
+ leading: Icon (
369
+ feedItem.accountActionType == AccountActionType .linkAccount
370
+ ? Icons .link
371
+ : Icons .upgrade,
372
+ color: colorScheme.onSecondaryContainer,
373
+ ),
374
+ title: Text (
375
+ feedItem.title,
376
+ style: theme.textTheme.titleMedium? .copyWith (
377
+ color: colorScheme.onSecondaryContainer,
378
+ fontWeight: FontWeight .bold,
379
+ ),
380
+ ),
381
+ subtitle: feedItem.description != null
382
+ ? Text (
383
+ feedItem.description! ,
384
+ style: theme.textTheme.bodySmall? .copyWith (
385
+ color: colorScheme.onSecondaryContainer.withOpacity (0.8 ),
309
386
),
310
- );
311
- case HeadlineImageStyle .largeThumbnail:
312
- tile = HeadlineTileImageTop (
313
- headline: headline,
314
- onHeadlineTap:
315
- () => context.goNamed (
316
- Routes .searchArticleDetailsName,
317
- pathParameters: {'id' : headline.id},
318
- extra: headline,
387
+ )
388
+ : null ,
389
+ trailing: feedItem.callToActionText != null
390
+ ? ElevatedButton (
391
+ style: ElevatedButton .styleFrom (
392
+ backgroundColor: colorScheme.secondary,
393
+ foregroundColor: colorScheme.onSecondary,
319
394
),
320
- );
321
- }
322
- return tile;
323
- case SearchModelType .category:
324
- return CategoryItemWidget (category: item as Category );
325
- case SearchModelType .source:
326
- return SourceItemWidget (source: item as Source );
395
+ onPressed: () {
396
+ if (feedItem.callToActionUrl != null ) {
397
+ context.push (feedItem.callToActionUrl! );
398
+ }
399
+ },
400
+ child: Text (feedItem.callToActionText! ),
401
+ )
402
+ : null ,
403
+ isThreeLine: feedItem.description != null && feedItem.description! .length > 50 ,
404
+ ),
405
+ );
327
406
}
407
+ return const SizedBox .shrink (); // Should not happen
328
408
},
329
409
),
330
410
HeadlinesSearchFailure (
0 commit comments