11import 'package:flutter/material.dart' ;
22import 'package:flutter_riverpod/flutter_riverpod.dart' ;
33import 'package:flutter_screenutil/flutter_screenutil.dart' ;
4+ import 'package:sejong_catch_frontend/core/theme/app_colors.dart' ;
5+ import 'package:sejong_catch_frontend/core/theme/app_spacing.dart' ;
6+ import 'package:sejong_catch_frontend/core/theme/app_shadows.dart' ;
7+ import 'package:sejong_catch_frontend/core/widgets/app_divider.dart' ;
48
59/// 📰 피드 페이지 - 공모전·취업·논문·공지·축제 통합 피드
610///
711/// CLAUDE.md 원칙:
812/// ✅ UI만 담당하는 깔끔한 페이지 (Search 패턴 적용!)
913/// ✅ 카테고리 필터 칩 + 피드 카드 리스트
1014/// ✅ 더미 데이터 하드코딩
15+ /// ✅ AppColors, AppSpacing, AppDivider 디자인 토큰 사용
1116class FeedPage extends ConsumerStatefulWidget {
1217 const FeedPage ({super .key});
1318
@@ -81,13 +86,16 @@ class _FeedPageState extends ConsumerState<FeedPage> {
8186 @override
8287 Widget build (BuildContext context) {
8388 return Scaffold (
84- backgroundColor: const Color ( 0xFFF7F7F8 ) ,
89+ backgroundColor: AppColors .surface ,
8590 appBar: _buildAppBar (),
8691 body: Column (
8792 children: [
8893 // 카테고리 필터
8994 _buildCategoryFilter (),
9095
96+ // 구분선 추가 (얇은 구분선)
97+ AppDivider .thin (),
98+
9199 // 피드 리스트
92100 Expanded (
93101 child: _buildFeedList (),
@@ -107,26 +115,26 @@ class _FeedPageState extends ConsumerState<FeedPage> {
107115 style: TextStyle (
108116 fontSize: 20. sp,
109117 fontWeight: FontWeight .w700,
110- color: const Color ( 0xFFDC143C ) ,
118+ color: AppColors .brandCrimson ,
111119 ),
112120 ),
113- SizedBox (width : 6. w) ,
121+ AppSpacing .horizontalSpaceXS ,
114122 Text (
115123 '피드' ,
116124 style: TextStyle (
117125 fontSize: 20. sp,
118126 fontWeight: FontWeight .w600,
119- color: const Color ( 0xFF1F2937 ) ,
127+ color: AppColors .textPrimary ,
120128 ),
121129 ),
122130 ],
123131 ),
124- backgroundColor: Colors .white,
132+ backgroundColor: AppColors .white,
125133 elevation: 0 ,
126134 actions: [
127135 IconButton (
128136 icon: Icon (Icons .notifications_outlined, size: 24. sp),
129- color: const Color ( 0xFF6B7280 ) ,
137+ color: AppColors .textSecondary ,
130138 onPressed: () {
131139 ScaffoldMessenger .of (context).showSnackBar (
132140 const SnackBar (content: Text ('알림 기능 준비 중! 🔔' )),
@@ -142,16 +150,13 @@ class _FeedPageState extends ConsumerState<FeedPage> {
142150 return Container (
143151 height: 56. h,
144152 decoration: BoxDecoration (
145- color: Colors .white,
146- border: Border (
147- bottom: BorderSide (color: const Color (0xFFE5E7EB ), width: 1 ),
148- ),
153+ color: AppColors .white,
149154 ),
150155 child: ListView .separated (
151156 scrollDirection: Axis .horizontal,
152157 padding: EdgeInsets .symmetric (horizontal: 18. w, vertical: 8. h),
153158 itemCount: _categories.length,
154- separatorBuilder: (context, index) => SizedBox (width : 8. w) ,
159+ separatorBuilder: (context, index) => AppSpacing .horizontalSpaceSM ,
155160 itemBuilder: (context, index) {
156161 final category = _categories[index];
157162 final isSelected = category == _selectedCategory;
@@ -165,12 +170,10 @@ class _FeedPageState extends ConsumerState<FeedPage> {
165170 child: Container (
166171 padding: EdgeInsets .symmetric (horizontal: 16. w, vertical: 8. h),
167172 decoration: BoxDecoration (
168- color: isSelected ? const Color ( 0xFFDC143C ) : Colors .white,
173+ color: isSelected ? AppColors .brandCrimson : AppColors .white,
169174 borderRadius: BorderRadius .circular (20. r),
170175 border: Border .all (
171- color: isSelected
172- ? const Color (0xFFDC143C )
173- : const Color (0xFFE5E7EB ),
176+ color: isSelected ? AppColors .brandCrimson : AppColors .divider,
174177 width: 1.5 ,
175178 ),
176179 ),
@@ -180,7 +183,7 @@ class _FeedPageState extends ConsumerState<FeedPage> {
180183 style: TextStyle (
181184 fontSize: 14. sp,
182185 fontWeight: isSelected ? FontWeight .w600 : FontWeight .w500,
183- color: isSelected ? Colors .white : const Color ( 0xFF6B7280 ) ,
186+ color: isSelected ? AppColors .white : AppColors .textSecondary ,
184187 ),
185188 ),
186189 ),
@@ -204,9 +207,9 @@ class _FeedPageState extends ConsumerState<FeedPage> {
204207 }
205208
206209 return ListView .separated (
207- padding: EdgeInsets . all ( 18. w) ,
210+ padding: AppSpacing .screenPadding ,
208211 itemCount: filteredItems.length,
209- separatorBuilder: (context, index) => SizedBox (height : 16. h) ,
212+ separatorBuilder: (context, index) => AppSpacing .verticalSpaceLG ,
210213 itemBuilder: (context, index) {
211214 final item = filteredItems[index];
212215 return _buildFeedCard (item);
@@ -228,21 +231,15 @@ class _FeedPageState extends ConsumerState<FeedPage> {
228231 },
229232 child: Container (
230233 decoration: BoxDecoration (
231- color: Colors .white,
234+ color: AppColors .white,
232235 borderRadius: BorderRadius .circular (16. r),
233236 border: Border .all (
234237 color: isUrgent
235- ? const Color ( 0xFFDC143C ) .withValues (alpha: 0.3 )
236- : const Color ( 0xFFE5E7EB ) ,
238+ ? AppColors .brandCrimson .withValues (alpha: 0.3 )
239+ : AppColors .divider ,
237240 width: isUrgent ? 2 : 1 ,
238241 ),
239- boxShadow: [
240- BoxShadow (
241- color: Colors .black.withValues (alpha: 0.05 ),
242- blurRadius: 8 ,
243- offset: const Offset (0 , 2 ),
244- ),
245- ],
242+ boxShadow: AppShadows .basic,
246243 ),
247244 child: Column (
248245 crossAxisAlignment: CrossAxisAlignment .start,
@@ -251,7 +248,7 @@ class _FeedPageState extends ConsumerState<FeedPage> {
251248 Container (
252249 height: 140. h,
253250 decoration: BoxDecoration (
254- color: const Color ( 0xFFF3F4F6 ) ,
251+ color: AppColors .surface ,
255252 borderRadius: BorderRadius .vertical (top: Radius .circular (16. r)),
256253 ),
257254 child: Stack (
@@ -261,7 +258,7 @@ class _FeedPageState extends ConsumerState<FeedPage> {
261258 child: Icon (
262259 Icons .image_outlined,
263260 size: 48. sp,
264- color: const Color ( 0xFF9CA3AF ) ,
261+ color: AppColors .disabled ,
265262 ),
266263 ),
267264
@@ -285,21 +282,16 @@ class _FeedPageState extends ConsumerState<FeedPage> {
285282 child: Container (
286283 padding: EdgeInsets .all (8. w),
287284 decoration: BoxDecoration (
288- color: Colors .white,
285+ color: AppColors .white,
289286 shape: BoxShape .circle,
290- boxShadow: [
291- BoxShadow (
292- color: Colors .black.withValues (alpha: 0.1 ),
293- blurRadius: 4 ,
294- ),
295- ],
287+ boxShadow: AppShadows .basic,
296288 ),
297289 child: Icon (
298290 item['isBookmarked' ] as bool
299291 ? Icons .bookmark
300292 : Icons .bookmark_border,
301293 size: 20. sp,
302- color: const Color ( 0xFFDC143C ) ,
294+ color: AppColors .brandCrimson ,
303295 ),
304296 ),
305297 ),
@@ -308,9 +300,12 @@ class _FeedPageState extends ConsumerState<FeedPage> {
308300 ),
309301 ),
310302
303+ // 구분선 (카드 내부 - 썸네일과 내용 사이)
304+ AppDivider .thin (),
305+
311306 // 카드 내용
312307 Padding (
313- padding: EdgeInsets . all ( 16. w) ,
308+ padding: AppSpacing .cardPadding ,
314309 child: Column (
315310 crossAxisAlignment: CrossAxisAlignment .start,
316311 children: [
@@ -320,26 +315,31 @@ class _FeedPageState extends ConsumerState<FeedPage> {
320315 style: TextStyle (
321316 fontSize: 16. sp,
322317 fontWeight: FontWeight .w600,
323- color: const Color ( 0xFF1F2937 ) ,
318+ color: AppColors .textPrimary ,
324319 ),
325320 maxLines: 1 ,
326321 overflow: TextOverflow .ellipsis,
327322 ),
328323
329- SizedBox (height : 6. h) ,
324+ AppSpacing .verticalSpaceXS ,
330325
331326 // 설명
332327 Text (
333328 item['description' ] as String ,
334329 style: TextStyle (
335330 fontSize: 14. sp,
336- color: const Color ( 0xFF6B7280 ) ,
331+ color: AppColors .textSecondary ,
337332 ),
338333 maxLines: 2 ,
339334 overflow: TextOverflow .ellipsis,
340335 ),
341336
342- SizedBox (height: 12. h),
337+ AppSpacing .verticalSpaceMD,
338+
339+ // 구분선 (내용과 하단 정보 사이)
340+ AppDivider .thin (),
341+
342+ AppSpacing .verticalSpaceMD,
343343
344344 // 하단 정보 (D-Day, 조회수, 우선순위)
345345 Row (
@@ -348,29 +348,26 @@ class _FeedPageState extends ConsumerState<FeedPage> {
348348 _buildInfoChip (
349349 icon: Icons .access_time,
350350 label: 'D-$dDay ' ,
351- color: isUrgent
352- ? const Color (0xFFDC143C )
353- : const Color (0xFF6B7280 ),
351+ color: isUrgent ? AppColors .error : AppColors .textSecondary,
354352 backgroundColor: isUrgent
355- ? const Color ( 0xFFFEF2F2 )
356- : const Color ( 0xFFF9FAFB ) ,
353+ ? AppColors .error. withValues (alpha : 0.1 )
354+ : AppColors .surface ,
357355 ),
358356
359- SizedBox (width : 8. w) ,
357+ AppSpacing .horizontalSpaceSM ,
360358
361359 // 조회수
362360 _buildInfoChip (
363361 icon: Icons .visibility_outlined,
364362 label: _formatNumber (item['viewCount' ] as int ),
365- color: const Color ( 0xFF6B7280 ) ,
366- backgroundColor: const Color ( 0xFFF9FAFB ) ,
363+ color: AppColors .textSecondary ,
364+ backgroundColor: AppColors .surface ,
367365 ),
368366
369- SizedBox (width : 8. w) ,
367+ AppSpacing .horizontalSpaceSM ,
370368
371369 // 우선순위
372- if (priority != 'low' )
373- _buildPriorityBadge (priority),
370+ if (priority != 'low' ) _buildPriorityBadge (priority),
374371 ],
375372 ),
376373 ],
@@ -387,22 +384,22 @@ class _FeedPageState extends ConsumerState<FeedPage> {
387384 Color badgeColor;
388385 switch (category) {
389386 case '공모전' :
390- badgeColor = const Color ( 0xFFDC143C ) ;
387+ badgeColor = AppColors .brandCrimson ;
391388 break ;
392389 case '취업' :
393- badgeColor = const Color ( 0xFF2563EB );
390+ badgeColor = AppColors .trustAcademic; // 파란색
394391 break ;
395392 case '논문' :
396- badgeColor = const Color (0xFF7C3AED );
393+ badgeColor = const Color (0xFF7C3AED ); // 보라색
397394 break ;
398395 case '학교공지' :
399- badgeColor = const Color ( 0xFF059669 ) ;
396+ badgeColor = AppColors .success ;
400397 break ;
401398 case '축제' :
402- badgeColor = const Color ( 0xFFF59E0B ) ;
399+ badgeColor = AppColors .warning ;
403400 break ;
404401 default :
405- badgeColor = const Color ( 0xFF6B7280 ) ;
402+ badgeColor = AppColors .textSecondary ;
406403 }
407404
408405 return Container (
@@ -416,7 +413,7 @@ class _FeedPageState extends ConsumerState<FeedPage> {
416413 style: TextStyle (
417414 fontSize: 11. sp,
418415 fontWeight: FontWeight .w600,
419- color: Colors .white,
416+ color: AppColors .white,
420417 ),
421418 ),
422419 );
@@ -439,7 +436,7 @@ class _FeedPageState extends ConsumerState<FeedPage> {
439436 mainAxisSize: MainAxisSize .min,
440437 children: [
441438 Icon (icon, size: 14. sp, color: color),
442- SizedBox (width : 4. w) ,
439+ AppSpacing .horizontalSpaceXS ,
443440 Text (
444441 label,
445442 style: TextStyle (
@@ -461,15 +458,15 @@ class _FeedPageState extends ConsumerState<FeedPage> {
461458 switch (priority) {
462459 case 'high' :
463460 label = '높음' ;
464- color = const Color ( 0xFFDC143C ) ;
461+ color = AppColors .priorityHigh ;
465462 break ;
466463 case 'mid' :
467464 label = '중간' ;
468- color = const Color ( 0xFFF59E0B ) ;
465+ color = AppColors .priorityMid ;
469466 break ;
470467 default :
471468 label = '낮음' ;
472- color = const Color ( 0xFF6B7280 ) ;
469+ color = AppColors .textSecondary ;
473470 }
474471
475472 return Container (
@@ -483,7 +480,7 @@ class _FeedPageState extends ConsumerState<FeedPage> {
483480 mainAxisSize: MainAxisSize .min,
484481 children: [
485482 Icon (Icons .flag, size: 12. sp, color: color),
486- SizedBox (width : 4. w) ,
483+ AppSpacing .horizontalSpaceXS ,
487484 Text (
488485 label,
489486 style: TextStyle (
@@ -506,23 +503,23 @@ class _FeedPageState extends ConsumerState<FeedPage> {
506503 Icon (
507504 Icons .inbox_outlined,
508505 size: 64. sp,
509- color: const Color ( 0xFF9CA3AF ) ,
506+ color: AppColors .disabled ,
510507 ),
511- SizedBox (height : 16. h) ,
508+ AppSpacing .verticalSpaceLG ,
512509 Text (
513510 '$_selectedCategory 정보가 없어요' ,
514511 style: TextStyle (
515512 fontSize: 18. sp,
516513 fontWeight: FontWeight .w600,
517- color: const Color ( 0xFF374151 ) ,
514+ color: AppColors .textPrimary ,
518515 ),
519516 ),
520- SizedBox (height : 8. h) ,
517+ AppSpacing .verticalSpaceSM ,
521518 Text (
522519 '다른 카테고리를 확인해보세요' ,
523520 style: TextStyle (
524521 fontSize: 14. sp,
525- color: const Color ( 0xFF6B7280 ) ,
522+ color: AppColors .textSecondary ,
526523 ),
527524 ),
528525 ],
@@ -537,4 +534,4 @@ class _FeedPageState extends ConsumerState<FeedPage> {
537534 }
538535 return number.toString ();
539536 }
540- }
537+ }
0 commit comments