From 13b931b0fb77de487a417c39eb2acad42b43a8d3 Mon Sep 17 00:00:00 2001 From: ShouravRakshit Date: Thu, 6 Feb 2025 23:31:05 -0700 Subject: [PATCH 1/4] changed the color of the title, gender,bio in the card --- FlatMate/Components/ProfileCardView.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/FlatMate/Components/ProfileCardView.swift b/FlatMate/Components/ProfileCardView.swift index d229062..665488c 100644 --- a/FlatMate/Components/ProfileCardView.swift +++ b/FlatMate/Components/ProfileCardView.swift @@ -114,9 +114,12 @@ struct ProfileCardView: View { VStack(alignment: .leading) { Text("\(profile.firstName), \(profile.age)") .font(.custom("Outfit-Bold", size: 32)) - .colorInvert() - Text(profile.gender).colorInvert() - Text("\(profile.roomState) in \(profile.location)").colorInvert() + .fontWeight(.bold) + .lineLimit(1) + .foregroundColor(.black) + + Text(profile.gender).foregroundColor(.black) + Text("\(profile.roomState) in \(profile.location)").foregroundColor(.black) } .padding() .background(.ultraThinMaterial) From 9b6712375d8aa82fcb5544840e17352686af938f Mon Sep 17 00:00:00 2001 From: ShouravRakshit Date: Thu, 6 Feb 2025 23:51:47 -0700 Subject: [PATCH 2/4] fixed the update of pet and noise tolerance --- FlatMate/Models/User.swift | 4 ++-- FlatMate/View/EditProfileView.swift | 10 +++++----- FlatMate/ViewModel/AuthViewModel.swift | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/FlatMate/Models/User.swift b/FlatMate/Models/User.swift index bab7358..a2d221b 100644 --- a/FlatMate/Models/User.swift +++ b/FlatMate/Models/User.swift @@ -16,11 +16,11 @@ struct User: Identifiable, Codable { var dob: Date? var bio: String? var isSmoker: Bool? - var pets: Bool? + var petsOk: Bool? var gender: String? var partyFrequency: String? var guestFrequency: String? - var noiseTolerance: Double? + var noise: Double? var profileImageURL: String? let hasCompletedOnboarding: Bool } diff --git a/FlatMate/View/EditProfileView.swift b/FlatMate/View/EditProfileView.swift index 8367339..30d658d 100644 --- a/FlatMate/View/EditProfileView.swift +++ b/FlatMate/View/EditProfileView.swift @@ -26,7 +26,7 @@ struct EditProfileView: View { @State private var selectedGender: String = genders[0] @State private var selectedPartyFrequency: String = frequencies[0] @State private var selectedGuestFrequency: String = frequencies[0] - @State private var noiseTolerance: Double = 0.0 + @State private var noise: Double = 0.0 @State private var profileImage: UIImage? = nil // @State private var isImagePickerPresented = false @State private var errorMessage: String? @@ -138,7 +138,7 @@ struct EditProfileView: View { .font(.custom("Outfit-Bold", fixedSize: 15)) HStack { Text("Quiet") - Slider(value: $noiseTolerance, in: 0...5, step: 0.1) + Slider(value: $noise, in: 0...5, step: 0.1) .accentColor(Color("primary")) Text("Loud") } @@ -219,7 +219,7 @@ struct EditProfileView: View { selectedGender = data["gender"] as? String ?? genders[0] selectedPartyFrequency = data["partyFrequency"] as? String ?? frequencies[0] selectedGuestFrequency = data["guestFrequency"] as? String ?? frequencies[0] - noiseTolerance = data["noise"] as? Double ?? 0.0 + noise = data["noise"] as? Double ?? 0.0 if let imageURLString = data["profileImageURL"] as? String, let url = URL(string: imageURLString) { @@ -250,11 +250,11 @@ struct EditProfileView: View { age: age, // Send age to backend bio: bio, isSmoker: isSmoker, - pets: petsOk, + petsOk: petsOk, gender: selectedGender, partyFrequency: selectedPartyFrequency, guestFrequency: selectedGuestFrequency, - noiseTolerance: noiseTolerance, + noise: noise, profileImage: profileImage ) errorMessage = nil diff --git a/FlatMate/ViewModel/AuthViewModel.swift b/FlatMate/ViewModel/AuthViewModel.swift index 06e7803..262582b 100644 --- a/FlatMate/ViewModel/AuthViewModel.swift +++ b/FlatMate/ViewModel/AuthViewModel.swift @@ -153,11 +153,11 @@ class AuthViewModel: ObservableObject { age: Int, bio: String, isSmoker: Bool, - pets: Bool, + petsOk: Bool, gender: String, partyFrequency: String, guestFrequency: String, - noiseTolerance: Double, + noise: Double, profileImage: UIImage? ) async throws { guard let uid = userSession?.uid else { @@ -174,11 +174,11 @@ class AuthViewModel: ObservableObject { "age": age, "bio": bio, "isSmoker": isSmoker, - "pets": pets, + "petsOk": petsOk, "gender": gender, "partyFrequency": partyFrequency, "guestFrequency": guestFrequency, - "noiseTolerance": noiseTolerance + "noise": noise ] do { @@ -208,11 +208,11 @@ class AuthViewModel: ObservableObject { currentUser.dob = dob currentUser.bio = bio currentUser.isSmoker = isSmoker - currentUser.pets = pets + currentUser.petsOk = petsOk currentUser.gender = gender currentUser.partyFrequency = partyFrequency currentUser.guestFrequency = guestFrequency - currentUser.noiseTolerance = noiseTolerance + currentUser.noise = noise if let url = updatedData["profileImageURL"] as? String { currentUser.profileImageURL = url } From fd5f4ce69c38f23fc960e861591750bce2a6bd9a Mon Sep 17 00:00:00 2001 From: ShouravRakshit Date: Fri, 7 Feb 2025 00:27:58 -0700 Subject: [PATCH 3/4] added AsyncImage logic for showing image --- FlatMate/Components/ProfileCardView.swift | 162 ++++++++++++++-------- 1 file changed, 106 insertions(+), 56 deletions(-) diff --git a/FlatMate/Components/ProfileCardView.swift b/FlatMate/Components/ProfileCardView.swift index 665488c..4fc712e 100644 --- a/FlatMate/Components/ProfileCardView.swift +++ b/FlatMate/Components/ProfileCardView.swift @@ -5,13 +5,17 @@ // Created by Ben Schmidt on 2024-10-20. // + + import SwiftUI struct ProfileCardView: View { + enum SwipeDirection { case left, right, none } + // The data model for each card struct Model: Identifiable, Equatable, Codable { let id: String let firstName: String @@ -20,12 +24,14 @@ struct ProfileCardView: View { let bio: String let roomState: String let location: String - let profileImageURL: String? // Now optional + let profileImageURL: String? let isSmoker: Bool let petsOk: Bool let noiseTolerance: Double let partyFrequency: String let guestFrequency: String + + // We store swipeDirection (left, right, none) so we can track or animate it var swipeDirection: SwipeDirection = .none enum CodingKeys: String, CodingKey { @@ -45,72 +51,64 @@ struct ProfileCardView: View { } } - private func renderTextWithEmoji(_ emoji: String, _ text: String) -> HStack> { - HStack(alignment: .top, spacing: 15) { - Text(emoji) - Text(text) - } - } - - private func renderNoiseTolerance(_ noiseTolerance: Double) -> HStack> { - if noiseTolerance < 3 { - return renderTextWithEmoji(noiseTolerance > 0 ? "✅" : "đŸšĢ", "Noise") - } - return renderTextWithEmoji("🔊", "Unbothered by noise") - } - - private func renderPartying(_ partyFrequency: String) -> HStack> { - switch partyFrequency { - case "Always": - return renderTextWithEmoji("đŸĒŠ", "Party Animal") - case "Sometimes": - return renderTextWithEmoji("✅", "Partying") - default: - return renderTextWithEmoji("đŸšĢ", "No Parties") - } - } - - private func renderGuests(_ guestFrequency: String) -> HStack> { - switch guestFrequency { - case "Always": - return renderTextWithEmoji("🧑‍🤝‍🧑", "Loves Guests") - case "Sometimes": - return renderTextWithEmoji("✅", "Occasional Guests") - default: - return renderTextWithEmoji("đŸšĢ", "No Guests") - } - } - - var profile: Model - var size: CGSize - var dragOffset: CGSize - var isTopCard: Bool - var isSecondCard: Bool + let profile: Model + let size: CGSize + let dragOffset: CGSize + let isTopCard: Bool + let isSecondCard: Bool + var body: some View { VStack(alignment: .leading) { + + // The top image portion ZStack(alignment: .bottomLeading) { - if let profileImageURL = profile.profileImageURL, let url = URL(string: profileImageURL) { - AsyncImage(url: url) { image in - image - .resizable() - .scaledToFill() - .frame(width: size.width, height: size.height * 0.65) - .clipShape(Rectangle()) - } placeholder: { - ProgressView() + + // Display a spinner placeholder while it loads, then fade it in. + if let profileImageURL = profile.profileImageURL, + let url = URL(string: profileImageURL) { + + AsyncImage(url: url, transaction: Transaction(animation: .easeIn)) { phase in + switch phase { + case .empty: + // Show a loading spinner while fetching + ProgressView() + .frame(width: size.width, height: size.height * 0.65) + .background(Color.gray.opacity(0.3)) + + case .success(let image): + image + .resizable() + .scaledToFill() + .frame(width: size.width, height: size.height * 0.65) + .clipShape(Rectangle()) + .transition(.opacity) + + case .failure: + // If the image fails to load, show a placeholder + Image(systemName: "person.crop.circle.fill") + .resizable() + .scaledToFit() + .frame(width: size.width, height: size.height * 0.65) + .foregroundColor(.gray) + .background(Color.gray.opacity(0.2)) + + @unknown default: + EmptyView() + } } + } else { - // Default placeholder for users without a profile image + // if there's no profileImageURL, show a default placeholder Image(systemName: "person.crop.circle.fill") .resizable() .scaledToFit() .frame(width: size.width, height: size.height * 0.65) .foregroundColor(.gray) .background(Color.gray.opacity(0.2)) - .clipShape(Rectangle()) } + // The overlay: name, age, location, etc. inside a thin blur VStack(alignment: .leading) { Text("\(profile.firstName), \(profile.age)") .font(.custom("Outfit-Bold", size: 32)) @@ -118,13 +116,17 @@ struct ProfileCardView: View { .lineLimit(1) .foregroundColor(.black) - Text(profile.gender).foregroundColor(.black) - Text("\(profile.roomState) in \(profile.location)").foregroundColor(.black) + Text(profile.gender) + .foregroundColor(.black) + + Text("\(profile.roomState) in \(profile.location)") + .foregroundColor(.black) } .padding() .background(.ultraThinMaterial) } + // bio and icons VStack(alignment: .leading, spacing: 6) { renderTextWithEmoji("đŸ—Ŗī¸", "“\(profile.bio)“") .padding(.bottom) @@ -136,11 +138,20 @@ struct ProfileCardView: View { } .padding() } + // Card styling .background(Color.white) .cornerRadius(20) - .shadow(color: isTopCard ? getShadowColor() : (isSecondCard && dragOffset.width != 0 ? Color.gray.opacity(0.2) : Color.clear), radius: 10, x: 0, y: 3) + .shadow( + color: isTopCard + ? getShadowColor() + : (isSecondCard && dragOffset.width != 0 ? Color.gray.opacity(0.2) : Color.clear), + radius: 10, x: 0, y: 3 + ) } + + + // Return a colored shadow on drag: green if swiping right, red if swiping left private func getShadowColor() -> Color { if dragOffset.width > 0 { return Color.green @@ -150,4 +161,43 @@ struct ProfileCardView: View { return Color.gray.opacity(0.2) } } + + private func renderTextWithEmoji(_ emoji: String, _ text: String) -> HStack> { + HStack(alignment: .top, spacing: 15) { + Text(emoji) + Text(text) + } + } + + // the noise value as text + emoji + private func renderNoiseTolerance(_ noiseTolerance: Double) -> HStack> { + if noiseTolerance < 3 { + return renderTextWithEmoji(noiseTolerance > 0 ? "✅" : "đŸšĢ", "Noise") + } + return renderTextWithEmoji("🔊", "Unbothered by noise") + } + + // the partyFrequency as text + emoji + private func renderPartying(_ partyFrequency: String) -> HStack> { + switch partyFrequency { + case "Always": + return renderTextWithEmoji("đŸĒŠ", "Party Animal") + case "Sometimes": + return renderTextWithEmoji("✅", "Partying") + default: + return renderTextWithEmoji("đŸšĢ", "No Parties") + } + } + + // the guestFrequency as text + emoji + private func renderGuests(_ guestFrequency: String) -> HStack> { + switch guestFrequency { + case "Always": + return renderTextWithEmoji("🧑‍🤝‍🧑", "Loves Guests") + case "Sometimes": + return renderTextWithEmoji("✅", "Occasional Guests") + default: + return renderTextWithEmoji("đŸšĢ", "No Guests") + } + } } From f3813838cfce85fbb48984c0f3be6b50fcabecdd Mon Sep 17 00:00:00 2001 From: ShouravRakshit Date: Thu, 13 Feb 2025 00:06:04 -0700 Subject: [PATCH 4/4] fix the UItest --- FlatMate/ViewModel/AuthViewModel.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/FlatMate/ViewModel/AuthViewModel.swift b/FlatMate/ViewModel/AuthViewModel.swift index 262582b..fe2af35 100644 --- a/FlatMate/ViewModel/AuthViewModel.swift +++ b/FlatMate/ViewModel/AuthViewModel.swift @@ -135,10 +135,15 @@ class AuthViewModel: ObservableObject { } } + @MainActor func completeOnboarding() async throws { guard let uid = userSession?.uid else { return } do { - try await Firestore.firestore().collection("users").document(uid).updateData(["hasCompletedOnboarding": true]) + try await Firestore.firestore() + .collection("users") + .document(uid) + .updateData(["hasCompletedOnboarding": true] as [String: Bool]) + self.hasCompletedOnboarding = true } catch { print("DEBUG: Failed to update onboarding status with error \(error.localizedDescription)")