diff --git a/dudu-tweet/dudu-tweet.xcodeproj/project.pbxproj b/dudu-tweet/dudu-tweet.xcodeproj/project.pbxproj index f6ad708..f381ec2 100644 --- a/dudu-tweet/dudu-tweet.xcodeproj/project.pbxproj +++ b/dudu-tweet/dudu-tweet.xcodeproj/project.pbxproj @@ -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 = ""; }; 24A07CAF2A017D3300F4ECA8 /* ExploreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreViewModel.swift; sourceTree = ""; }; 24A07CB12A01869F00F4ECA8 /* SearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBar.swift; sourceTree = ""; }; + 24A07CB32A020ECB00F4ECA8 /* TweetService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TweetService.swift; sourceTree = ""; }; + 24A07CB52A020FF900F4ECA8 /* UploadTweetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadTweetViewModel.swift; sourceTree = ""; }; + 24A07CB72A02173400F4ECA8 /* FeedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewModel.swift; sourceTree = ""; }; + 24A07CB92A02186500F4ECA8 /* Tweet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tweet.swift; sourceTree = ""; }; 24A59AB32A002EB8009C9E3E /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; 24A59AB92A0030CB009C9E3E /* ExploreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreView.swift; sourceTree = ""; }; 24A59ABB2A0030EC009C9E3E /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; @@ -165,6 +173,7 @@ children = ( 24FA4D9D2A0134CB002D202A /* ImageUploader.swift */, 24FA4D9F2A013B24002D202A /* UserService.swift */, + 24A07CB32A020ECB00F4ECA8 /* TweetService.swift */, ); path = Service; sourceTree = ""; @@ -173,6 +182,7 @@ isa = PBXGroup; children = ( 24FA4DA12A013D2E002D202A /* User.swift */, + 24A07CB92A02186500F4ECA8 /* Tweet.swift */, ); path = Model; sourceTree = ""; @@ -210,6 +220,7 @@ 2492CC222A0022F80086C525 /* ViewModels */ = { isa = PBXGroup; children = ( + 24A07CB72A02173400F4ECA8 /* FeedViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -359,6 +370,7 @@ 24A59AD92A00DB67009C9E3E /* ViewModels */ = { isa = PBXGroup; children = ( + 24A07CB52A020FF900F4ECA8 /* UploadTweetViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -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 */, diff --git a/dudu-tweet/dudu-tweet.xcodeproj/project.xcworkspace/xcuserdata/ching.xcuserdatad/UserInterfaceState.xcuserstate b/dudu-tweet/dudu-tweet.xcodeproj/project.xcworkspace/xcuserdata/ching.xcuserdatad/UserInterfaceState.xcuserstate index 128a06c..176d7e4 100644 Binary files a/dudu-tweet/dudu-tweet.xcodeproj/project.xcworkspace/xcuserdata/ching.xcuserdatad/UserInterfaceState.xcuserstate and b/dudu-tweet/dudu-tweet.xcodeproj/project.xcworkspace/xcuserdata/ching.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/dudu-tweet/dudu-tweet/Core/Components/Tweets/TweetRowView.swift b/dudu-tweet/dudu-tweet/Core/Components/Tweets/TweetRowView.swift index 0458783..b7c5851 100644 --- a/dudu-tweet/dudu-tweet/Core/Components/Tweets/TweetRowView.swift +++ b/dudu-tweet/dudu-tweet/Core/Components/Tweets/TweetRowView.swift @@ -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() +// } +//} diff --git a/dudu-tweet/dudu-tweet/Core/Explore/ViewModels/ExploreViewModel.swift b/dudu-tweet/dudu-tweet/Core/Explore/ViewModels/ExploreViewModel.swift index 6fd046a..6f118c2 100644 --- a/dudu-tweet/dudu-tweet/Core/Explore/ViewModels/ExploreViewModel.swift +++ b/dudu-tweet/dudu-tweet/Core/Explore/ViewModels/ExploreViewModel.swift @@ -34,7 +34,6 @@ class ExploreViewModel: ObservableObject { func fetchUsers() { service.fetchUsers { users in self.users = users - print("DEBUG: users \(users)") } } } diff --git a/dudu-tweet/dudu-tweet/Core/Feed/View/FeedView.swift b/dudu-tweet/dudu-tweet/Core/Feed/View/FeedView.swift index 1e33046..bc27e55 100644 --- a/dudu-tweet/dudu-tweet/Core/Feed/View/FeedView.swift +++ b/dudu-tweet/dudu-tweet/Core/Feed/View/FeedView.swift @@ -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) } } } diff --git a/dudu-tweet/dudu-tweet/Core/Feed/ViewModels/FeedViewModel.swift b/dudu-tweet/dudu-tweet/Core/Feed/ViewModels/FeedViewModel.swift new file mode 100644 index 0000000..52d6874 --- /dev/null +++ b/dudu-tweet/dudu-tweet/Core/Feed/ViewModels/FeedViewModel.swift @@ -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 + } + } + } + } +} diff --git a/dudu-tweet/dudu-tweet/Core/Profile/Views/ProfileView.swift b/dudu-tweet/dudu-tweet/Core/Profile/Views/ProfileView.swift index ee33c0f..ad61344 100644 --- a/dudu-tweet/dudu-tweet/Core/Profile/Views/ProfileView.swift +++ b/dudu-tweet/dudu-tweet/Core/Profile/Views/ProfileView.swift @@ -166,7 +166,7 @@ extension ProfileView { ScrollView { LazyVStack { ForEach(0...9, id: \.self) { _ in - TweetRowView() +// TweetRowView() } } } diff --git a/dudu-tweet/dudu-tweet/Core/UploadTweet/ViewModels/UploadTweetViewModel.swift b/dudu-tweet/dudu-tweet/Core/UploadTweet/ViewModels/UploadTweetViewModel.swift new file mode 100644 index 0000000..1e8bba1 --- /dev/null +++ b/dudu-tweet/dudu-tweet/Core/UploadTweet/ViewModels/UploadTweetViewModel.swift @@ -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 { + + } + } + } +} diff --git a/dudu-tweet/dudu-tweet/Core/UploadTweet/Views/NewTweetView.swift b/dudu-tweet/dudu-tweet/Core/UploadTweet/Views/NewTweetView.swift index aab19ac..e4d1959 100644 --- a/dudu-tweet/dudu-tweet/Core/UploadTweet/Views/NewTweetView.swift +++ b/dudu-tweet/dudu-tweet/Core/UploadTweet/Views/NewTweetView.swift @@ -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() +// } +//} diff --git a/dudu-tweet/dudu-tweet/Model/Tweet.swift b/dudu-tweet/dudu-tweet/Model/Tweet.swift new file mode 100644 index 0000000..34e19d8 --- /dev/null +++ b/dudu-tweet/dudu-tweet/Model/Tweet.swift @@ -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? +} diff --git a/dudu-tweet/dudu-tweet/Service/TweetService.swift b/dudu-tweet/dudu-tweet/Service/TweetService.swift new file mode 100644 index 0000000..11065cf --- /dev/null +++ b/dudu-tweet/dudu-tweet/Service/TweetService.swift @@ -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) + } + } +} + diff --git a/dudu-tweet/dudu-tweet/Service/UserService.swift b/dudu-tweet/dudu-tweet/Service/UserService.swift index 98477f7..ec30a4d 100644 --- a/dudu-tweet/dudu-tweet/Service/UserService.swift +++ b/dudu-tweet/dudu-tweet/Service/UserService.swift @@ -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) } }