5. feed(피드 리스트)

강재영's avatar
Dec 18, 2024
5. feed(피드 리스트)

화면결과

notion image

폴더구조

Riverpod(상태관리)로 전역적으로 상태를 선언하고
read해야되는곳과 watch해야되는곳을 부분적으로 상태관리
notion image
 
 
 

코드

looking_screen.dart

import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fronttodayhome/components/image_container.dart'; import 'package:fronttodayhome/models/Feed.dart'; import 'package:fronttodayhome/screens/looking/components/looking_body.dart'; import 'package:fronttodayhome/screens/looking/looking_vm.dart'; class LookingScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final model = ref.watch(LookingScreenProvider); if (model == null || model.list == null) { return Center(child: CircularProgressIndicator()); // 로딩 중일 때 표시 } return Scaffold( backgroundColor: Colors.grey[100], appBar: AppBar( title: Text("둘러보기"), actions: [ IconButton(onPressed: () {}, icon: Icon(CupertinoIcons.search)), IconButton( onPressed: () {}, icon: Icon(CupertinoIcons.plus_rectangle_on_rectangle)), ], bottom: PreferredSize( preferredSize: Size.fromHeight(1.0), child: Container( color: Colors.grey, height: 1.0, ), )), body: ListView( children: [ look_header(), ...List.generate( model.list!.length, (index) => Padding( padding: const EdgeInsets.only(bottom: 16.0), child: LookingBody(feed: model.list![index]), ), ), ], ), ); } } class look_header extends StatelessWidget { const look_header({ super.key, }); @override Widget build(BuildContext context) { return Card( margin: EdgeInsets.only(bottom: 12.0), elevation: 0.5, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0.0)), child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ ImageContainer( borderRadius: 6.0, imageUrl: 'https://picsum.photos/id/780/200/100', width: 45.0, height: 45.0, ), const SizedBox(width: 16.0), Expanded( child: Text( lifeTitle, maxLines: 2, overflow: TextOverflow.ellipsis, ), ) ], ), ), ); } }
 

looking_body.dart

import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:fronttodayhome/components/image_container.dart'; import 'package:fronttodayhome/screens/looking/looking_vm.dart'; import 'package:fronttodayhome/theme.dart'; class LookingBody extends StatelessWidget { final Looking feed; // Feed 대신 Looking으로 변경 const LookingBody({Key? key, required this.feed}) : super(key: key); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide(width: 1, color: Color(0xFFD4D5DD)), ), ), child: Column( children: [ _buildTop(), _buildWriter(), _buildWriting(), _buildImage(), Divider( height: 1, thickness: 1, color: Colors.grey[300], ), _buildTail(), ], ), ); } // 기존의 _buildTop 위젯을 다시 통합 Widget _buildTop() { return Padding( padding: const EdgeInsets.symmetric( vertical: 16, horizontal: 16, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( padding: EdgeInsets.all(4), decoration: BoxDecoration( shape: BoxShape.rectangle, borderRadius: BorderRadius.all(Radius.circular(4)), color: Color.fromRGBO(247, 247, 247, 1)), child: Text( feed.category, style: TextTheme().bodyMedium, ), ), Text( feed.date, style: textTheme().bodyMedium, //작동안함 //스타일해야될듯 ), ], ), ); } // 기존의 _buildWriter 위젯을 다시 통합 Widget _buildWriter() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ ImageContainer( width: 30, height: 30, borderRadius: 15, imageUrl: feed.profileImgUri, ), const SizedBox(width: 8.0), Text.rich( TextSpan(children: [ TextSpan( text: '${feed.userName}', style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ]), ), ], ), ); } // 기존의 _buildWriting 위젯을 다시 통합 Widget _buildWriting() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), child: Align( alignment: Alignment.centerLeft, child: Text( feed.content, style: textTheme().bodyLarge, maxLines: 3, overflow: TextOverflow.ellipsis, textAlign: TextAlign.start, ), ), ); } // 기존의 _buildImage 위젯을 다시 통합 Widget _buildImage() { return Visibility( visible: feed.contentImgUris.isNotEmpty, child: Padding( padding: EdgeInsets.only(left: 16, right: 16, bottom: 16), child: Image.network( feed.contentImgUris.isNotEmpty ? feed.contentImgUris[0].url : '', height: 200, width: double.infinity, fit: BoxFit.cover, ), ), ); } // 기존의 _buildTail 위젯을 다시 통합 Widget _buildTail() { if (feed.contentImgUris.isEmpty) { return SizedBox.shrink(); // 아무것도 렌더링하지 않음 } return Padding( padding: const EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ for (var imageUrl in feed.contentImgUris.take(3)) // 최대 3개의 이미지만 표시 _buildImageItem(imageUrl.url), ], ), Row( children:[ Text( '상품 더보기', style: textTheme().displayMedium, ), const SizedBox(width: 8.0), Icon( CupertinoIcons.right_chevron, size: 18.0, color: Colors.black87, ), ] ), ], ), ); } } Widget _buildImageItem(String imageUrl) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0), child: ClipRRect( borderRadius: BorderRadius.circular(8.0), // 이미지에 border radius 적용 child: Image.network( imageUrl, width: 50, // 이미지 너비 height: 50, // 이미지 높이 fit: BoxFit.cover, // 이미지 잘 맞게 표시 ), ), ); }
 
 

looking_vm.dart

import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fronttodayhome/data/repository/feed_repository.dart'; import 'package:logger/logger.dart'; class LookingScreenVm extends StateNotifier<LookingScreenModel?> { LookingScreenVm(super.state); Future<void> notifyInit() async { // 1. 통신을 해서 응답 받기 List<dynamic> list = await FeedRepository().findAll(); Logger().d("이건 응답받은 데이터 $list"); List<Looking>? feeds = list.map((e) => Looking.fromMap(e)).toList(); Logger().d(feeds); // 2. 상태 갱신 state = LookingScreenModel (list: feeds); } void updateIndex(int newIndex) { // 다시해야함 state = LookingScreenModel( list: state!.list); } } class LookingScreenModel { List<Looking>? list; LookingScreenModel({this.list}); } // Feed 창고 데이터 class Looking { final String category; final String profileImgUri; final String userName; final int postId; final String content; final String date; final List<ContentImgUris> contentImgUris; // 대문자로 수정 Looking({ required this.category, required this.profileImgUri, required this.userName, required this.postId, required this.content, required this.date, this.contentImgUris = const [], }); // JSON 데이터를 Feed 객체로 변환하는 메서드 factory Looking.fromMap(Map<String, dynamic> map) { return Looking( category: map['category'], content: map['content'], postId: map['postId'], date: map['date'], userName: map['userName'], profileImgUri: map['profileImgUrl'], // userFeedPhotos를 ContentImgUris 객체로 변환 contentImgUris: (map['userFeedPhotos'] as List).map((photo) { return ContentImgUris( id: photo['id'], type: photo['type'], url: photo['url'], ); }).toList(), ); } } class ContentImgUris{ final int id; final String type; final String url; ContentImgUris({required this.id, required this.type, required this.url}); } final LookingScreenProvider = StateNotifierProvider<LookingScreenVm, LookingScreenModel?>((ref) { return LookingScreenVm(null)..notifyInit(); });
Share article

강재영 블로그