feat(ViewModels, Views): 获取 liked tweets,在 Profile 页面展示
获取 liked tweets,在 Profile 页面展示 Signed-off-by: Ching <loooching@gmail.com>
This commit is contained in:
parent
5b7648e6e7
commit
d34651e306
@ -20,6 +20,7 @@
|
||||
24A07CB82A02173400F4ECA8 /* FeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A07CB72A02173400F4ECA8 /* FeedViewModel.swift */; };
|
||||
24A07CBA2A02186500F4ECA8 /* Tweet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A07CB92A02186500F4ECA8 /* Tweet.swift */; };
|
||||
24A07CBC2A022B2000F4ECA8 /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A07CBB2A022B2000F4ECA8 /* ProfileViewModel.swift */; };
|
||||
24A07CC22A02302700F4ECA8 /* TweetRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A07CC12A02302700F4ECA8 /* TweetRowViewModel.swift */; };
|
||||
24A59AB42A002EB8009C9E3E /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AB32A002EB8009C9E3E /* MainTabView.swift */; };
|
||||
24A59ABA2A0030CB009C9E3E /* ExploreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AB92A0030CB009C9E3E /* ExploreView.swift */; };
|
||||
24A59ABC2A0030EC009C9E3E /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59ABB2A0030EC009C9E3E /* NotificationsView.swift */; };
|
||||
@ -68,6 +69,7 @@
|
||||
24A07CB72A02173400F4ECA8 /* FeedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewModel.swift; sourceTree = "<group>"; };
|
||||
24A07CB92A02186500F4ECA8 /* Tweet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tweet.swift; sourceTree = "<group>"; };
|
||||
24A07CBB2A022B2000F4ECA8 /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = "<group>"; };
|
||||
24A07CC12A02302700F4ECA8 /* TweetRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweetRowViewModel.swift; sourceTree = "<group>"; };
|
||||
24A59AB32A002EB8009C9E3E /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = "<group>"; };
|
||||
24A59AB92A0030CB009C9E3E /* ExploreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreView.swift; sourceTree = "<group>"; };
|
||||
24A59ABB2A0030EC009C9E3E /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
|
||||
@ -238,11 +240,28 @@
|
||||
2492CC262A0025A50086C525 /* Tweets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2492CC272A0025DD0086C525 /* TweetRowView.swift */,
|
||||
24A07CBE2A022FCB00F4ECA8 /* ViewModels */,
|
||||
24A07CBD2A022FC600F4ECA8 /* Views */,
|
||||
);
|
||||
path = Tweets;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A07CBD2A022FC600F4ECA8 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2492CC272A0025DD0086C525 /* TweetRowView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A07CBE2A022FCB00F4ECA8 /* ViewModels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
24A07CC12A02302700F4ECA8 /* TweetRowViewModel.swift */,
|
||||
);
|
||||
path = ViewModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24A59AB22A002E79009C9E3E /* MainTab */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -522,6 +541,7 @@
|
||||
24A59ABA2A0030CB009C9E3E /* ExploreView.swift in Sources */,
|
||||
24A07CB62A020FF900F4ECA8 /* UploadTweetViewModel.swift in Sources */,
|
||||
24A07CB42A020ECB00F4ECA8 /* TweetService.swift in Sources */,
|
||||
24A07CC22A02302700F4ECA8 /* TweetRowViewModel.swift in Sources */,
|
||||
24A59AD22A00BE14009C9E3E /* SideMenuViewModel.swift in Sources */,
|
||||
24A59ADF2A00DCC2009C9E3E /* TextArea.swift in Sources */,
|
||||
24A07CB22A01869F00F4ECA8 /* SearchBar.swift in Sources */,
|
||||
|
||||
Binary file not shown.
@ -0,0 +1,38 @@
|
||||
//
|
||||
// TweetRowViewModel.swift
|
||||
// dudu-tweet
|
||||
//
|
||||
// Created by ching on 2023/5/3.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class TweetVeiwModel: ObservableObject {
|
||||
private let service = TweetService()
|
||||
@Published var tweet: Tweet
|
||||
|
||||
init(tweet: Tweet) {
|
||||
self.tweet = tweet
|
||||
self.checkUserLikedTweet()
|
||||
}
|
||||
|
||||
func likeTweet() {
|
||||
service.likeTweet(tweet) { _ in
|
||||
self.tweet.didLike = true
|
||||
}
|
||||
}
|
||||
|
||||
func unlikeTweet() {
|
||||
service.unlikeTweet(tweet) { _ in
|
||||
self.tweet.didLike = false
|
||||
}
|
||||
}
|
||||
|
||||
func checkUserLikedTweet() {
|
||||
service.checkIfUserLikedTweet(tweet) { didLiked in
|
||||
if didLiked {
|
||||
self.tweet.didLike = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,15 +9,15 @@ import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct TweetRowView: View {
|
||||
let tweet: Tweet
|
||||
@ObservedObject var viewModel: TweetVeiwModel
|
||||
|
||||
init(tweet: Tweet) {
|
||||
self.tweet = tweet
|
||||
self.viewModel = TweetVeiwModel(tweet: tweet)
|
||||
}
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
// profile image + user info + tweet
|
||||
if let user = tweet.user {
|
||||
if let user = viewModel.tweet.user {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
// profile image
|
||||
KFImage(URL(string: user.profileImageUrl))
|
||||
@ -41,7 +41,7 @@ struct TweetRowView: View {
|
||||
}
|
||||
|
||||
// tweet caption
|
||||
Text(tweet.caption)
|
||||
Text(viewModel.tweet.caption)
|
||||
.font(.subheadline)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
@ -64,10 +64,11 @@ struct TweetRowView: View {
|
||||
}
|
||||
Spacer()
|
||||
Button {
|
||||
// action here
|
||||
viewModel.tweet.didLike ?? false ? viewModel.unlikeTweet() : viewModel.likeTweet()
|
||||
} label: {
|
||||
Image(systemName: "heart")
|
||||
Image(systemName: viewModel.tweet.didLike ?? false ? "heart.fill" : "heart")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(viewModel.tweet.didLike ?? false ? .red : .gray)
|
||||
}
|
||||
Spacer()
|
||||
Button {
|
||||
@ -10,12 +10,30 @@ import Foundation
|
||||
|
||||
class ProfileViewModel: ObservableObject {
|
||||
@Published var tweets = [Tweet]()
|
||||
@Published var likedTweets = [Tweet]()
|
||||
private let service = TweetService()
|
||||
private let userService = UserService()
|
||||
let user: User
|
||||
|
||||
init(user: User) {
|
||||
self.user = user
|
||||
self.fetchUserTweet()
|
||||
self.fetchLikedTweets()
|
||||
}
|
||||
|
||||
var actionBarTitle: String {
|
||||
user.isCurrentUser ? "Edit Profile" : "Follow"
|
||||
}
|
||||
|
||||
func tweets(forFilter filter: TweetFilterViewModel) -> [Tweet] {
|
||||
switch filter {
|
||||
case .tweets:
|
||||
return tweets
|
||||
case .replies:
|
||||
return tweets
|
||||
case .likes:
|
||||
return likedTweets
|
||||
}
|
||||
}
|
||||
|
||||
func fetchUserTweet() {
|
||||
@ -27,4 +45,19 @@ class ProfileViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchLikedTweets() {
|
||||
guard let uid = user.id else { return }
|
||||
service.fetchLikedTweets(forUid: uid) { tweets in
|
||||
self.likedTweets = tweets
|
||||
|
||||
for i in 0 ..< tweets.count {
|
||||
let uid = tweets[i].uid
|
||||
self.userService.fetchUser(withUid: uid) { user in
|
||||
self.likedTweets[i].user = user
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +87,7 @@ extension ProfileView {
|
||||
Button {
|
||||
// action here
|
||||
} label: {
|
||||
Text("Edit Profile")
|
||||
Text(viewModel.actionBarTitle)
|
||||
.font(.subheadline).bold()
|
||||
.frame(width: 120, height: 32)
|
||||
.foregroundColor(.black)
|
||||
@ -165,7 +165,7 @@ extension ProfileView {
|
||||
var tweetsView: some View {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
ForEach(viewModel.tweets) { tweet in
|
||||
ForEach(viewModel.tweets(forFilter: self.selectedFilter)) { tweet in
|
||||
TweetRowView(tweet: tweet)
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,4 +16,5 @@ struct Tweet: Identifiable, Decodable {
|
||||
var likes: Int
|
||||
|
||||
var user: User?
|
||||
var didLike: Bool? = false
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import FirebaseFirestoreSwift
|
||||
import Firebase
|
||||
|
||||
struct User: Identifiable, Decodable {
|
||||
@DocumentID var id: String?
|
||||
@ -13,4 +14,6 @@ struct User: Identifiable, Decodable {
|
||||
let fullname: String
|
||||
let profileImageUrl: String
|
||||
let email: String
|
||||
|
||||
var isCurrentUser: Bool { return Auth.auth().currentUser?.uid == self.id}
|
||||
}
|
||||
|
||||
@ -50,5 +50,64 @@ struct TweetService {
|
||||
completion(tweets.sorted(by: { $0.timestamp.dateValue() > $1.timestamp.dateValue()}))
|
||||
}
|
||||
}
|
||||
|
||||
func likeTweet(_ tweet: Tweet, completion: @escaping(Bool) -> Void) {
|
||||
guard let uid = Auth.auth().currentUser?.uid else { return }
|
||||
guard let tweetId = tweet.id else { return }
|
||||
|
||||
let userLikeRef = Firestore.firestore().collection("users").document(uid).collection("user-likes")
|
||||
Firestore.firestore().collection("tweets").document(tweetId)
|
||||
.updateData(["likes": tweet.likes + 1]) { _ in
|
||||
userLikeRef.document(tweetId).setData([:]) { _ in
|
||||
completion(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func unlikeTweet(_ tweet: Tweet, completion: @escaping(Bool) -> Void) {
|
||||
guard let uid = Auth.auth().currentUser?.uid else { return }
|
||||
guard let tweetId = tweet.id else { return }
|
||||
guard tweet.likes > 0 else { return }
|
||||
|
||||
let userLikeRef = Firestore.firestore().collection("users").document(uid).collection("user-likes")
|
||||
Firestore.firestore().collection("tweets").document(tweetId)
|
||||
.updateData(["likes": tweet.likes - 1]) { _ in
|
||||
userLikeRef.document(tweetId).delete() { _ in
|
||||
completion(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkIfUserLikedTweet(_ tweet: Tweet, completion: @escaping(Bool) -> Void) {
|
||||
guard let uid = Auth.auth().currentUser?.uid else { return }
|
||||
guard let tweetId = tweet.id else { return }
|
||||
print("DEBUG: checking likes for \(tweetId)")
|
||||
Firestore.firestore().collection("users").document(uid).collection("user-likes")
|
||||
.document(tweetId)
|
||||
.getDocument { snapshot, _ in
|
||||
guard let snapshot = snapshot else { return }
|
||||
completion(snapshot.exists)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchLikedTweets(forUid uid: String, completion: @escaping([Tweet]) -> Void) {
|
||||
var tweets = [Tweet]()
|
||||
Firestore.firestore().collection("users").document(uid).collection("user-likes")
|
||||
.getDocuments { snapshot, _ in
|
||||
guard let documents = snapshot?.documents else { return }
|
||||
documents.forEach { doc in
|
||||
let tweetID = doc.documentID
|
||||
Firestore.firestore().collection("tweets")
|
||||
.document(tweetID)
|
||||
.getDocument { snapshot, _ in
|
||||
guard let tweet = try? snapshot?.data(as: Tweet.self) else { return }
|
||||
tweets.append(tweet)
|
||||
completion(tweets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user