Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
Tags
- Snapshot
- Camera
- datetime
- permission
- runTransaction
- provider
- controller
- ListView.builder
- snackbar
- signout
- consumer
- globalkey
- setstate
- switch
- transform
- 문법
- borderRadius
- Stream
- divider
- swift 문법
- enum
- Swift
- changenotifierprovider
- Navigator
- Firebase
- platformexception
- changenotifier
- multiprovider
- reference
- user
Archives
- Today
- Total
코딩하는 제리
[Flutter/Project](Instagram Clone) toggle like button 본문
Flutter/Project_InstaClone(완)
[Flutter/Project](Instagram Clone) toggle like button
JerryCho 2021. 3. 10. 12:29
소스코드 및 pubspec.yaml
// repo/post_network_repository.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_project_IJ/constants/firestore_keys.dart';
import 'package:flutter_project_IJ/models/firestore/post_model.dart';
import 'package:flutter_project_IJ/repo/helper/transformers.dart';
import 'package:rxdart/rxdart.dart';
/*
포스트 데이터 업로드 과정
1. post reference를 가져옴(생성)
2. 어떤 포스트가 해당 유저에 포함되어 있는지 user reference를 가져옴
3. post collection과 user collection 두가지를 업데이트 해야하기에 transaction 사용.
4. user data에 postKey를 업데이트
5. post reference를 통해 post data를 업로드
*/
class PostNetworkRepository with Transformers {
Future<Map<String, dynamic>> createNewPost(
String postKey, Map<String, dynamic> postData) async {
// 1. post reference를 가져옴(생성)
final DocumentReference postRef =
Firestore.instance.collection(COLLECTION_POSTS).document(postKey);
// post reference를 통해 해당 document의 snapshot을 가져옴.
final DocumentSnapshot postSnapshot = await postRef.get();
// 2. 어떤 포스트가 해당 유저에 포함되어 있는지 user reference를 가져옴
final DocumentReference userRef = Firestore.instance
.collection(COLLECTION_USERS)
.document(postData[KEY_USERKEY]);
// 3. transaction -> 데이터 업로드에 하나라도 실패하면 이전의 데이터로 바꿔줌.
return Firestore.instance.runTransaction((Transaction tx) async {
// 해당 postSnapshot이 존재하지 않으면
if (!postSnapshot.exists) {
// 4. 포스트 데이터 업로드
await tx.set(postRef, postData);
// 5. user의 mypost를 업데이트
// user reference에 postKey 추가
await tx.update(userRef, {
KEY_MYPOSTS: FieldValue.arrayUnion([postKey])
});
}
});
}
// postLink를 가져와서 저장
Future<void> updatePostImageUrl({String postImg, String postKey}) async {
// 1. post reference를 가져옴(생성)
final DocumentReference postRef =
Firestore.instance.collection(COLLECTION_POSTS).document(postKey);
// post reference를 통해 해당 document의 snapshot을 가져옴.
final DocumentSnapshot postSnapshot = await postRef.get();
// postSnapshot이 존재하면 업데이트
if (postSnapshot.exists) {
// KEY_POSTIMG에 받아온 postImg를 저장.
await postRef.updateData({KEY_POSTIMG: postImg});
}
}
// follow한 유저의 포스트 가져오기
Stream<void> getPostsFromSpecificUser(String userKey) {
// 파라미터로 받아온 userKey와 KEY_USERKEY와 일치하는 포스트만 가져옴.
return Firestore.instance
.collection(COLLECTION_POSTS)
.where(KEY_USERKEY, isEqualTo: userKey)
.snapshots()
.transform(toPosts);
}
/* RxDart - CombineLatestStream
* 1. Get collection reference
* 2. Create stream for every following users
* 3. Put all the post stream into the list
* 4. using RxDart, combine all the stream
* */
Stream<List<PostModel>> fetchPostsFromAllFollowers(List<dynamic> followings) {
// 1. COLLECTION_POSTS reference를 가져옴.
final CollectionReference collectionReference =
Firestore.instance.collection(COLLECTION_POSTS);
// 2. stream들을 저장하는 List 생성
List<Stream<List<PostModel>>> streams = [];
// 3. List 변수 streams에 하나씩 넣음
// where() -> 조건과 일치하는 document만 가져옴
// followers 리스트를 받아와 변수 follower에 하나씩 넣음.
// userKey와 follower가 일치하는 document를 가져옴.
for (final following in followings) {
streams.add(collectionReference
.where(KEY_USERKEY, isEqualTo: following)
.snapshots()
.transform(toPosts));
}
/*
* 4. Cmobine은 stream을 9개까지 받아올 수 있음
* list<List<PostModel>> -> List<List<PostModel>>로 받아옴.
* transform(combineListOfPosts)를 거쳐 List<PostModel>로 변경 후
* 최근 peed가 가장 상단에 와야하기에 transform(latestToTop)을 이용해 정렬함.
* */
return CombineLatestStream.list<List<PostModel>>(streams)
.transform(combineListOfPosts)
.transform(latestToTop);
}
Future<void> toggleLike(String postKey, String userKey) async {
final DocumentReference postRef =
Firestore.instance.collection(COLLECTION_POSTS).document(postKey);
final DocumentSnapshot postSnapshot = await postRef.get();
if (postSnapshot.exists) {
// 해당 포스트의 userKey가 KEY_NUMOFLIKES 리스트에 존재하는 지 확인
if (postSnapshot.data[KEY_NUMOFLIKES].contains(userKey)) {
postRef.updateData({
KEY_NUMOFLIKES: FieldValue.arrayRemove([userKey])
});
} else {
postRef.updateData({
KEY_NUMOFLIKES: FieldValue.arrayUnion([userKey])
});
}
}
}
}
PostNetworkRepository postNetworkRepository = PostNetworkRepository();
// widget/post.dart
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_project_IJ/constants/common_size.dart';
import 'package:flutter_project_IJ/constants/screen_size.dart';
import 'package:flutter_project_IJ/models/firestore/post_model.dart';
import 'package:flutter_project_IJ/models/user_model_state.dart';
import 'package:flutter_project_IJ/repo/image_network_repository.dart';
import 'package:flutter_project_IJ/repo/post_network_repository.dart';
import 'package:flutter_project_IJ/screens/comments_screen.dart';
import 'package:flutter_project_IJ/widgets/comment.dart';
import 'package:flutter_project_IJ/widgets/rounded_avatar.dart';
import 'package:provider/provider.dart';
import 'my_progress_indicator.dart';
class Post extends StatelessWidget {
final PostModel postModel;
Post(
this.postModel, {
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_postHeader(),
_postImage(),
_postActionsButton(context),
_postLikes(),
_postCaption(),
_moreComments(context),
_lastComment(),
],
);
}
Padding _postCaption() {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: common_gap, // 가로 양끝
vertical: common_xxs_gap, // 세로 양끝.
),
child: Comment(
showImage: false,
username: postModel.username,
text: postModel.caption,
),
);
}
Padding _lastComment() {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: common_gap, // 가로 양끝
vertical: common_xxs_gap, // 세로 양끝.
),
child: Comment(
showImage: false,
username: postModel.lastCommentor,
text: postModel.lastComment,
),
);
}
Padding _postLikes() {
return Padding(
padding: const EdgeInsets.only(left: common_gap),
child: Text(
'${postModel.numOfLikes == null ? 0 : postModel.numOfLikes.length} likes',
style: TextStyle(fontWeight: FontWeight.bold),
),
);
}
Row _postActionsButton(BuildContext context) {
return Row(
children: <Widget>[
Consumer<UserModelState>(
builder: (BuildContext context, UserModelState userModelState,
Widget child) {
return IconButton(
icon: ImageIcon(
AssetImage(postModel.numOfLikes
.contains(userModelState.userModel.userKey)
? 'assets/images/heart_selected.png'
: 'assets/images/heart.png'),
color: postModel.numOfLikes
.contains(userModelState.userModel.userKey)
? Colors.redAccent
: Colors.black,
),
onPressed: () {
postNetworkRepository.toggleLike(
postModel.postKey, userModelState.userModel.userKey);
},
color: Colors.black87,
);
},
),
IconButton(
icon: ImageIcon(AssetImage('assets/images/comment.png')),
onPressed: () {
_goToComments(context);
},
color: Colors.black87,
),
IconButton(
icon: ImageIcon(AssetImage('assets/images/direct_message.png')),
onPressed: () {},
color: Colors.black87,
),
Spacer(), // 위젯을 제외한 나머지 여백을 모두 차지
IconButton(
icon: ImageIcon(AssetImage('assets/images/bookmark.png')),
onPressed: () {},
color: Colors.black87,
),
],
);
}
Widget _postHeader() {
return Row(
children: [
Padding(
padding: const EdgeInsets.all(common_xxs_gap),
child: RoundedAvatar(),
),
Expanded(
// 다른 위젯을 제외한 나머지 여백을 모두 차지
child: Text(postModel.username),
),
IconButton(
icon: Icon(
Icons.more_horiz,
color: Colors.black87,
),
onPressed: null,
),
],
);
}
Widget _postImage() {
Widget progress = MyProgressIndicator(containerSize: size.width);
return CachedNetworkImage(
// CachedNetworkImage() 받아온 이미지를 캐시파일로 저장해 재사용.
imageUrl: postModel.postImg,
placeholder: (BuildContext context, String url) {
return progress;
},
imageBuilder: (BuildContext context, ImageProvider imageProvider) {
return AspectRatio(
aspectRatio: 1 / 1 /* 1대 1의 비율로 이미지 생성 */,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: imageProvider /* CachedNetworkImage 의 imageUrl */,
fit: BoxFit.cover,
),
),
),
);
},
);
}
// 댓글 더 보기
Widget _moreComments(BuildContext context) {
return Visibility(
// 조건에 해당하면 child 표시
visible:
(postModel.numOfComments != null && postModel.numOfComments >= 2),
child: GestureDetector(
// 제스처 추가
onTap: () {
_goToComments(context);
},
child: Padding(
padding: const EdgeInsets.only(left: common_gap),
child: Text(
'View all ${postModel.numOfComments} comments',
style: TextStyle(color: Colors.grey[500]),
),
),
),
);
}
// 댓글 화면 이동
_goToComments(BuildContext context) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (BuildContext context) {
return CommentsScreen(postModel.postKey, postModel);
}));
}
}
'Flutter > Project_InstaClone(완)' 카테고리의 다른 글
[Flutter/Project](Instagram Clone) comments 더보기 버튼 생성 (0) | 2021.03.10 |
---|---|
[Flutter/Project](Instagram Clone) Comments 화면 Firestore 연결 (0) | 2021.03.10 |
[Flutter/Project](Instagram Clone) comment 입력창 레이아웃 (0) | 2021.03.09 |
[Flutter/Project](Instagram Clone) comment model 및 생성/업데이트 파일 (0) | 2021.03.08 |
[Flutter/Project](Instagram Clone) follow한 유저의 포스트를 피드에 출력하기 (0) | 2021.03.04 |
Comments