@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
55import 'package:freecodecamp/extensions/i18n_extension.dart' ;
66import 'package:freecodecamp/models/news/tutorial_model.dart' ;
77import 'package:freecodecamp/ui/views/news/news-feed/news_feed_viewmodel.dart' ;
8+ import 'package:freecodecamp/ui/views/news/widgets/tag_widget.dart' ;
89import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart' ;
910import 'package:stacked/stacked.dart' ;
1011
@@ -82,8 +83,8 @@ class NewsFeedView extends StatelessWidget {
8283 fetchNextPage: fetchNextPage,
8384 separatorBuilder: (context, int i) => const Divider (
8485 color: Color .fromRGBO (0x2A , 0x2A , 0x40 , 1 ),
85- thickness: 3 ,
86- height: 3 ,
86+ thickness: 1 ,
87+ height: 1 ,
8788 ),
8889 builderDelegate: PagedChildBuilderDelegate <Tutorial >(
8990 itemBuilder: (context, tutorial, index) => Container (
@@ -126,143 +127,137 @@ class NewsFeedView extends StatelessWidget {
126127 // );
127128 // }
128129
129- InkWell tutorialThumbnailBuilder (Tutorial tutorial, NewsFeedViewModel model) {
130+ Widget tutorialThumbnailBuilder (Tutorial tutorial, NewsFeedViewModel model) {
130131 return InkWell (
131132 key: Key (tutorial.id),
132133 splashColor: Colors .transparent,
133134 onTap: () {
134135 model.navigateTo (tutorial.id, tutorial.slug);
135136 },
136137 child: Padding (
137- padding: const EdgeInsets .only (bottom: 32.0 ),
138- child: thumbnailView (tutorial, model),
139- ),
140- );
141- }
142-
143- Column thumbnailView (Tutorial tutorial, NewsFeedViewModel model) {
144- return Column (
145- children: [
146- Container (
147- color: const Color .fromRGBO (0x2A , 0x2A , 0x40 , 1 ),
148- child: AspectRatio (
149- aspectRatio: 16 / 9 ,
150- child: tutorial.featureImage == null
151- ? Image .asset (
152- 'assets/images/freecodecamp-banner.png' ,
153- fit: BoxFit .cover,
154- )
155- : CachedNetworkImage (
156- imageUrl: tutorial.featureImage! ,
157- errorWidget: (context, url, error) {
158- log ('Error loading image: $url - $tutorial .featureImage $error ' );
159- return const Icon (Icons .error);
160- },
161- imageBuilder: (context, imageProvider) => Container (
162- decoration: BoxDecoration (
163- image: DecorationImage (
164- image: imageProvider,
165- fit: BoxFit .cover,
166- ),
167- ),
168- ),
169- ),
170- ),
171- ),
172- Align (
173- alignment: Alignment .centerLeft,
174- child: Padding (
175- padding: const EdgeInsets .only (left: 16 , right: 16 ),
176- child: Wrap (
177- children: [
178- for (int j = 0 ; j < tutorial.tagNames.length && j < 3 ; j++ )
179- tutorial.tagNames[j]
180- ],
181- ),
182- ),
183- ),
184- Container (
185- padding: const EdgeInsets .only (left: 16 , right: 16 , top: 8 ),
186- child: tutorialHeader (tutorial, model),
187- )
188- ],
189- );
190- }
191-
192- Widget tutorialHeader (Tutorial tutorial, NewsFeedViewModel model) {
193- return Column (
194- children: [
195- Row (
196- children: [
197- Expanded (
198- child: Text (
199- tutorial.title,
200- maxLines: 2 ,
201- style: const TextStyle (
202- fontSize: 20 ,
203- overflow: TextOverflow .ellipsis,
204- height: 1.5 ,
205- ),
206- ),
207- ),
208- ],
209- ),
210- Row (
138+ padding: const EdgeInsets .symmetric (horizontal: 16 , vertical: 12 ),
139+ child: Column (
140+ crossAxisAlignment: CrossAxisAlignment .start,
211141 children: [
212- Padding (
213- padding: const EdgeInsets .only (right: 16 , top: 16 ),
214- child: InkWell (
215- onTap: () {
216- model.navigateToAuthor (tutorial.authorSlug);
217- },
142+ ClipRRect (
143+ borderRadius: BorderRadius .circular (12 ),
144+ child: AspectRatio (
145+ aspectRatio: 16 / 9 ,
218146 child: Container (
219- width: 45 ,
220- height: 45 ,
221- color: const Color .fromRGBO (0x2A , 0x2A , 0x40 , 1 ),
222- child: tutorial.profileImage == null
147+ color: const Color (0xFF2A2A40 ),
148+ child: tutorial.featureImage == null
223149 ? Image .asset (
224- 'assets/images/placeholder-profile-img.png' ,
225- width: 45 ,
226- height: 45 ,
150+ 'assets/images/freecodecamp-banner.png' ,
227151 fit: BoxFit .cover,
228152 )
229153 : CachedNetworkImage (
230- imageUrl: tutorial.profileImage as String ,
231- errorWidget: (context, url, error) => Image .asset (
232- 'assets/images/placeholder-profile-img.png' ,
233- width: 45 ,
234- height: 45 ,
235- fit: BoxFit .cover,
236- ),
237- imageBuilder: (context, imageProvider) => Container (
238- decoration: BoxDecoration (
239- image: DecorationImage (
240- image: imageProvider,
241- fit: BoxFit .cover,
242- ),
243- ),
154+ imageUrl: tutorial.featureImage! ,
155+ fit: BoxFit .cover,
156+ placeholder: (context, url) => Container (
157+ color: const Color (0xFF2A2A40 ),
244158 ),
159+ errorWidget: (context, url, error) {
160+ log ('Error loading image: $url - ${tutorial .featureImage } $error ' );
161+ return Image .asset (
162+ 'assets/images/freecodecamp-banner.png' ,
163+ fit: BoxFit .cover,
164+ );
165+ },
245166 ),
246167 ),
247168 ),
248169 ),
249- Column (
250- crossAxisAlignment: CrossAxisAlignment .start,
170+ const SizedBox (height: 8 ),
171+ if (tutorial.rawTags.isNotEmpty)
172+ Padding (
173+ padding: const EdgeInsets .only (bottom: 6 ),
174+ child: Wrap (
175+ spacing: 0 ,
176+ runSpacing: 4 ,
177+ children: [
178+ for (int j = 0 ; j < tutorial.rawTags.length && j < 3 ; j++ )
179+ TagButton (
180+ tagName: tutorial.rawTags[j]['name' ],
181+ tagSlug: tutorial.rawTags[j]['slug' ] ?? tutorial.rawTags[j]['id' ],
182+ compact: true ,
183+ key: UniqueKey (),
184+ ),
185+ ],
186+ ),
187+ ),
188+ Text (
189+ tutorial.title,
190+ maxLines: 2 ,
191+ overflow: TextOverflow .ellipsis,
192+ style: const TextStyle (
193+ fontSize: 16 ,
194+ fontWeight: FontWeight .w600,
195+ height: 1.25 ,
196+ ),
197+ ),
198+ const SizedBox (height: 8 ),
199+ Row (
251200 children: [
252- Padding (
253- padding: const EdgeInsets .only (bottom: 10 , top: 16 ),
254- child: Text (
255- tutorial.authorName.toUpperCase (),
201+ GestureDetector (
202+ onTap: () => model.navigateToAuthor (tutorial.authorSlug),
203+ child: ClipRRect (
204+ borderRadius: BorderRadius .circular (12 ),
205+ child: SizedBox (
206+ width: 24 ,
207+ height: 24 ,
208+ child: tutorial.profileImage == null
209+ ? Image .asset (
210+ 'assets/images/placeholder-profile-img.png' ,
211+ fit: BoxFit .cover,
212+ )
213+ : CachedNetworkImage (
214+ imageUrl: tutorial.profileImage! ,
215+ fit: BoxFit .cover,
216+ placeholder: (context, url) => Container (
217+ color: const Color (0xFF2A2A40 ),
218+ ),
219+ errorWidget: (context, url, error) => Image .asset (
220+ 'assets/images/placeholder-profile-img.png' ,
221+ fit: BoxFit .cover,
222+ ),
223+ ),
224+ ),
225+ ),
226+ ),
227+ const SizedBox (width: 8 ),
228+ Flexible (
229+ child: GestureDetector (
230+ onTap: () => model.navigateToAuthor (tutorial.authorSlug),
231+ child: Text (
232+ tutorial.authorName,
233+ maxLines: 1 ,
234+ overflow: TextOverflow .ellipsis,
235+ style: TextStyle (
236+ fontSize: 13 ,
237+ color: Colors .white.withValues (alpha: 0.8 ),
238+ ),
239+ ),
240+ ),
241+ ),
242+ Text (
243+ ' • ' ,
244+ style: TextStyle (
245+ fontSize: 13 ,
246+ color: Colors .white.withValues (alpha: 0.5 ),
256247 ),
257248 ),
258249 Text (
259250 NewsFeedViewModel .parseDate (tutorial.createdAt),
251+ style: TextStyle (
252+ fontSize: 13 ,
253+ color: Colors .white.withValues (alpha: 0.5 ),
254+ ),
260255 ),
261256 ],
262257 ),
263258 ],
264259 ),
265- ] ,
260+ ) ,
266261 );
267262 }
268263}
0 commit comments