diff --git a/lib/core/TopScreen.dart b/lib/core/TopScreen.dart index c63f3af6..25c44d0d 100644 --- a/lib/core/TopScreen.dart +++ b/lib/core/TopScreen.dart @@ -41,7 +41,7 @@ class TopScreen extends ConsumerWidget { selectedIconTheme: const IconThemeData(color: Colors.black), selectedItemColor: Colors.black, unselectedIconTheme: const IconThemeData(color: Colors.grey), - backgroundColor: Colors.blueGrey[100], + backgroundColor: Colors.white, items: const [ BottomNavigationBarItem( icon: Icon(Icons.podcasts), diff --git a/lib/feature/friendsList/provider/FriendsListImageProvider.dart b/lib/feature/friendsList/provider/FriendsListImageProvider.dart index c190a640..d05cd107 100644 --- a/lib/feature/friendsList/provider/FriendsListImageProvider.dart +++ b/lib/feature/friendsList/provider/FriendsListImageProvider.dart @@ -3,8 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; // 친구목록 이미지 URL을 제공하는 Provider final friendsListImageProvider = -FutureProvider.family((ref, imageName) async { + FutureProvider.family((ref, imageName) async { final storageRef = FirebaseStorage.instance.ref('profileimage/$imageName'); final downloadUrl = await storageRef.getDownloadURL(); return downloadUrl; -}); \ No newline at end of file +}); diff --git a/lib/feature/friendsList/provider/FriendsListProvider.dart b/lib/feature/friendsList/provider/FriendsListProvider.dart index a20adb87..47ab7527 100644 --- a/lib/feature/friendsList/provider/FriendsListProvider.dart +++ b/lib/feature/friendsList/provider/FriendsListProvider.dart @@ -1,11 +1,9 @@ import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../model/FriendModel.dart'; import '../../../utils/AppStrings.dart'; -import '../../../utils/Talker.dart'; import '../../userreport/provider/UserReportBottomSheetWidget.dart'; // 친구목록을 제공하는 Provider @@ -21,7 +19,8 @@ final friendsListProvider = StreamProvider>((ref) { .asyncMap((snapshot) async { final friendModels = await Future.wait(snapshot.docs.map((doc) async { final userID = doc['userID'] as String; - final userDoc = await firestore.collection('users_test').doc(userID).get(); + final userDoc = + await firestore.collection('users_test').doc(userID).get(); if (userDoc.exists) { return FriendModel.fromJson(userDoc.data()!); @@ -57,8 +56,6 @@ final deleteFriendProvider = }; }); - - // ui 팝업 메뉴 선택시 처리하는 함수 void handleMenuSelection( BuildContext context, WidgetRef ref, int value, FriendModel friend) async { diff --git a/lib/feature/match/MatchScreen.dart b/lib/feature/match/MatchScreen.dart index 387d8a55..ba6bb422 100644 --- a/lib/feature/match/MatchScreen.dart +++ b/lib/feature/match/MatchScreen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import '../../utils/AppStrings.dart'; import 'widget/MatchProfileListWidget.dart'; import 'widget/MatchFilterWidget.dart'; @@ -23,8 +24,9 @@ class MatchScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( + centerTitle: true, title: Text( - 'Petting', + AppStrings.appbar_Text_Logo, style: GoogleFonts.lobster( textStyle: const TextStyle( fontSize: 24, diff --git a/lib/feature/match/provider/MatchProvider.dart b/lib/feature/match/provider/MatchProvider.dart index 8309ff57..1a09d7ec 100644 --- a/lib/feature/match/provider/MatchProvider.dart +++ b/lib/feature/match/provider/MatchProvider.dart @@ -185,7 +185,6 @@ class MatchScreenNotifier extends StateNotifier> { Future _addFriend(String userId, String friendId) async { final firestore = FirebaseFirestore.instance; final userDoc = firestore.collection('users_test').doc(userId); - try { await userDoc.collection('friends').doc(friendId).set({ 'userID': friendId, diff --git a/lib/feature/post/PostScreen.dart b/lib/feature/post/PostScreen.dart index db4fb9e7..8b500d54 100644 --- a/lib/feature/post/PostScreen.dart +++ b/lib/feature/post/PostScreen.dart @@ -1,5 +1,4 @@ // 게시물 화면! -// import 'package:blueberry_flutter_template/feature/mbti/MBTIScreen.dart'; import 'package:blueberry_flutter_template/feature/post/widget/PostListViewWidget.dart'; import 'package:blueberry_flutter_template/feature/profile/ProfileDetailScreen.dart'; @@ -8,6 +7,7 @@ import 'package:blueberry_flutter_template/feature/setting/SettingScreen.dart'; import 'package:blueberry_flutter_template/utils/AppStrings.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:google_fonts/google_fonts.dart'; import '../../core/widget/CustomFab.dart'; import '../chat/ChatRoomScreen.dart'; @@ -38,8 +38,12 @@ class PostScreen extends StatelessWidget { AppBar _buildAppBar(BuildContext context) { return AppBar( - centerTitle: false, - title: const Text(AppStrings.appbar_Text_Logo), + centerTitle: true, + title: Text(AppStrings.appbar_Text_Logo, + style: GoogleFonts.lobster( + fontSize: 24, + fontWeight: FontWeight.bold, + )), actions: [ IconButton( icon: const Icon(Icons.message), diff --git a/lib/feature/post/PostingScreen.dart b/lib/feature/post/PostingScreen.dart index ee32195e..bd1e533f 100644 --- a/lib/feature/post/PostingScreen.dart +++ b/lib/feature/post/PostingScreen.dart @@ -1,8 +1,4 @@ -import 'package:blueberry_flutter_template/utils/Talker.dart'; import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; - -import '../../model/PostModel.dart'; class PostingScreen extends StatefulWidget { static const name = 'CreatePostPage'; @@ -63,27 +59,27 @@ class _PostingScreenState extends State { }, ), const SizedBox(height: 20), - Center( - child: ElevatedButton( - onPressed: () { - if (_formKey.currentState?.validate() ?? false) { - _formKey.currentState?.save(); - final String uploadTime = - DateFormat('yyyy-MM-dd HH:mm:ss') - .format(DateTime.now()); - final post = PostModel( - title: title!, - content: content!, - imageUrl: imageUrl!, - uploadTime: uploadTime, - ); - // 포스트 생성 후 처리 - talker.info('포스트 생성: ${post.toJson()}'); - } - }, - child: const Text('포스트 생성'), - ), - ), + // Center( + // child: ElevatedButton( + // onPressed: () { + // if (_formKey.currentState?.validate() ?? false) { + // _formKey.currentState?.save(); + // final String uploadTime = + // DateFormat('yyyy-MM-dd HH:mm:ss') + // .format(DateTime.now()); + // final post = PostModel( + // title: title!, + // content: content!, + // imageUrl: imageUrl!, + // uploadTime: uploadTime, + // ); + // // 포스트 생성 후 처리 + // talker.info('포스트 생성: ${post.toJson()}'); + // } + // }, + // child: const Text('포스트 생성'), + // ), + // ), ], ), ), diff --git a/lib/feature/post/provider/DisLikeProvider.dart b/lib/feature/post/provider/DisLikeProvider.dart new file mode 100644 index 00000000..b0f50b2b --- /dev/null +++ b/lib/feature/post/provider/DisLikeProvider.dart @@ -0,0 +1,73 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final dislikeProvider = + StateNotifierProvider>((ref) { + return DislikeNotifier(); +}); + +class DislikeNotifier extends StateNotifier> { + DislikeNotifier() : super({}); + + // 싫어요 상태를 가져오기 + Future fetchDislikeStatus(String postID, String userID) async { + final dislikeDoc = await FirebaseFirestore.instance + .collection('posts') + .doc(postID) + .collection('dislikes') + .doc(userID) + .get(); + + if (dislikeDoc.exists) { + state = { + ...state, + postID: true, + }; + } else { + state = { + ...state, + postID: false, + }; + } + } + + // 싫어요 토글 기능 + Future toggleDislike(String postID, String userID) async { + final postRef = FirebaseFirestore.instance.collection('posts').doc(postID); + final dislikeRef = postRef.collection('dislikes').doc(userID); + + final dislikeDoc = await dislikeRef.get(); + final isDisliked = dislikeDoc.exists; + + if (isDisliked) { + // 이미 싫어요 상태라면 싫어요 취소 + await dislikeRef.delete(); + state = { + ...state, + postID: false, + }; + } else { + // 싫어요 추가 + await dislikeRef.set({ + 'createdAt': Timestamp.now(), + 'postID': postID, + 'userID': userID, + }); + state = { + ...state, + postID: true, + }; + } + + // 싫어요 카운트 업데이트 + await _updateDislikesCount(postRef, isDisliked ? -1 : 1); + } + + // 싫어요 카운트 업데이트 기능 + Future _updateDislikesCount( + DocumentReference postRef, int dislikeChange) async { + await postRef.update({ + 'dislikesCount': FieldValue.increment(dislikeChange), + }); + } +} diff --git a/lib/feature/post/provider/LikeProvider.dart b/lib/feature/post/provider/LikeProvider.dart new file mode 100644 index 00000000..13643e29 --- /dev/null +++ b/lib/feature/post/provider/LikeProvider.dart @@ -0,0 +1,73 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final likeProvider = + StateNotifierProvider>((ref) { + return LikeNotifier(); +}); + +class LikeNotifier extends StateNotifier> { + LikeNotifier() : super({}); + + // 좋아요 상태를 가져오기 + Future fetchLikeStatus(String postID, String userID) async { + final likeDoc = await FirebaseFirestore.instance + .collection('posts') + .doc(postID) + .collection('likes') + .doc(userID) + .get(); + + if (likeDoc.exists) { + state = { + ...state, + 'postID': true, + }; + } else { + state = { + ...state, + 'postID': false, + }; + } + } + + // 좋아요 토글 기능 + Future toggleLike(String postID, String userID) async { + final postRef = FirebaseFirestore.instance.collection('posts').doc(postID); + final likeRef = postRef.collection('likes').doc(userID); + + final likeDoc = await likeRef.get(); + final isLiked = likeDoc.exists; + + if (isLiked) { + // 이미 좋아요 상태라면 좋아요 취소 + await likeRef.delete(); + state = { + ...state, + postID: false, + }; + } else { + // 좋아요 추가 + await likeRef.set({ + 'createdAt': Timestamp.now(), + 'postID': postID, + 'userID': userID, + }); + state = { + ...state, + postID: true, + }; + } + + // 좋아요 카운트 업데이트 + await _updateLikesCount(postRef, isLiked ? -1 : 1); + } + + // 좋아요 카운트 업데이트 기능 + Future _updateLikesCount( + DocumentReference postRef, int likeChange) async { + await postRef.update({ + 'likesCount': FieldValue.increment(likeChange), + }); + } +} diff --git a/lib/feature/post/provider/PostProvider.dart b/lib/feature/post/provider/PostProvider.dart index bc5f3db5..38e79781 100644 --- a/lib/feature/post/provider/PostProvider.dart +++ b/lib/feature/post/provider/PostProvider.dart @@ -2,9 +2,24 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:blueberry_flutter_template/model/PostModel.dart'; -final postListInfoProvider = StreamProvider>((ref) { +final postProvider = StreamProvider>((ref) { final firestore = FirebaseFirestore.instance; + return firestore.collection('posts').snapshots().map((snapshot) { - return snapshot.docs.map((doc) => PostModel.fromJson(doc.data())).toList(); + return snapshot.docs.map((doc) { + final data = doc.data(); + + // Firestore Timestamp 를 String으로 변환 + final createdAt = + (data['createdAt'] as Timestamp).toDate().toIso8601String(); + + // 변환된 String 형의 createdAt 를 PostModel에 전달(freezed로 생성된 fromJson은 String을 DateTime으로 변환) + final post = PostModel.fromJson({ + ...data, + 'createdAt': createdAt, + }); + + return post; + }).toList(); }); }); diff --git a/lib/feature/post/provider/UserInfoProvider.dart b/lib/feature/post/provider/UserInfoProvider.dart new file mode 100644 index 00000000..327ba658 --- /dev/null +++ b/lib/feature/post/provider/UserInfoProvider.dart @@ -0,0 +1,28 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:blueberry_flutter_template/model/PostUserInfoModel.dart'; + +final postUserInfoProvider = + FutureProvider.family((ref, userID) async { + final firestore = FirebaseFirestore.instance; + + final userDoc = await firestore.collection('users_test').doc(userID).get(); + + if (userDoc.exists) { + final data = userDoc.data()!; + final userName = data['name'] as String; + final imageName = data['imageName'] as String; + + final storageRef = + FirebaseStorage.instance.ref().child('profileimage/$imageName'); + final profileImageUrl = await storageRef.getDownloadURL(); + + return PostUserInfoModel( + name: userName, + profileImageUrl: profileImageUrl, + ); + } else { + throw Exception('User data not found for userID: $userID'); + } +}); diff --git a/lib/feature/post/widget/PostListViewItemWidget.dart b/lib/feature/post/widget/PostListViewItemWidget.dart index 48e0e019..d5ff6496 100644 --- a/lib/feature/post/widget/PostListViewItemWidget.dart +++ b/lib/feature/post/widget/PostListViewItemWidget.dart @@ -1,164 +1,129 @@ +import 'package:blueberry_flutter_template/model/PostModel.dart'; +import 'package:blueberry_flutter_template/model/PostUserInfoModel.dart'; +import 'package:intl/intl.dart'; import 'package:flutter/material.dart'; -class PostListViewItemWidget extends StatefulWidget { - final String title; - final String uploadTime; - final String content; - final String imageUrl; +class PostListViewItemWidget extends StatelessWidget { + final PostModel post; + final bool isLiked; + final bool isDisliked; + final PostUserInfoModel userInfo; + final VoidCallback onLikeToggle; + final VoidCallback onDislikeToggle; const PostListViewItemWidget({ super.key, - required this.title, - required this.uploadTime, - required this.content, - required this.imageUrl, + required this.post, + required this.isLiked, + required this.isDisliked, + required this.userInfo, + required this.onLikeToggle, + required this.onDislikeToggle, }); - @override - _PostListViewItemWidgetState createState() => _PostListViewItemWidgetState(); -} - -// 카드 스타일을 위한 변수 -final cardDecoration = BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15.0), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - spreadRadius: 3, - blurRadius: 5, - offset: const Offset(0, 3), // 그림자 위치 - ), - ], -); - -class _PostListViewItemWidgetState extends State { - bool isLiked = false; // 좋아요 상태를 저장하는 변수 - bool showComment = false; // 댓글 표시 여부를 저장하는 변수 - @override Widget build(BuildContext context) { + // DateTime을 String으로 변환 + String formattedDate = DateFormat.yMMMd().format(post.createdAt); + return Container( - margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), - decoration: cardDecoration, - child: ClipRRect( - borderRadius: BorderRadius.circular(15.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Stack( - children: [ - // 이미지 - ClipRRect( - borderRadius: BorderRadius.circular(15.0), + color: Colors.grey[200], + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + elevation: 3, + color: Colors.white, + margin: const EdgeInsets.symmetric(vertical: 15, horizontal: 10), + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CircleAvatar( + radius: 20, + backgroundImage: NetworkImage(userInfo.profileImageUrl), + ), + const SizedBox(width: 10), + Text( + userInfo.name, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const Spacer(), + IconButton( + icon: const Icon(Icons.more_horiz), + onPressed: () { + // 추가 옵션 클릭 시 동작 + }, + ), + ], + ), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular(15.0), + child: AspectRatio( + aspectRatio: 1.0, // 이미지 1:1 비율 설정 child: Image.network( - widget.imageUrl, + post.imageUrl, width: double.infinity, - height: 200, fit: BoxFit.cover, ), ), - // 제목과 날짜를 이미지 위에 오버레이 - Positioned( - bottom: 10, - left: 10, - right: 10, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + ), + const SizedBox(height: 10), + Text(formattedDate), + const SizedBox(height: 10), + Text(post.content), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( children: [ - Text( - widget.title, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, - color: Colors.white, - shadows: [ - Shadow( - offset: Offset(0, 1), - blurRadius: 3.0, - color: Colors.black, - ), - ], + IconButton( + icon: Icon( + isLiked + ? Icons.thumb_up + : Icons.thumb_up_alt_outlined, + color: isLiked ? Colors.blue : Colors.grey, ), + onPressed: onLikeToggle, ), - const SizedBox(height: 5), - Text( - widget.uploadTime, - style: const TextStyle( - color: Colors.white70, - fontSize: 12, - shadows: [ - Shadow( - offset: Offset(0, 1), - blurRadius: 3.0, - color: Colors.black, - ), - ], + const SizedBox(width: 5), + Text('${post.likesCount}'), + const SizedBox(width: 10), + IconButton( + icon: Icon( + isDisliked + ? Icons.thumb_down + : Icons.thumb_down_alt_outlined, + color: isDisliked ? Colors.blue : Colors.grey, ), + onPressed: onDislikeToggle, + ), + const SizedBox(width: 5), + Text('${post.dislikesCount}'), + const SizedBox(width: 10), + IconButton( + icon: const Icon(Icons.chat_bubble_outline), + onPressed: () { + // 댓글 기능 구현 + }, ), + const SizedBox(width: 5), + Text('${post.commentsCount}'), ], ), - ), - ], - ), - const SizedBox(height: 10), - // 본문 내용 - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: Text( - widget.content, - style: const TextStyle( - fontSize: 16, - ), - ), - ), - const SizedBox(height: 10), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // 좋아요 버튼 - IconButton( - icon: Icon( - isLiked ? Icons.thumb_up : Icons.thumb_up_off_alt, - color: isLiked ? Colors.blue : Colors.grey, - ), - onPressed: () { - setState(() { - isLiked = !isLiked; // 좋아요 상태 토글 - }); - }, - ), - // 댓글 달기 버튼 - IconButton( - onPressed: () => setState(() { - showComment = !showComment; // 댓글 표시 여부 토글 - }), - icon: const Icon(Icons.comment)) + const Icon(Icons.bookmark_border), ], ), - ), - // 임시 댓글 표시 - if (showComment) - const Padding( - padding: EdgeInsets.symmetric(horizontal: 15), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Divider(), // 구분선 추가 - Text( - '정우님 항상 감사합니다', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - SizedBox(height: 10), - ], - ), - ), - ], + ], + ), ), ), ); diff --git a/lib/feature/post/widget/PostListViewWidget.dart b/lib/feature/post/widget/PostListViewWidget.dart index a6531f0f..adb237f2 100644 --- a/lib/feature/post/widget/PostListViewWidget.dart +++ b/lib/feature/post/widget/PostListViewWidget.dart @@ -1,33 +1,80 @@ +import 'package:blueberry_flutter_template/feature/post/provider/LikeProvider.dart'; +import 'package:blueberry_flutter_template/feature/post/provider/DislikeProvider.dart'; import 'package:blueberry_flutter_template/feature/post/provider/PostProvider.dart'; import 'package:blueberry_flutter_template/feature/post/widget/PostListViewItemWidget.dart'; +import 'package:blueberry_flutter_template/utils/Talker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../provider/UserInfoProvider.dart'; +import 'ShimmerSkeleton.dart'; class PostListViewWidget extends ConsumerWidget { const PostListViewWidget({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final postList = ref.watch(postListInfoProvider); + final postList = ref.watch(postProvider); + final likeState = ref.watch(likeProvider); + final dislikeState = ref.watch(dislikeProvider); + const userID = 'eztqDqrvEXDc8nqnnrB8'; // 로그인을 가정한 임시 사용자 ID return SafeArea( child: postList.when( - data: (posts) => ListView.builder( - shrinkWrap: true, - padding: const EdgeInsets.all(8.0), - itemCount: posts.length, - itemBuilder: (context, index) { - final post = posts[index]; - return PostListViewItemWidget( - title: post.title, - uploadTime: post.uploadTime, - content: post.content, - imageUrl: post.imageUrl, - ); - }, - ), - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stackTrace) => Center(child: Text('Error: $error')), + data: (posts) { + return ListView.builder( + shrinkWrap: true, + padding: const EdgeInsets.all(8.0), + itemCount: posts.length, + itemBuilder: (context, index) { + final post = posts[index]; + final isLiked = likeState[post.postID] ?? false; + final isDisliked = dislikeState[post.postID] ?? false; + + final postUserInfo = ref.watch(postUserInfoProvider(post.userID)); + + return postUserInfo.when( + data: (userInfo) { + return PostListViewItemWidget( + post: post, + isLiked: isLiked, + isDisliked: isDisliked, + userInfo: userInfo, + onLikeToggle: () { + if (isDisliked) { + ref + .read(dislikeProvider.notifier) + .toggleDislike(post.postID, userID); // Dislike 해제 + } + ref + .read(likeProvider.notifier) + .toggleLike(post.postID, userID); // Like 토글 + }, + onDislikeToggle: () { + if (isLiked) { + ref + .read(likeProvider.notifier) + .toggleLike(post.postID, userID); // Like 해제 + } + ref + .read(dislikeProvider.notifier) + .toggleDislike(post.postID, userID); // Dislike 토글 + }, + ); + }, + loading: () => const ShimmerSkeleton(), + error: (error, stackTrace) { + talker.error('Error loading user info: $error'); + return Center(child: Text('Error: $error')); + }, + ); + }, + ); + }, + loading: () => const ShimmerSkeleton(), + error: (error, stackTrace) { + talker.error('Error loading posts: $error'); + return Center(child: Text('Error: $error')); + }, ), ); } diff --git a/lib/feature/post/widget/ShimmerSkeleton.dart b/lib/feature/post/widget/ShimmerSkeleton.dart new file mode 100644 index 00000000..247f4f6d --- /dev/null +++ b/lib/feature/post/widget/ShimmerSkeleton.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +class ShimmerSkeleton extends StatelessWidget { + const ShimmerSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: ListView.builder( + shrinkWrap: true, + padding: const EdgeInsets.all(8.0), + itemCount: 3, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Container( + height: 440, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + ), + margin: const EdgeInsets.symmetric(horizontal: 10.0), + ), + ); + }, + ), + ); + } +} diff --git a/lib/model/FriendModel.dart b/lib/model/FriendModel.dart index 3bc330c1..2d487150 100644 --- a/lib/model/FriendModel.dart +++ b/lib/model/FriendModel.dart @@ -2,6 +2,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'generated/FriendModel.freezed.dart'; + part 'generated/FriendModel.g.dart'; @freezed @@ -20,6 +21,7 @@ class FriendModel with _$FriendModel { _$FriendModelFromJson(json); } +// Timestamp 변환 함수 서비스에 두고 임포트해서 사용 예정(해당 방식 리뷰 필요) // 최상위 함수로 Timestamp 변환 함수 정의 DateTime fromJsonTimestamp(Timestamp timestamp) => timestamp.toDate(); diff --git a/lib/model/PostModel.dart b/lib/model/PostModel.dart index ae7e5edf..b022336c 100644 --- a/lib/model/PostModel.dart +++ b/lib/model/PostModel.dart @@ -6,10 +6,14 @@ part 'generated/PostModel.g.dart'; @freezed class PostModel with _$PostModel { const factory PostModel({ - required String title, required String content, required String imageUrl, - required String uploadTime, + required DateTime createdAt, + required num likesCount, + required num dislikesCount, + required num commentsCount, + required String userID, + required String postID, }) = _PostModel; factory PostModel.fromJson(Map json) => diff --git a/lib/model/PostUserInfoModel.dart b/lib/model/PostUserInfoModel.dart new file mode 100644 index 00000000..749a7223 --- /dev/null +++ b/lib/model/PostUserInfoModel.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'generated/PostUserInfoModel.freezed.dart'; +part 'generated/PostUserInfoModel.g.dart'; + +@freezed +class PostUserInfoModel with _$PostUserInfoModel { + const factory PostUserInfoModel({ + required String name, + required String profileImageUrl, + }) = _PostUserInfoModel; + + factory PostUserInfoModel.fromJson(Map json) => + _$PostUserInfoModelFromJson(json); +}