inblog logo
|
강재영 블로그
    2차프로젝트

    5. feed(피드 리스트)

    강재영's avatar
    강재영
    Dec 18, 2024
    5. feed(피드 리스트)
    Contents
    화면결과폴더구조코드

    화면결과

    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
    Contents
    화면결과폴더구조코드

    강재영 블로그

    RSS·Powered by Inblog