feat(Views): 增加 profileImageUpload page,增加图片上传功能
增加 profileImageUpload page,增加图片上传功能 Signed-off-by: Ching <loooching@gmail.com>
This commit is contained in:
parent
698f18624c
commit
3eb9b13f86
@ -36,6 +36,10 @@
|
|||||||
24A59AF42A010411009C9E3E /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 24A59AF32A010411009C9E3E /* FirebaseFirestore */; };
|
24A59AF42A010411009C9E3E /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = 24A59AF32A010411009C9E3E /* FirebaseFirestore */; };
|
||||||
24A59AF62A010411009C9E3E /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 24A59AF52A010411009C9E3E /* FirebaseFirestoreSwift */; };
|
24A59AF62A010411009C9E3E /* FirebaseFirestoreSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 24A59AF52A010411009C9E3E /* FirebaseFirestoreSwift */; };
|
||||||
24A59AF82A010411009C9E3E /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 24A59AF72A010411009C9E3E /* FirebaseStorage */; };
|
24A59AF82A010411009C9E3E /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 24A59AF72A010411009C9E3E /* FirebaseStorage */; };
|
||||||
|
24A59AFA2A01081F009C9E3E /* AuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A59AF92A01081F009C9E3E /* AuthViewModel.swift */; };
|
||||||
|
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 */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@ -66,6 +70,10 @@
|
|||||||
24A59AE92A00F672009C9E3E /* CustomInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomInputField.swift; sourceTree = "<group>"; };
|
24A59AE92A00F672009C9E3E /* CustomInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomInputField.swift; sourceTree = "<group>"; };
|
||||||
24A59AEC2A00F942009C9E3E /* AuthHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthHeaderView.swift; sourceTree = "<group>"; };
|
24A59AEC2A00F942009C9E3E /* AuthHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthHeaderView.swift; sourceTree = "<group>"; };
|
||||||
24A59AEE2A010142009C9E3E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../GoogleService-Info.plist"; sourceTree = "<group>"; };
|
24A59AEE2A010142009C9E3E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||||
|
24A59AF92A01081F009C9E3E /* AuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewModel.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>"; };
|
||||||
|
24FA4D9D2A0134CB002D202A /* ImageUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUploader.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -102,6 +110,7 @@
|
|||||||
2492CC0B2A000EB00086C525 /* dudu-tweet */ = {
|
2492CC0B2A000EB00086C525 /* dudu-tweet */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
24FA4D9A2A012E05002D202A /* Utils */,
|
||||||
2492CC1F2A0022990086C525 /* Extensions */,
|
2492CC1F2A0022990086C525 /* Extensions */,
|
||||||
2492CC1E2A0022970086C525 /* Model */,
|
2492CC1E2A0022970086C525 /* Model */,
|
||||||
2492CC1D2A0022960086C525 /* Service */,
|
2492CC1D2A0022960086C525 /* Service */,
|
||||||
@ -144,6 +153,7 @@
|
|||||||
2492CC1D2A0022960086C525 /* Service */ = {
|
2492CC1D2A0022960086C525 /* Service */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
24FA4D9D2A0134CB002D202A /* ImageUploader.swift */,
|
||||||
);
|
);
|
||||||
path = Service;
|
path = Service;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -353,6 +363,7 @@
|
|||||||
children = (
|
children = (
|
||||||
24A59AE32A00EF1F009C9E3E /* LoginView.swift */,
|
24A59AE32A00EF1F009C9E3E /* LoginView.swift */,
|
||||||
24A59AE52A00EF3A009C9E3E /* RegistrationView.swift */,
|
24A59AE52A00EF3A009C9E3E /* RegistrationView.swift */,
|
||||||
|
24FA4D982A012A5D002D202A /* ProfilePhotoSelectorView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -360,6 +371,7 @@
|
|||||||
24A59AE22A00EEF8009C9E3E /* ViewModels */ = {
|
24A59AE22A00EEF8009C9E3E /* ViewModels */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
24A59AF92A01081F009C9E3E /* AuthViewModel.swift */,
|
||||||
);
|
);
|
||||||
path = ViewModels;
|
path = ViewModels;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -372,6 +384,14 @@
|
|||||||
path = Authentication;
|
path = Authentication;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
24FA4D9A2A012E05002D202A /* Utils */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
24FA4D9B2A012E1A002D202A /* ImagePicker.swift */,
|
||||||
|
);
|
||||||
|
path = Utils;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@ -454,12 +474,15 @@
|
|||||||
files = (
|
files = (
|
||||||
24A59ABE2A003108009C9E3E /* MessagesView.swift in Sources */,
|
24A59ABE2A003108009C9E3E /* MessagesView.swift in Sources */,
|
||||||
24A59AC22A003249009C9E3E /* ProfileView.swift in Sources */,
|
24A59AC22A003249009C9E3E /* ProfileView.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 */,
|
||||||
|
24FA4D9E2A0134CB002D202A /* ImageUploader.swift in Sources */,
|
||||||
24A59AEA2A00F672009C9E3E /* CustomInputField.swift in Sources */,
|
24A59AEA2A00F672009C9E3E /* CustomInputField.swift in Sources */,
|
||||||
24A59AC42A003A52009C9E3E /* TwewtFilterViewModel.swift in Sources */,
|
24A59AC42A003A52009C9E3E /* TwewtFilterViewModel.swift in Sources */,
|
||||||
2492CC252A0023220086C525 /* FeedView.swift in Sources */,
|
2492CC252A0023220086C525 /* FeedView.swift in Sources */,
|
||||||
24A59ACE2A00BDCB009C9E3E /* SideMenuView.swift in Sources */,
|
24A59ACE2A00BDCB009C9E3E /* SideMenuView.swift in Sources */,
|
||||||
|
24FA4D992A012A5D002D202A /* ProfilePhotoSelectorView.swift in Sources */,
|
||||||
2492CC0D2A000EB00086C525 /* dudu_tweetApp.swift in Sources */,
|
2492CC0D2A000EB00086C525 /* dudu_tweetApp.swift in Sources */,
|
||||||
24A59ABA2A0030CB009C9E3E /* ExploreView.swift in Sources */,
|
24A59ABA2A0030CB009C9E3E /* ExploreView.swift in Sources */,
|
||||||
24A59AD22A00BE14009C9E3E /* SideMenuViewModel.swift in Sources */,
|
24A59AD22A00BE14009C9E3E /* SideMenuViewModel.swift in Sources */,
|
||||||
@ -469,6 +492,7 @@
|
|||||||
24A59AE62A00EF3A009C9E3E /* RegistrationView.swift in Sources */,
|
24A59AE62A00EF3A009C9E3E /* RegistrationView.swift in Sources */,
|
||||||
24A59ADD2A00DB9F009C9E3E /* NewTweetView.swift in Sources */,
|
24A59ADD2A00DB9F009C9E3E /* NewTweetView.swift in Sources */,
|
||||||
24A59AED2A00F942009C9E3E /* AuthHeaderView.swift in Sources */,
|
24A59AED2A00F942009C9E3E /* AuthHeaderView.swift in Sources */,
|
||||||
|
24A59AFA2A01081F009C9E3E /* AuthViewModel.swift in Sources */,
|
||||||
24A59AE82A00F106009C9E3E /* RoundedShape.swift in Sources */,
|
24A59AE82A00F106009C9E3E /* RoundedShape.swift in Sources */,
|
||||||
24A59AC92A00BA81009C9E3E /* UserRowView.swift in Sources */,
|
24A59AC92A00BA81009C9E3E /* UserRowView.swift in Sources */,
|
||||||
24A59AB42A002EB8009C9E3E /* MainTabView.swift in Sources */,
|
24A59AB42A002EB8009C9E3E /* MainTabView.swift in Sources */,
|
||||||
|
|||||||
Binary file not shown.
@ -9,11 +9,32 @@ import SwiftUI
|
|||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@State private var showMenu = false
|
@State private var showMenu = false
|
||||||
|
@EnvironmentObject var viewModel: AuthViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
// no user loggin
|
||||||
|
if viewModel.userSession == nil {
|
||||||
|
LoginView()
|
||||||
|
} else {
|
||||||
|
// have a logged in user
|
||||||
|
mainInterfaceView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension ContentView {
|
||||||
|
var mainInterfaceView: some View {
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
MainTabView()
|
MainTabView()
|
||||||
// .navigationBarHidden(showMenu)
|
|
||||||
.toolbar(showMenu ? .hidden : .visible)
|
.toolbar(showMenu ? .hidden : .visible)
|
||||||
|
|
||||||
if showMenu {
|
if showMenu {
|
||||||
@ -52,9 +73,3 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ContentView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
ContentView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -0,0 +1,77 @@
|
|||||||
|
//
|
||||||
|
// AuthViewModel.swift
|
||||||
|
// dudu-tweet
|
||||||
|
//
|
||||||
|
// Created by ching on 2023/5/2.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Firebase
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
class AuthViewModel: ObservableObject {
|
||||||
|
@Published var userSession: FirebaseAuth.User?
|
||||||
|
@Published var didAuthenticateUser = false
|
||||||
|
private var tempUserSession: FirebaseAuth.User?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.userSession = Auth.auth().currentUser
|
||||||
|
|
||||||
|
print("DEBUG: user session is \(self.userSession?.uid)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func login(withEmail email: String, password: String) {
|
||||||
|
print("DEBUG: login with email \(email)")
|
||||||
|
Auth.auth().signIn(withEmail: email, password: password) { result, error in
|
||||||
|
if let error = error {
|
||||||
|
print("DEBUG: Failed to sign in with error \(error.localizedDescription)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let user = result?.user else { return }
|
||||||
|
self.userSession = user
|
||||||
|
|
||||||
|
print("DEBUG: Did log user \(user.uid) in.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func register(withEmail email: String, password: String, fullname: String, username: String) {
|
||||||
|
print("DEBUG: register with email \(email)")
|
||||||
|
Auth.auth().createUser(withEmail: email, password: password) { result, error in
|
||||||
|
if let error = error {
|
||||||
|
print("DEBUG: Failed to register with error \(error.localizedDescription)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let user = result?.user else { return }
|
||||||
|
self.tempUserSession = user
|
||||||
|
|
||||||
|
let data = ["email": email,
|
||||||
|
"username": username.lowercased(),
|
||||||
|
"fullname": fullname,
|
||||||
|
"uid": user.uid]
|
||||||
|
Firestore.firestore().collection("users")
|
||||||
|
.document(user.uid)
|
||||||
|
.setData(data) { _ in
|
||||||
|
print("DEBUG: setData completion block called...")
|
||||||
|
self.didAuthenticateUser = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func signOut() {
|
||||||
|
userSession = nil
|
||||||
|
try? Auth.auth().signOut()
|
||||||
|
}
|
||||||
|
|
||||||
|
func uploadProfileImage(_ image: UIImage) {
|
||||||
|
guard let uid = tempUserSession?.uid else { return }
|
||||||
|
|
||||||
|
ImageUploader.uploadImage(image: image) { profileImageUrl in
|
||||||
|
Firestore.firestore().collection("users")
|
||||||
|
.document(uid)
|
||||||
|
.updateData(["profileImageUrl": profileImageUrl]) { _ in
|
||||||
|
self.userSession = self.tempUserSession
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ struct LoginView: View {
|
|||||||
@State private var email = ""
|
@State private var email = ""
|
||||||
@State private var password = ""
|
@State private var password = ""
|
||||||
|
|
||||||
|
@EnvironmentObject var viewModel: AuthViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
@ -22,6 +23,7 @@ struct LoginView: View {
|
|||||||
|
|
||||||
CustomInputField(imageName: "lock",
|
CustomInputField(imageName: "lock",
|
||||||
palceholderText: "Password",
|
palceholderText: "Password",
|
||||||
|
isSecureField: true,
|
||||||
text: $password)
|
text: $password)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 32)
|
.padding(.horizontal, 32)
|
||||||
@ -42,7 +44,7 @@ struct LoginView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
print("Sign in here")
|
viewModel.login(withEmail: email, password: password)
|
||||||
} label: {
|
} label: {
|
||||||
Text("Sign In")
|
Text("Sign In")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
|
|||||||
@ -0,0 +1,82 @@
|
|||||||
|
//
|
||||||
|
// ProfilePhotoSelectorView.swift
|
||||||
|
// dudu-tweet
|
||||||
|
//
|
||||||
|
// Created by ching on 2023/5/2.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ProfilePhotoSelectorView: View {
|
||||||
|
@State private var showImagePicker = false
|
||||||
|
@State private var selectedImage: UIImage?
|
||||||
|
@State private var profileImage: Image?
|
||||||
|
@EnvironmentObject var viewModel: AuthViewModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
AuthHeaderView(title1: "Create your account", title2: "Add a profile photo")
|
||||||
|
|
||||||
|
Button {
|
||||||
|
print("Pick image here..")
|
||||||
|
showImagePicker.toggle()
|
||||||
|
} label: {
|
||||||
|
if let profileImage = profileImage {
|
||||||
|
profileImage
|
||||||
|
.resizable()
|
||||||
|
.modifier(ProfileImageModifier())
|
||||||
|
} else {
|
||||||
|
Image(systemName: "plus.circle")
|
||||||
|
.resizable()
|
||||||
|
.modifier(ProfileImageModifier())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $showImagePicker, onDismiss: loadImage) {
|
||||||
|
ImagePicker(selectedImage: $selectedImage)
|
||||||
|
}
|
||||||
|
.padding(.top, 44)
|
||||||
|
|
||||||
|
if let selectedImage = selectedImage {
|
||||||
|
Button {
|
||||||
|
print("DEBUG: Finishing register user")
|
||||||
|
viewModel.uploadProfileImage(selectedImage)
|
||||||
|
} label: {
|
||||||
|
Text("Continue")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(width: 340, height: 50)
|
||||||
|
.background(Color(.systemBlue))
|
||||||
|
.clipShape(Capsule())
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.shadow(color: .gray.opacity(0.5), radius: 10, x:0, y:0)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImage() {
|
||||||
|
guard let selectedImage = selectedImage else { return }
|
||||||
|
profileImage = Image(uiImage: selectedImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ProfileImageModifier: ViewModifier {
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.foregroundColor(Color(.systemBlue))
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 180, height: 180)
|
||||||
|
.clipShape(Circle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ProfilePhotoSelectorView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
ProfilePhotoSelectorView()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,12 +15,17 @@ struct RegistrationView: View {
|
|||||||
|
|
||||||
// @Environment(\.presentationMode) var presentationMode
|
// @Environment(\.presentationMode) var presentationMode
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
@EnvironmentObject var viewModel: AuthViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
AuthHeaderView(title1: "Get started.",
|
AuthHeaderView(title1: "Get started.",
|
||||||
title2: "Create your account")
|
title2: "Create your account")
|
||||||
|
|
||||||
|
NavigationLink(destination: ProfilePhotoSelectorView(),
|
||||||
|
isActive: $viewModel.didAuthenticateUser,
|
||||||
|
label: {})
|
||||||
|
|
||||||
VStack(spacing: 40) {
|
VStack(spacing: 40) {
|
||||||
CustomInputField(imageName: "envelope",
|
CustomInputField(imageName: "envelope",
|
||||||
palceholderText: "Email",
|
palceholderText: "Email",
|
||||||
@ -36,12 +41,16 @@ struct RegistrationView: View {
|
|||||||
|
|
||||||
CustomInputField(imageName: "lock",
|
CustomInputField(imageName: "lock",
|
||||||
palceholderText: "Password",
|
palceholderText: "Password",
|
||||||
|
isSecureField: true,
|
||||||
text: $password)
|
text: $password)
|
||||||
}
|
}
|
||||||
.padding(32)
|
.padding(32)
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
print("Sign up here")
|
viewModel.register(withEmail: email,
|
||||||
|
password: password,
|
||||||
|
fullname: fullname,
|
||||||
|
username: username)
|
||||||
} label: {
|
} label: {
|
||||||
Text("Sign Up")
|
Text("Sign Up")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import SwiftUI
|
|||||||
struct CustomInputField: View {
|
struct CustomInputField: View {
|
||||||
let imageName: String
|
let imageName: String
|
||||||
let palceholderText: String
|
let palceholderText: String
|
||||||
|
var isSecureField: Bool? = false
|
||||||
@Binding var text: String
|
@Binding var text: String
|
||||||
|
|
||||||
|
|
||||||
@ -21,8 +22,12 @@ struct CustomInputField: View {
|
|||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.foregroundColor(Color(.darkGray))
|
.foregroundColor(Color(.darkGray))
|
||||||
|
if isSecureField ?? false {
|
||||||
|
SecureField(palceholderText, text: $text)
|
||||||
|
} else {
|
||||||
|
TextField(palceholderText, text: $text)
|
||||||
|
}
|
||||||
|
|
||||||
TextField(palceholderText, text: $text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|||||||
@ -8,6 +8,9 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct SideMenuView: View {
|
struct SideMenuView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject var authViewModel: AuthViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 32) {
|
VStack(alignment: .leading, spacing: 32) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
@ -36,7 +39,7 @@ struct SideMenuView: View {
|
|||||||
}
|
}
|
||||||
} else if viewModel == .logout {
|
} else if viewModel == .logout {
|
||||||
Button {
|
Button {
|
||||||
print("handle logout here")
|
authViewModel.signOut()
|
||||||
} label: {
|
} label: {
|
||||||
SideMenuOptionRowView(viewModel: viewModel)
|
SideMenuOptionRowView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
|
|||||||
31
dudu-tweet/dudu-tweet/Service/ImageUploader.swift
Normal file
31
dudu-tweet/dudu-tweet/Service/ImageUploader.swift
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// ImageUploader.swift
|
||||||
|
// dudu-tweet
|
||||||
|
//
|
||||||
|
// Created by ching on 2023/5/2.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Firebase
|
||||||
|
import FirebaseStorage
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct ImageUploader {
|
||||||
|
static func uploadImage(image: UIImage, completion: @escaping (String) -> Void) {
|
||||||
|
guard let imageData = image.jpegData(compressionQuality: 0.5) else { return }
|
||||||
|
|
||||||
|
let filename = NSUUID().uuidString
|
||||||
|
let ref = Storage.storage().reference(withPath: "/profile_image/\(filename)")
|
||||||
|
|
||||||
|
ref.putData(imageData, metadata: nil) { _, error in
|
||||||
|
if let error = error {
|
||||||
|
print("DEBUG: Failed to upload image with error \(error.localizedDescription)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.downloadURL { imageUrl, _ in
|
||||||
|
guard let imageUrl = imageUrl?.absoluteURL else { return }
|
||||||
|
completion(imageUrl.absoluteString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
dudu-tweet/dudu-tweet/Utils/ImagePicker.swift
Normal file
44
dudu-tweet/dudu-tweet/Utils/ImagePicker.swift
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// ImagePicker.swift
|
||||||
|
// dudu-tweet
|
||||||
|
//
|
||||||
|
// Created by ching on 2023/5/2.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ImagePicker: UIViewControllerRepresentable {
|
||||||
|
@Binding var selectedImage: UIImage?
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
return Coordinator(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeUIViewController(context: Context) -> some UIViewController {
|
||||||
|
let picker = UIImagePickerController()
|
||||||
|
picker.delegate = context.coordinator
|
||||||
|
return picker
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImagePicker {
|
||||||
|
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
|
||||||
|
|
||||||
|
let parent: ImagePicker
|
||||||
|
|
||||||
|
init(_ parent: ImagePicker) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||||||
|
guard let image = info[.originalImage] as? UIImage else { return }
|
||||||
|
parent.selectedImage = image
|
||||||
|
parent.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,6 +11,9 @@ import Firebase
|
|||||||
@main
|
@main
|
||||||
struct dudu_tweetApp: App {
|
struct dudu_tweetApp: App {
|
||||||
|
|
||||||
|
@StateObject var viewModel = AuthViewModel()
|
||||||
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
FirebaseApp.configure()
|
FirebaseApp.configure()
|
||||||
}
|
}
|
||||||
@ -19,8 +22,9 @@ struct dudu_tweetApp: App {
|
|||||||
WindowGroup {
|
WindowGroup {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ContentView()
|
ContentView()
|
||||||
// LoginView()
|
// ProfilePhotoSelectorView()
|
||||||
}
|
}
|
||||||
|
.environmentObject(viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user