feat(Model): 增加 User model,用获取到的 User 数据替换写死的数据

增加 User model,用获取到的 User 数据替换写死的数据

Signed-off-by: Ching <loooching@gmail.com>
This commit is contained in:
Ching 2023-05-02 22:01:03 +08:00
parent 3eb9b13f86
commit 62c6c6d2c5
10 changed files with 158 additions and 47 deletions

View File

@ -40,6 +40,9 @@
24FA4D992A012A5D002D202A /* ProfilePhotoSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D982A012A5D002D202A /* ProfilePhotoSelectorView.swift */; }; 24FA4D992A012A5D002D202A /* ProfilePhotoSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D982A012A5D002D202A /* ProfilePhotoSelectorView.swift */; };
24FA4D9C2A012E1A002D202A /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D9B2A012E1A002D202A /* ImagePicker.swift */; }; 24FA4D9C2A012E1A002D202A /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D9B2A012E1A002D202A /* ImagePicker.swift */; };
24FA4D9E2A0134CB002D202A /* ImageUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FA4D9D2A0134CB002D202A /* ImageUploader.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 */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -74,6 +77,8 @@
24FA4D982A012A5D002D202A /* ProfilePhotoSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePhotoSelectorView.swift; sourceTree = "<group>"; }; 24FA4D982A012A5D002D202A /* ProfilePhotoSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePhotoSelectorView.swift; sourceTree = "<group>"; };
24FA4D9B2A012E1A002D202A /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = "<group>"; }; 24FA4D9B2A012E1A002D202A /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = "<group>"; };
24FA4D9D2A0134CB002D202A /* ImageUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploader.swift; sourceTree = "<group>"; }; 24FA4D9D2A0134CB002D202A /* ImageUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploader.swift; sourceTree = "<group>"; };
24FA4D9F2A013B24002D202A /* UserService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserService.swift; sourceTree = "<group>"; };
24FA4DA12A013D2E002D202A /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -85,6 +90,7 @@
24A59AF82A010411009C9E3E /* FirebaseStorage in Frameworks */, 24A59AF82A010411009C9E3E /* FirebaseStorage in Frameworks */,
24A59AF42A010411009C9E3E /* FirebaseFirestore in Frameworks */, 24A59AF42A010411009C9E3E /* FirebaseFirestore in Frameworks */,
24A59AF62A010411009C9E3E /* FirebaseFirestoreSwift in Frameworks */, 24A59AF62A010411009C9E3E /* FirebaseFirestoreSwift in Frameworks */,
24FA4DA52A0142E2002D202A /* Kingfisher in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -154,6 +160,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
24FA4D9D2A0134CB002D202A /* ImageUploader.swift */, 24FA4D9D2A0134CB002D202A /* ImageUploader.swift */,
24FA4D9F2A013B24002D202A /* UserService.swift */,
); );
path = Service; path = Service;
sourceTree = "<group>"; sourceTree = "<group>";
@ -161,6 +168,7 @@
2492CC1E2A0022970086C525 /* Model */ = { 2492CC1E2A0022970086C525 /* Model */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
24FA4DA12A013D2E002D202A /* User.swift */,
); );
path = Model; path = Model;
sourceTree = "<group>"; sourceTree = "<group>";
@ -413,6 +421,7 @@
24A59AF32A010411009C9E3E /* FirebaseFirestore */, 24A59AF32A010411009C9E3E /* FirebaseFirestore */,
24A59AF52A010411009C9E3E /* FirebaseFirestoreSwift */, 24A59AF52A010411009C9E3E /* FirebaseFirestoreSwift */,
24A59AF72A010411009C9E3E /* FirebaseStorage */, 24A59AF72A010411009C9E3E /* FirebaseStorage */,
24FA4DA42A0142E2002D202A /* Kingfisher */,
); );
productName = "dudu-tweet"; productName = "dudu-tweet";
productReference = 2492CC092A000EB00086C525 /* dudu-tweet.app */; productReference = 2492CC092A000EB00086C525 /* dudu-tweet.app */;
@ -444,6 +453,7 @@
mainGroup = 2492CC002A000EB00086C525; mainGroup = 2492CC002A000EB00086C525;
packageReferences = ( packageReferences = (
24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, 24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
24FA4DA32A0142E2002D202A /* XCRemoteSwiftPackageReference "Kingfisher" */,
); );
productRefGroup = 2492CC0A2A000EB00086C525 /* Products */; productRefGroup = 2492CC0A2A000EB00086C525 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -474,6 +484,7 @@
files = ( files = (
24A59ABE2A003108009C9E3E /* MessagesView.swift in Sources */, 24A59ABE2A003108009C9E3E /* MessagesView.swift in Sources */,
24A59AC22A003249009C9E3E /* ProfileView.swift in Sources */, 24A59AC22A003249009C9E3E /* ProfileView.swift in Sources */,
24FA4DA02A013B24002D202A /* UserService.swift in Sources */,
24FA4D9C2A012E1A002D202A /* ImagePicker.swift in Sources */, 24FA4D9C2A012E1A002D202A /* ImagePicker.swift in Sources */,
2492CC0F2A000EB00086C525 /* ContentView.swift in Sources */, 2492CC0F2A000EB00086C525 /* ContentView.swift in Sources */,
2492CC282A0025DD0086C525 /* TweetRowView.swift in Sources */, 2492CC282A0025DD0086C525 /* TweetRowView.swift in Sources */,
@ -491,6 +502,7 @@
24A59AE42A00EF1F009C9E3E /* LoginView.swift in Sources */, 24A59AE42A00EF1F009C9E3E /* LoginView.swift in Sources */,
24A59AE62A00EF3A009C9E3E /* RegistrationView.swift in Sources */, 24A59AE62A00EF3A009C9E3E /* RegistrationView.swift in Sources */,
24A59ADD2A00DB9F009C9E3E /* NewTweetView.swift in Sources */, 24A59ADD2A00DB9F009C9E3E /* NewTweetView.swift in Sources */,
24FA4DA22A013D2E002D202A /* User.swift in Sources */,
24A59AED2A00F942009C9E3E /* AuthHeaderView.swift in Sources */, 24A59AED2A00F942009C9E3E /* AuthHeaderView.swift in Sources */,
24A59AFA2A01081F009C9E3E /* AuthViewModel.swift in Sources */, 24A59AFA2A01081F009C9E3E /* AuthViewModel.swift in Sources */,
24A59AE82A00F106009C9E3E /* RoundedShape.swift in Sources */, 24A59AE82A00F106009C9E3E /* RoundedShape.swift in Sources */,
@ -721,6 +733,14 @@
minimumVersion = 9.0.0; 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 */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
@ -744,6 +764,11 @@
package = 24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; package = 24A59AF02A010411009C9E3E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseStorage; productName = FirebaseStorage;
}; };
24FA4DA42A0142E2002D202A /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = 24FA4DA32A0142E2002D202A /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = Kingfisher;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
}; };
rootObject = 2492CC012A000EB00086C525 /* Project object */; rootObject = 2492CC012A000EB00086C525 /* Project object */;

View File

@ -72,6 +72,15 @@
"version" : "2.3.0" "version" : "2.3.0"
} }
}, },
{
"identity" : "kingfisher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/onevcat/Kingfisher.git",
"state" : {
"revision" : "af4be924ad984cf4d16f4ae4df424e79a443d435",
"version" : "7.6.2"
}
},
{ {
"identity" : "leveldb", "identity" : "leveldb",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import Kingfisher
struct ContentView: View { struct ContentView: View {
@State private var showMenu = false @State private var showMenu = false
@ -58,13 +59,18 @@ extension ContentView {
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarLeading) { ToolbarItem(placement: .navigationBarLeading) {
Button { if let user = viewModel.currentUser {
withAnimation(.easeInOut) { Button {
showMenu.toggle() 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)
} }
} }
} }

View File

@ -11,11 +11,14 @@ import SwiftUI
class AuthViewModel: ObservableObject { class AuthViewModel: ObservableObject {
@Published var userSession: FirebaseAuth.User? @Published var userSession: FirebaseAuth.User?
@Published var didAuthenticateUser = false @Published var didAuthenticateUser = false
@Published var currentUser: User?
private var tempUserSession: FirebaseAuth.User? private var tempUserSession: FirebaseAuth.User?
private let service = UserService()
init() { init() {
self.userSession = Auth.auth().currentUser self.userSession = Auth.auth().currentUser
self.fetchUser()
print("DEBUG: user session is \(self.userSession?.uid)") print("DEBUG: user session is \(self.userSession?.uid)")
} }
@ -29,6 +32,7 @@ class AuthViewModel: ObservableObject {
guard let user = result?.user else { return } guard let user = result?.user else { return }
self.userSession = user self.userSession = user
self.fetchUser()
print("DEBUG: Did log user \(user.uid) in.") print("DEBUG: Did log user \(user.uid) in.")
} }
@ -47,8 +51,7 @@ class AuthViewModel: ObservableObject {
let data = ["email": email, let data = ["email": email,
"username": username.lowercased(), "username": username.lowercased(),
"fullname": fullname, "fullname": fullname]
"uid": user.uid]
Firestore.firestore().collection("users") Firestore.firestore().collection("users")
.document(user.uid) .document(user.uid)
.setData(data) { _ in .setData(data) { _ in
@ -71,7 +74,15 @@ class AuthViewModel: ObservableObject {
.document(uid) .document(uid)
.updateData(["profileImageUrl": profileImageUrl]) { _ in .updateData(["profileImageUrl": profileImageUrl]) { _ in
self.userSession = self.tempUserSession 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
}
}
} }

View File

@ -15,7 +15,7 @@ struct ExploreView: View {
LazyVStack { LazyVStack {
ForEach(0...25, id: \.self) { _ in ForEach(0...25, id: \.self) { _ in
NavigationLink { NavigationLink {
ProfileView() // ProfileView()
} label: { } label: {
UserRowView() UserRowView()
} }

View File

@ -6,14 +6,20 @@
// //
import SwiftUI import SwiftUI
import Kingfisher
struct ProfileView: View { struct ProfileView: View {
@State private var selectedFilter: TweetFilterViewModel = .tweets @State private var selectedFilter: TweetFilterViewModel = .tweets
// @Environment(\.presentationMode) var mode // explore // @Environment(\.presentationMode) var mode // explore
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
private let user: User
@Namespace var animation @Namespace var animation
init(user: User) {
self.user = user
}
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
headerView headerView
@ -28,12 +34,17 @@ struct ProfileView: View {
Spacer() Spacer()
} }
.toolbar(.hidden)
} }
} }
struct ProfileView_Previews: PreviewProvider { struct ProfileView_Previews: PreviewProvider {
static var previews: some View { 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() .resizable()
.frame(width: 20, height: 16) .frame(width: 20, height: 16)
.foregroundColor(.white) .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) .frame(width: 72, height: 72)
.offset(x: 16, y: 24) .offset(x: 16, y: 24)
} }
@ -86,12 +100,12 @@ extension ProfileView {
var userInfoDetails: some View { var userInfoDetails: some View {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
HStack { HStack {
Text("Zhang San") Text(user.fullname)
.font(.title2).bold() .font(.title2).bold()
Image(systemName: "checkmark.seal.fill") Image(systemName: "checkmark.seal.fill")
.foregroundColor(Color(.systemBlue)) .foregroundColor(Color(.systemBlue))
} }
Text("@zhang3") Text("@\(user.username)")
.font(.subheadline) .font(.subheadline)
.foregroundColor(.gray) .foregroundColor(.gray)
Text("一个普通人") Text("一个普通人")

View File

@ -6,50 +6,56 @@
// //
import SwiftUI import SwiftUI
import Kingfisher
struct SideMenuView: View { struct SideMenuView: View {
@EnvironmentObject var authViewModel: AuthViewModel @EnvironmentObject var authViewModel: AuthViewModel
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 32) { if let user = authViewModel.currentUser {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 32) {
Circle() VStack(alignment: .leading) {
.frame(width: 48, height: 48) KFImage(URL(string: user.profileImageUrl))
.resizable()
.scaledToFill()
.clipShape(Circle())
.frame(width: 48, height: 48)
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text("张三") Text(user.fullname)
.font(.headline) .font(.headline)
Text("@zhang3") Text("@\(user.username)")
.font(.caption) .font(.caption)
.foregroundColor(.gray) .foregroundColor(.gray)
}
UserStatsView()
.padding(.vertical)
}
.padding(.leading)
ForEach(SideMenuViewModel.allCases, id: \.rawValue) { viewModel in
if viewModel == .profile {
NavigationLink {
ProfileView()
} label: {
SideMenuOptionRowView(viewModel: viewModel)
}
} else if viewModel == .logout {
Button {
authViewModel.signOut()
} label: {
SideMenuOptionRowView(viewModel: viewModel)
} }
} else { UserStatsView()
SideMenuOptionRowView(viewModel: viewModel) .padding(.vertical)
} }
} .padding(.leading)
Spacer() 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)
}
}
Spacer()
}
} }
} }
} }

View File

@ -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
}

View File

@ -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)
}
}
}