@@ -33,13 +33,56 @@ class ContentCollectionDecoratorWidget extends StatelessWidget {
3333 /// List of IDs of sources the user is currently following.
3434 final List <String > followedSourceIds;
3535
36+ @override
37+ Widget build (BuildContext context) {
38+ return _ContentCollectionView (
39+ item: item,
40+ onFollowToggle: onFollowToggle,
41+ followedTopicIds: followedTopicIds,
42+ followedSourceIds: followedSourceIds,
43+ );
44+ }
45+ }
46+
47+ class _ContentCollectionView extends StatefulWidget {
48+ const _ContentCollectionView ({
49+ required this .item,
50+ required this .onFollowToggle,
51+ required this .followedTopicIds,
52+ required this .followedSourceIds,
53+ });
54+
55+ final ContentCollectionItem item;
56+ final ValueSetter <FeedItem > onFollowToggle;
57+ final List <String > followedTopicIds;
58+ final List <String > followedSourceIds;
59+
60+ @override
61+ State <_ContentCollectionView > createState () => _ContentCollectionViewState ();
62+ }
63+
64+ class _ContentCollectionViewState extends State <_ContentCollectionView > {
65+ final _scrollController = ScrollController ();
66+
67+ @override
68+ void initState () {
69+ super .initState ();
70+ _scrollController.addListener (() => setState (() {}));
71+ }
72+
73+ @override
74+ void dispose () {
75+ _scrollController.dispose ();
76+ super .dispose ();
77+ }
78+
3679 @override
3780 Widget build (BuildContext context) {
3881 final l10n = AppLocalizationsX (context).l10n;
3982 final theme = Theme .of (context);
4083
4184 String getTitle () {
42- switch (item.decoratorType) {
85+ switch (widget. item.decoratorType) {
4386 case FeedDecoratorType .suggestedTopics:
4487 return l10n.suggestedTopicsTitle;
4588 case FeedDecoratorType .suggestedSources:
@@ -50,7 +93,7 @@ class ContentCollectionDecoratorWidget extends StatelessWidget {
5093 case FeedDecoratorType .upgrade:
5194 case FeedDecoratorType .rateApp:
5295 case FeedDecoratorType .enableNotifications:
53- return item.title;
96+ return widget. item.title;
5497 }
5598 }
5699
@@ -78,21 +121,67 @@ class ContentCollectionDecoratorWidget extends StatelessWidget {
78121 const SizedBox (height: AppSpacing .sm),
79122 SizedBox (
80123 height: 180 ,
81- child: ListView .builder (
82- scrollDirection: Axis .horizontal,
83- itemCount: item.items.length,
84- padding: const EdgeInsets .symmetric (horizontal: AppSpacing .md),
85- itemBuilder: (context, index) {
86- final suggestion = item.items[index];
87- final isFollowing =
88- (suggestion is Topic &&
89- followedTopicIds.contains (suggestion.id)) ||
90- (suggestion is Source &&
91- followedSourceIds.contains (suggestion.id));
92- return SuggestionItemWidget (
93- item: suggestion,
94- onFollowToggle: onFollowToggle,
95- isFollowing: isFollowing,
124+ child: LayoutBuilder (
125+ builder: (context, constraints) {
126+ final listView = ListView .builder (
127+ controller: _scrollController,
128+ scrollDirection: Axis .horizontal,
129+ itemCount: widget.item.items.length,
130+ padding: const EdgeInsets .symmetric (
131+ horizontal: AppSpacing .lg,
132+ ),
133+ itemBuilder: (context, index) {
134+ final suggestion = widget.item.items[index];
135+ final isFollowing =
136+ (suggestion is Topic &&
137+ widget.followedTopicIds.contains (suggestion.id)) ||
138+ (suggestion is Source &&
139+ widget.followedSourceIds.contains (suggestion.id));
140+ return SuggestionItemWidget (
141+ item: suggestion,
142+ onFollowToggle: widget.onFollowToggle,
143+ isFollowing: isFollowing,
144+ );
145+ },
146+ );
147+
148+ var showStartFade = false ;
149+ var showEndFade = false ;
150+ if (_scrollController.hasClients &&
151+ _scrollController.position.maxScrollExtent > 0 ) {
152+ final pixels = _scrollController.position.pixels;
153+ final minScroll = _scrollController.position.minScrollExtent;
154+ final maxScroll = _scrollController.position.maxScrollExtent;
155+
156+ if (pixels > minScroll) {
157+ showStartFade = true ;
158+ }
159+ if (pixels < maxScroll) {
160+ showEndFade = true ;
161+ }
162+ }
163+
164+ final colors = < Color > [
165+ if (showStartFade) Colors .transparent,
166+ Colors .black,
167+ Colors .black,
168+ if (showEndFade) Colors .transparent,
169+ ];
170+
171+ final stops = < double > [
172+ if (showStartFade) 0.0 ,
173+ if (showStartFade) 0.05 else 0.0 ,
174+ if (showEndFade) 0.95 else 1.0 ,
175+ if (showEndFade) 1.0 ,
176+ ];
177+
178+ return ShaderMask (
179+ shaderCallback: (bounds) => LinearGradient (
180+ colors: colors,
181+ stops: stops,
182+ ).createShader (bounds),
183+ blendMode: BlendMode .dstIn,
184+ child: listView,
96185 );
97186 },
98187 ),
0 commit comments