@@ -34,9 +34,12 @@ class HeadlineDetailsPage extends StatefulWidget {
3434}
3535
3636class _HeadlineDetailsPageState extends State <HeadlineDetailsPage > {
37+ final _metadataChipsScrollController = ScrollController ();
38+
3739 @override
3840 void initState () {
3941 super .initState ();
42+ _metadataChipsScrollController.addListener (() => setState (() {}));
4043 if (widget.initialHeadline != null ) {
4144 context.read <HeadlineDetailsBloc >().add (
4245 HeadlineProvided (widget.initialHeadline! ),
@@ -51,6 +54,12 @@ class _HeadlineDetailsPageState extends State<HeadlineDetailsPage> {
5154 }
5255 }
5356
57+ @override
58+ void dispose () {
59+ _metadataChipsScrollController.dispose ();
60+ super .dispose ();
61+ }
62+
5463 @override
5564 Widget build (BuildContext context) {
5665 final l10n = AppLocalizationsX (context).l10n;
@@ -384,21 +393,62 @@ class _HeadlineDetailsPageState extends State<HeadlineDetailsPage> {
384393 sliver: SliverToBoxAdapter (
385394 child: SizedBox (
386395 height: 36 ,
387- child: BlocBuilder < HeadlineDetailsBloc , HeadlineDetailsState > (
388- builder: (context, state ) {
396+ child: LayoutBuilder (
397+ builder: (context, constraints ) {
389398 final chips = _buildMetadataChips (
390399 context,
391400 headline,
392401 onEntityChipTap,
393402 );
394- return ListView .separated (
403+
404+ final listView = ListView .separated (
405+ controller: _metadataChipsScrollController,
395406 scrollDirection: Axis .horizontal,
396407 itemCount: chips.length,
397408 separatorBuilder: (context, index) =>
398409 const SizedBox (width: AppSpacing .sm),
399410 itemBuilder: (context, index) => chips[index],
400411 clipBehavior: Clip .none,
401412 );
413+
414+ // Determine if the fade should be shown based on scroll position.
415+ var showStartFade = false ;
416+ var showEndFade = false ;
417+ if (_metadataChipsScrollController.hasClients &&
418+ _metadataChipsScrollController.position.maxScrollExtent >
419+ 0 ) {
420+ final pixels = _metadataChipsScrollController.position.pixels;
421+ final minScroll =
422+ _metadataChipsScrollController.position.minScrollExtent;
423+ final maxScroll =
424+ _metadataChipsScrollController.position.maxScrollExtent;
425+
426+ if (pixels > minScroll) showStartFade = true ;
427+ if (pixels < maxScroll) showEndFade = true ;
428+ }
429+
430+ final colors = < Color > [
431+ if (showStartFade) Colors .transparent,
432+ Colors .black,
433+ Colors .black,
434+ if (showEndFade) Colors .transparent,
435+ ];
436+
437+ final stops = < double > [
438+ if (showStartFade) 0.0 ,
439+ if (showStartFade) 0.05 else 0.0 ,
440+ if (showEndFade) 0.95 else 1.0 ,
441+ if (showEndFade) 1.0 ,
442+ ];
443+
444+ return ShaderMask (
445+ shaderCallback: (bounds) => LinearGradient (
446+ colors: colors,
447+ stops: stops,
448+ ).createShader (bounds),
449+ blendMode: BlendMode .dstIn,
450+ child: listView,
451+ );
402452 },
403453 ),
404454 ),
@@ -583,26 +633,12 @@ class _HeadlineDetailsPageState extends State<HeadlineDetailsPage> {
583633 void Function (ContentType type, String id) onEntityChipTap,
584634 ) {
585635 final theme = Theme .of (context);
586- final textTheme = theme.textTheme;
587- final colorScheme = theme.colorScheme;
588- final chipLabelStyle = textTheme.labelMedium? .copyWith (
589- color: colorScheme.onSecondaryContainer,
590- fontWeight: FontWeight .w600,
591- );
592- final chipBackgroundColor = colorScheme.secondaryContainer.withOpacity (0.6 );
593- final chipAvatarColor = colorScheme.onSecondaryContainer;
594- const chipAvatarSize = 18.0 ;
595-
596- Widget buildChip ({
597- required IconData icon,
598- required String label,
599- required VoidCallback onPressed,
600- }) {
636+
637+ Widget buildChip ({required String label, required VoidCallback onPressed}) {
601638 return ActionChip (
602- avatar: Icon (icon, size: chipAvatarSize, color: chipAvatarColor),
603639 label: Text (label),
604- labelStyle : chipLabelStyle,
605- backgroundColor : chipBackgroundColor ,
640+ // Use default theme styles for a cleaner look.
641+ labelStyle : theme.textTheme.labelMedium ,
606642 onPressed: onPressed,
607643 visualDensity: VisualDensity .compact,
608644 padding: const EdgeInsets .symmetric (
@@ -618,18 +654,15 @@ class _HeadlineDetailsPageState extends State<HeadlineDetailsPage> {
618654
619655 return [
620656 buildChip (
621- icon: Icons .source_outlined,
622657 label: headline.source.name,
623658 onPressed: () =>
624659 onEntityChipTap (ContentType .source, headline.source.id),
625660 ),
626661 buildChip (
627- icon: Icons .category_outlined,
628662 label: headline.topic.name,
629663 onPressed: () => onEntityChipTap (ContentType .topic, headline.topic.id),
630664 ),
631665 buildChip (
632- icon: Icons .location_city_outlined,
633666 label: headline.eventCountry.name,
634667 onPressed: () =>
635668 onEntityChipTap (ContentType .country, headline.eventCountry.id),
0 commit comments