diff --git a/dudu-tweet/dudu-tweet.xcodeproj/project.pbxproj b/dudu-tweet/dudu-tweet.xcodeproj/project.pbxproj index 966b4a2..f82785c 100644 --- a/dudu-tweet/dudu-tweet.xcodeproj/project.pbxproj +++ b/dudu-tweet/dudu-tweet.xcodeproj/project.pbxproj @@ -40,6 +40,9 @@ 24FA4D992A012A5D002D202A /* ProfilePhotoSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D982A012A5D002D202A /* ProfilePhotoSelectorView.swift */; }; 24FA4D9C2A012E1A002D202A /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D9B2A012E1A002D202A /* ImagePicker.swift */; }; 24FA4D9E2A0134CB002D202A /* ImageUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D9D2A0134CB002D202A /* ImageUploader.swift */; }; + 24FA4DA02A013B24002D202A /* UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D9F2A013B24002D202A /* UserService.swift */; }; + 24FA4DA22A013D2E002D202A /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4DA12A013D2E002D202A /* User.swift */; }; + 24FA4DA52A0142E2002D202A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 24FA4DA42A0142E2002D202A /* Kingfisher */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -74,6 +77,8 @@ 24FA4D982A012A5D002D202A /* ProfilePhotoSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePhotoSelectorView.swift; sourceTree = ""; }; 24FA4D9B2A012E1A002D202A /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; 24FA4D9D2A0134CB002D202A /* ImageUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploader.swift; sourceTree = ""; }; + 24FA4D9F2A013B24002D202A /* UserService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserService.swift; sourceTree = ""; }; + 24FA4DA12A013D2E002D202A /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +90,7 @@ 24A59AF82A010411009C9E3E /* FirebaseStorage in Frameworks */, 24A59AF42A010411009C9E3E /* FirebaseFirestore in Frameworks */, 24A59AF62A010411009C9E3E /* FirebaseFirestoreSwift in Frameworks */, + 24FA4DA52A0142E2002D202A /* Kingfisher in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -154,6 +160,7 @@ isa = PBXGroup; children = ( 24FA4D9D2A0134CB002D202A /* ImageUploader.swift */, + 24FA4D9F2A013B24002D202A /* UserService.swift */, ); path = Service; sourceTree = ""; @@ -161,6 +168,7 @@ 2492CC1E2A0022970086C525 /* Model */ = { isa = PBXGroup; children = ( + 24FA4DA12A013D2E002D202A /* User.swift */, ); path = Model; sourceTree = ""; @@ -413,6 +421,7 @@ 24A59AF32A010411009C9E3E /* FirebaseFirestore */, 24A59AF52A010411009C9E3E /* FirebaseFirestoreSwift */, 24A59AF72A010411009C9E3E /* FirebaseStorage */, + 24FA4DA42A0142E2002D202A /* Kingfisher */, ); productName = "dudu-tweet"; productReference = 2492CC092A000EB00086C525 /* dudu-tweet.app */; @@ -444,6 +453,7 @@ mainGroup = 2492CC002A000EB00086C525; packageReferences = ( 24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + 24FA4DA32A0142E2002D202A /* XCRemoteSwiftPackageReference "Kingfisher" */, ); productRefGroup = 2492CC0A2A000EB00086C525 /* Products */; projectDirPath = ""; @@ -474,6 +484,7 @@ files = ( 24A59ABE2A003108009C9E3E /* MessagesView.swift in Sources */, 24A59AC22A003249009C9E3E /* ProfileView.swift in Sources */, + 24FA4DA02A013B24002D202A /* UserService.swift in Sources */, 24FA4D9C2A012E1A002D202A /* ImagePicker.swift in Sources */, 2492CC0F2A000EB00086C525 /* ContentView.swift in Sources */, 2492CC282A0025DD0086C525 /* TweetRowView.swift in Sources */, @@ -491,6 +502,7 @@ 24A59AE42A00EF1F009C9E3E /* LoginView.swift in Sources */, 24A59AE62A00EF3A009C9E3E /* RegistrationView.swift in Sources */, 24A59ADD2A00DB9F009C9E3E /* NewTweetView.swift in Sources */, + 24FA4DA22A013D2E002D202A /* User.swift in Sources */, 24A59AED2A00F942009C9E3E /* AuthHeaderView.swift in Sources */, 24A59AFA2A01081F009C9E3E /* AuthViewModel.swift in Sources */, 24A59AE82A00F106009C9E3E /* RoundedShape.swift in Sources */, @@ -721,6 +733,14 @@ minimumVersion = 9.0.0; }; }; + 24FA4DA32A0142E2002D202A /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 7.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -744,6 +764,11 @@ package = 24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; productName = FirebaseStorage; }; + 24FA4DA42A0142E2002D202A /* Kingfisher */ = { + isa = XCSwiftPackageProductDependency; + package = 24FA4DA32A0142E2002D202A /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2492CC012A000EB00086C525 /* Project object */; diff --git a/dudu-tweet/dudu-tweet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/dudu-tweet/dudu-tweet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fb6c96a..2beb5bc 100644 --- a/dudu-tweet/dudu-tweet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/dudu-tweet/dudu-tweet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -72,6 +72,15 @@ "version" : "2.3.0" } }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "af4be924ad984cf4d16f4ae4df424e79a443d435", + "version" : "7.6.2" + } + }, { "identity" : "leveldb", "kind" : "remoteSourceControl", 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 7c8cda6..12d758b 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/ContentView.swift b/dudu-tweet/dudu-tweet/ContentView.swift index 944c34c..e804562 100644 --- a/dudu-tweet/dudu-tweet/ContentView.swift +++ b/dudu-tweet/dudu-tweet/ContentView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import Kingfisher struct ContentView: View { @State private var showMenu = false @@ -58,13 +59,18 @@ extension ContentView { .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { - Button { - withAnimation(.easeInOut) { - showMenu.toggle() + if let user = viewModel.currentUser { + Button { + withAnimation(.easeInOut) { + showMenu.toggle() + } + } label: { + KFImage(URL(string: user.profileImageUrl)) + .resizable() + .scaledToFill() + .clipShape(Circle()) + .frame(width: 32, height: 32) } - } label: { - Circle() - .frame(width: 32, height: 32) } } } diff --git a/dudu-tweet/dudu-tweet/Core/Authentication/ViewModels/AuthViewModel.swift b/dudu-tweet/dudu-tweet/Core/Authentication/ViewModels/AuthViewModel.swift index ea52931..d58e9e4 100644 --- a/dudu-tweet/dudu-tweet/Core/Authentication/ViewModels/AuthViewModel.swift +++ b/dudu-tweet/dudu-tweet/Core/Authentication/ViewModels/AuthViewModel.swift @@ -11,11 +11,14 @@ import SwiftUI class AuthViewModel: ObservableObject { @Published var userSession: FirebaseAuth.User? @Published var didAuthenticateUser = false + @Published var currentUser: User? private var tempUserSession: FirebaseAuth.User? + private let service = UserService() + init() { self.userSession = Auth.auth().currentUser - + self.fetchUser() print("DEBUG: user session is \(self.userSession?.uid)") } @@ -29,6 +32,7 @@ class AuthViewModel: ObservableObject { guard let user = result?.user else { return } self.userSession = user + self.fetchUser() print("DEBUG: Did log user \(user.uid) in.") } @@ -47,8 +51,7 @@ class AuthViewModel: ObservableObject { let data = ["email": email, "username": username.lowercased(), - "fullname": fullname, - "uid": user.uid] + "fullname": fullname] Firestore.firestore().collection("users") .document(user.uid) .setData(data) { _ in @@ -71,7 +74,15 @@ class AuthViewModel: ObservableObject { .document(uid) .updateData(["profileImageUrl": profileImageUrl]) { _ in self.userSession = self.tempUserSession + self.fetchUser() } } } + + func fetchUser() { + guard let uid = self.userSession?.uid else { return } + service.fetchUser(withUid: uid) { user in + self.currentUser = user + } + } } diff --git a/dudu-tweet/dudu-tweet/Core/Explore/Views/ExploreView.swift b/dudu-tweet/dudu-tweet/Core/Explore/Views/ExploreView.swift index 86c6358..8df4b27 100644 --- a/dudu-tweet/dudu-tweet/Core/Explore/Views/ExploreView.swift +++ b/dudu-tweet/dudu-tweet/Core/Explore/Views/ExploreView.swift @@ -15,7 +15,7 @@ struct ExploreView: View { LazyVStack { ForEach(0...25, id: \.self) { _ in NavigationLink { - ProfileView() +// ProfileView() } label: { UserRowView() } diff --git a/dudu-tweet/dudu-tweet/Core/Profile/Views/ProfileView.swift b/dudu-tweet/dudu-tweet/Core/Profile/Views/ProfileView.swift index 3145491..ee33c0f 100644 --- a/dudu-tweet/dudu-tweet/Core/Profile/Views/ProfileView.swift +++ b/dudu-tweet/dudu-tweet/Core/Profile/Views/ProfileView.swift @@ -6,14 +6,20 @@ // import SwiftUI +import Kingfisher struct ProfileView: View { @State private var selectedFilter: TweetFilterViewModel = .tweets // @Environment(\.presentationMode) var mode // 用来从explore 页面跳转过来之后跳转回去 @Environment(\.dismiss) private var dismiss + private let user: User @Namespace var animation + init(user: User) { + self.user = user + } + var body: some View { VStack(alignment: .leading) { headerView @@ -28,12 +34,17 @@ struct ProfileView: View { Spacer() } + .toolbar(.hidden) } } struct ProfileView_Previews: PreviewProvider { static var previews: some View { - ProfileView() + ProfileView(user: User(id: NSUUID().uuidString, + username: "zhang3", + fullname: "张三", + profileImageUrl: "", + email: "zhang3@gmail.com")) } } @@ -51,10 +62,13 @@ extension ProfileView { .resizable() .frame(width: 20, height: 16) .foregroundColor(.white) - .offset(x: 16, y: 0) + .offset(x: 16, y: -4) } - Circle() + KFImage(URL(string: user.profileImageUrl)) + .resizable() + .scaledToFill() + .clipShape(Circle()) .frame(width: 72, height: 72) .offset(x: 16, y: 24) } @@ -86,12 +100,12 @@ extension ProfileView { var userInfoDetails: some View { VStack(alignment: .leading, spacing: 4) { HStack { - Text("Zhang San") + Text(user.fullname) .font(.title2).bold() Image(systemName: "checkmark.seal.fill") .foregroundColor(Color(.systemBlue)) } - Text("@zhang3") + Text("@\(user.username)") .font(.subheadline) .foregroundColor(.gray) Text("一个普通人") diff --git a/dudu-tweet/dudu-tweet/Core/SideMenu/Views/SideMenuView.swift b/dudu-tweet/dudu-tweet/Core/SideMenu/Views/SideMenuView.swift index e9b2d91..dcbf9cd 100644 --- a/dudu-tweet/dudu-tweet/Core/SideMenu/Views/SideMenuView.swift +++ b/dudu-tweet/dudu-tweet/Core/SideMenu/Views/SideMenuView.swift @@ -6,50 +6,56 @@ // import SwiftUI +import Kingfisher struct SideMenuView: View { @EnvironmentObject var authViewModel: AuthViewModel var body: some View { - VStack(alignment: .leading, spacing: 32) { - VStack(alignment: .leading) { - Circle() - .frame(width: 48, height: 48) - - VStack(alignment: .leading, spacing: 4) { - Text("张三") - .font(.headline) - Text("@zhang3") - .font(.caption) - .foregroundColor(.gray) + if let user = authViewModel.currentUser { + VStack(alignment: .leading, spacing: 32) { + VStack(alignment: .leading) { + KFImage(URL(string: user.profileImageUrl)) + .resizable() + .scaledToFill() + .clipShape(Circle()) + .frame(width: 48, height: 48) + + VStack(alignment: .leading, spacing: 4) { + Text(user.fullname) + .font(.headline) + Text("@\(user.username)") + .font(.caption) + .foregroundColor(.gray) + } + + UserStatsView() + .padding(.vertical) } + .padding(.leading) - UserStatsView() - .padding(.vertical) - } - .padding(.leading) - - ForEach(SideMenuViewModel.allCases, id: \.rawValue) { viewModel in - if viewModel == .profile { - NavigationLink { - ProfileView() - } label: { + ForEach(SideMenuViewModel.allCases, id: \.rawValue) { viewModel in + if viewModel == .profile { + NavigationLink { + ProfileView(user: user) + } label: { + SideMenuOptionRowView(viewModel: viewModel) + } + } else if viewModel == .logout { + Button { + authViewModel.signOut() + } label: { + SideMenuOptionRowView(viewModel: viewModel) + } + + } else { SideMenuOptionRowView(viewModel: viewModel) } - } else if viewModel == .logout { - Button { - authViewModel.signOut() - } label: { - SideMenuOptionRowView(viewModel: viewModel) - } - - } else { - SideMenuOptionRowView(viewModel: viewModel) } + + Spacer() } - - Spacer() } } } diff --git a/dudu-tweet/dudu-tweet/Model/User.swift b/dudu-tweet/dudu-tweet/Model/User.swift new file mode 100644 index 0000000..c5837ff --- /dev/null +++ b/dudu-tweet/dudu-tweet/Model/User.swift @@ -0,0 +1,16 @@ +// +// User.swift +// dudu-tweet +// +// Created by ching on 2023/5/2. +// + +import FirebaseFirestoreSwift + +struct User: Identifiable, Decodable { + @DocumentID var id: String? + let username: String + let fullname: String + let profileImageUrl: String + let email: String +} diff --git a/dudu-tweet/dudu-tweet/Service/UserService.swift b/dudu-tweet/dudu-tweet/Service/UserService.swift new file mode 100644 index 0000000..d05b0d5 --- /dev/null +++ b/dudu-tweet/dudu-tweet/Service/UserService.swift @@ -0,0 +1,24 @@ +// +// UserService.swift +// dudu-tweet +// +// Created by ching on 2023/5/2. +// + +import Firebase +import FirebaseFirestoreSwift + +struct UserService { + func fetchUser(withUid uid: String, completion: @escaping(User) -> Void) { + print("DEBUG: Fetching user info..") + Firestore.firestore().collection("users") + .document(uid) + .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) + } + } +}