feat(ViewModels, Models): 增加 Tweet model,增加上传和获取 tweet 逻辑

增加 Tweet model,增加上传和获取 tweet 逻辑

Signed-off-by: Ching <loooching@gmail.com>
This commit is contained in:
Ching 2023-05-03 12:54:49 +08:00
parent 577643d9b1
commit 9d237961b6
12 changed files with 221 additions and 70 deletions

View File

@ -15,6 +15,10 @@
2492CC282A0025DD0086C525 /* TweetRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2492CC272A0025DD0086C525 /* TweetRowView.swift */; };
24A07CB02A017D3300F4ECA8 /* ExploreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A07CAF2A017D3300F4ECA8 /* ExploreViewModel.swift */; };
24A07CB22A01869F00F4ECA8 /* SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A07CB12A01869F00F4ECA8 /* SearchBar.swift */; };
24A07CB42A020ECB00F4ECA8 /* TweetService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A07CB32A020ECB00F4ECA8 /* TweetService.swift */; };
24A07CB62A020FF900F4ECA8 /* UploadTweetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A07CB52A020FF900F4ECA8 /* UploadTweetViewModel.swift */; };
24A07CB82A02173400F4ECA8 /* FeedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A07CB72A02173400F4ECA8 /* FeedViewModel.swift */; };
24A07CBA2A02186500F4ECA8 /* Tweet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A07CB92A02186500F4ECA8 /* Tweet.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 */; };
@ -58,6 +62,10 @@
2492CC272A0025DD0086C525 /* TweetRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweetRowView.swift; sourceTree = "<group>"; };
24A07CAF2A017D3300F4ECA8 /* ExploreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreViewModel.swift; sourceTree = "<group>"; };
24A07CB12A01869F00F4ECA8 /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = "<group>"; };
24A07CB32A020ECB00F4ECA8 /* TweetService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweetService.swift; sourceTree = "<group>"; };
24A07CB52A020FF900F4ECA8 /* UploadTweetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadTweetViewModel.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
@ -165,6 +173,7 @@
children = (
24FA4D9D2A0134CB002D202A /* ImageUploader.swift */,
24FA4D9F2A013B24002D202A /* UserService.swift */,
24A07CB32A020ECB00F4ECA8 /* TweetService.swift */,
);
path = Service;
sourceTree = "<group>";
@ -173,6 +182,7 @@
isa = PBXGroup;
children = (
24FA4DA12A013D2E002D202A /* User.swift */,
24A07CB92A02186500F4ECA8 /* Tweet.swift */,
);
path = Model;
sourceTree = "<group>";
@ -210,6 +220,7 @@
2492CC222A0022F80086C525 /* ViewModels */ = {
isa = PBXGroup;
children = (
24A07CB72A02173400F4ECA8 /* FeedViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
@ -359,6 +370,7 @@
24A59AD92A00DB67009C9E3E /* ViewModels */ = {
isa = PBXGroup;
children = (
24A07CB52A020FF900F4ECA8 /* UploadTweetViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
@ -493,6 +505,8 @@
24FA4DA02A013B24002D202A /* UserService.swift in Sources */,
24FA4D9C2A012E1A002D202A /* ImagePicker.swift in Sources */,
2492CC0F2A000EB00086C525 /* ContentView.swift in Sources */,
24A07CBA2A02186500F4ECA8 /* Tweet.swift in Sources */,
24A07CB82A02173400F4ECA8 /* FeedViewModel.swift in Sources */,
2492CC282A0025DD0086C525 /* TweetRowView.swift in Sources */,
24FA4D9E2A0134CB002D202A /* ImageUploader.swift in Sources */,
24A59AEA2A00F672009C9E3E /* CustomInputField.swift in Sources */,
@ -503,6 +517,8 @@
24A07CB02A017D3300F4ECA8 /* ExploreViewModel.swift in Sources */,
2492CC0D2A000EB00086C525 /* dudu_tweetApp.swift in Sources */,
24A59ABA2A0030CB009C9E3E /* ExploreView.swift in Sources */,
24A07CB62A020FF900F4ECA8 /* UploadTweetViewModel.swift in Sources */,
24A07CB42A020ECB00F4ECA8 /* TweetService.swift in Sources */,
24A59AD22A00BE14009C9E3E /* SideMenuViewModel.swift in Sources */,
24A59ADF2A00DCC2009C9E3E /* TextArea.swift in Sources */,
24A07CB22A01869F00F4ECA8 /* SearchBar.swift in Sources */,

View File

@ -6,35 +6,45 @@
//
import SwiftUI
import Kingfisher
struct TweetRowView: View {
let tweet: Tweet
init(tweet: Tweet) {
self.tweet = tweet
}
var body: some View {
VStack(alignment: .leading) {
// profile image + user info + tweet
HStack(alignment: .top, spacing: 12) {
// profile image
Circle()
.frame(width: 56, height: 56)
.foregroundColor(Color(.systemBlue))
// user info + tweet caption
VStack(alignment: .leading) {
// user info
HStack {
Text("Zhang San")
.font(.subheadline).bold()
Text("@zhang3")
.foregroundColor(.gray)
.font(.caption)
Text("2min")
.foregroundColor(.gray)
.font(.caption)
}
if let user = tweet.user {
HStack(alignment: .top, spacing: 12) {
// profile image
KFImage(URL(string: user.profileImageUrl))
.resizable()
.scaledToFill()
.clipShape(Circle())
.frame(width: 56, height: 56)
// tweet caption
Text("全都不是我干的。")
.font(.subheadline)
.multilineTextAlignment(.leading)
// user info + tweet caption
VStack(alignment: .leading) {
// user info
HStack {
Text(user.fullname)
.font(.subheadline).bold()
Text("@\(user.username)")
.foregroundColor(.gray)
.font(.caption)
Text("2min")
.foregroundColor(.gray)
.font(.caption)
}
// tweet caption
Text(tweet.caption)
.font(.subheadline)
.multilineTextAlignment(.leading)
}
}
}
// action buttons
@ -75,9 +85,9 @@ struct TweetRowView: View {
}
}
struct TweetRowView_Previews: PreviewProvider {
static var previews: some View {
TweetRowView()
}
}
//
//struct TweetRowView_Previews: PreviewProvider {
// static var previews: some View {
// TweetRowView()
// }
//}

View File

@ -34,7 +34,6 @@ class ExploreViewModel: ObservableObject {
func fetchUsers() {
service.fetchUsers { users in
self.users = users
print("DEBUG: users \(users)")
}
}
}

View File

@ -9,13 +9,14 @@ import SwiftUI
struct FeedView: View {
@State private var showNewTweetView = false
@ObservedObject var viewModel = FeedViewModel()
var body: some View {
ZStack(alignment: .bottomTrailing) {
ScrollView {
LazyVStack {
ForEach(0 ... 20, id: \.self) { _ in
TweetRowView()
ForEach(viewModel.tweets) { tweet in
TweetRowView(tweet: tweet)
}
}
}

View File

@ -0,0 +1,30 @@
//
// FeedViewModel.swift
// dudu-tweet
//
// Created by ching on 2023/5/3.
//
import Foundation
class FeedViewModel: ObservableObject {
@Published var tweets = [Tweet]()
let service = TweetService()
let userService = UserService()
init() {
fetchTweets()
}
func fetchTweets() {
service.fetchTweets { tweets in
self.tweets = tweets
for i in 0 ..< tweets.count {
let uid = tweets[i].uid
self.userService.fetchUser(withUid: uid) { user in
self.tweets[i].user = user
}
}
}
}
}

View File

@ -166,7 +166,7 @@ extension ProfileView {
ScrollView {
LazyVStack {
ForEach(0...9, id: \.self) { _ in
TweetRowView()
// TweetRowView()
}
}
}

View File

@ -0,0 +1,23 @@
//
// UploadTweetViewModel.swift
// dudu-tweet
//
// Created by ching on 2023/5/3.
//
import Foundation
class UploadTweetViewModel: ObservableObject {
@Published var didUploadTweet = false
let service = TweetService()
func uploadTweet(withCaption caption: String) {
service.uploadTweet(caption: caption) { success in
if success {
self.didUploadTweet = true
} else {
}
}
}
}

View File

@ -6,51 +6,65 @@
//
import SwiftUI
import Kingfisher
struct NewTweetView: View {
@State private var caption = ""
// @Environment(\.presentationMode) var presentationMode
@Environment(\.dismiss) private var dismiss
@EnvironmentObject var authViewModel: AuthViewModel
@ObservedObject var viewModel = UploadTweetViewModel()
var body: some View {
VStack {
HStack {
Button {
// presentationMode.wrappedValue.dismiss()
if let user = authViewModel.currentUser {
VStack {
HStack {
Button {
// presentationMode.wrappedValue.dismiss()
dismiss()
} label: {
Text("Cancel")
.foregroundColor(Color(.systemBlue))
}
Spacer()
Button {
viewModel.uploadTweet(withCaption: caption)
} label: {
Text("Tweet")
.bold()
.padding(.horizontal)
.padding(.vertical, 8)
.background(Color(.systemBlue))
.foregroundColor(.white)
.clipShape(Capsule())
}
}
.padding()
HStack(alignment: .top) {
KFImage(URL(string: user.profileImageUrl))
.resizable()
.scaledToFill()
.clipShape(Circle())
.frame(width: 64, height: 64)
TextArea("What's happening?", text: $caption)
}
.padding()
}
.onReceive(viewModel.$didUploadTweet) { success in
if success {
dismiss()
} label: {
Text("Cancel")
.foregroundColor(Color(.systemBlue))
}
Spacer()
Button {
print("tweet this..")
} label: {
Text("Tweet")
.bold()
.padding(.horizontal)
.padding(.vertical, 8)
.background(Color(.systemBlue))
.foregroundColor(.white)
.clipShape(Capsule())
}
}
.padding()
HStack(alignment: .top) {
Circle()
.frame(width: 64, height: 64)
TextArea("What's happening?", text: $caption)
}
.padding()
}
}
}
struct NewTweetView_Previews: PreviewProvider {
static var previews: some View {
NewTweetView()
}
}
//
//struct NewTweetView_Previews: PreviewProvider {
// static var previews: some View {
// NewTweetView()
// }
//}

View File

@ -0,0 +1,19 @@
//
// Tweet.swift
// dudu-tweet
//
// Created by ching on 2023/5/3.
//
import FirebaseFirestoreSwift
import Firebase
struct Tweet: Identifiable, Decodable {
@DocumentID var id: String?
let caption: String
let timestamp: Timestamp
let uid: String
var likes: Int
var user: User?
}

View File

@ -0,0 +1,41 @@
//
// TweetService.swift
// dudu-tweet
//
// Created by ching on 2023/5/3.
//
import Firebase
struct TweetService {
func uploadTweet(caption: String, completion: @escaping(Bool) -> Void) {
guard let uid = Auth.auth().currentUser?.uid else { return }
let data = ["uid": uid,
"caption": caption,
"likes": 0,
"timestamp": Timestamp(date: Date())] as [String : Any]
Firestore.firestore().collection("tweets").document()
.setData(data) { error in
if let error = error {
print("DEBUG: uploaded tweet with error \(error)")
completion(false)
}
completion(true)
print("DEBUG: uploaded tweet...")
}
}
func fetchTweets(completion: @escaping([Tweet]) -> Void) {
Firestore.firestore().collection("tweets").getDocuments { snapshot, _ in
guard let documents = snapshot?.documents else { return }
let tweets = documents.compactMap({ try? $0.data(as: Tweet.self) })
completion(tweets)
}
}
}

View File

@ -16,8 +16,6 @@ struct UserService {
.getDocument { snapshot, _ in
guard let snapshot = snapshot else { return }
guard let user = try? snapshot.data(as: User.self) else { return }
print("DEBUG: Username is \(user.username)")
print("DEBUG: User id is \(user.id)")
completion(user)
}
}