From 1edfe19689f9c3b6af1575f7c9d814f66574ebf0 Mon Sep 17 00:00:00 2001 From: kaelin good Date: Thu, 13 Mar 2025 19:09:59 -0600 Subject: [PATCH] implemented block and decline features --- Orbit/Services/MeetupApprovalService.swift | 24 ++++----- Orbit/UI/OrbitApp.swift | 3 +- Orbit/UI/Screens/HomeView.swift | 2 +- .../Meetup/MeetuprequestDetailedView.swift | 44 ++++++++------- .../ViewModels/MeetupApprovalViewModel.swift | 43 ++++++++------- .../ViewModels/MeetupRequestViewModel.swift | 23 ++++---- Orbit/UI/ViewModels/UserViewModel.swift | 54 +++++++++++++++++++ 7 files changed, 132 insertions(+), 61 deletions(-) diff --git a/Orbit/Services/MeetupApprovalService.swift b/Orbit/Services/MeetupApprovalService.swift index 2aa657d..88c7908 100644 --- a/Orbit/Services/MeetupApprovalService.swift +++ b/Orbit/Services/MeetupApprovalService.swift @@ -86,19 +86,19 @@ class MeetupApprovalService: MeetupApprovalServiceProtocol { } // Delete an approval - func deleteApproval(approvalId: String) async throws { - do { - try await appwriteService.databases.deleteDocument( - databaseId: appwriteService.databaseId, - collectionId: collectionId, - documentId: approvalId - ) - } catch { - throw NSError( - domain: "Failed to delete approval", code: 500, userInfo: nil) + func deleteApproval(approvalId: String) async throws { + do { + let result = try await appwriteService.databases.deleteDocument( + databaseId: appwriteService.databaseId, + collectionId: collectionId, + documentId: approvalId + ) + } catch { + throw NSError( + domain: "Failed to delete approval", code: 500, userInfo: nil) + } } - } - + // List all approvals func listApprovals(queries: [String]? = nil) async throws -> [MeetupApprovalDocument] diff --git a/Orbit/UI/OrbitApp.swift b/Orbit/UI/OrbitApp.swift index 8a9347a..9494eb5 100644 --- a/Orbit/UI/OrbitApp.swift +++ b/Orbit/UI/OrbitApp.swift @@ -49,6 +49,7 @@ struct OrbitApp: App { .font: UIFont.systemFont(ofSize: 24, weight: .bold), ] UINavigationBar.appearance().scrollEdgeAppearance = scrollEdgeAppearance + } var body: some Scene { @@ -58,7 +59,7 @@ struct OrbitApp: App { .environmentObject(authVM) .environmentObject(userVM) .environmentObject(msgVM) - .environmentObject(chatRequestVM) + .environmentObject(chatRequestVM) .environmentObject(meetupRequestVM) .environmentObject(meetupApprovalVM) .environmentObject(appDelegate.appState) diff --git a/Orbit/UI/Screens/HomeView.swift b/Orbit/UI/Screens/HomeView.swift index 07de1a3..452aea3 100644 --- a/Orbit/UI/Screens/HomeView.swift +++ b/Orbit/UI/Screens/HomeView.swift @@ -2,12 +2,12 @@ import AppwriteModels import SwiftUI struct HomeView: View { - @EnvironmentObject private var userVM: UserViewModel @EnvironmentObject private var authVM: AuthViewModel @EnvironmentObject private var chatRequestVM: ChatRequestViewModel @EnvironmentObject private var meetupRequestVM: MeetupRequestViewModel @EnvironmentObject private var appState: AppState @Environment(\.colorScheme) var colorScheme + @EnvironmentObject private var userVM: UserViewModel // @State private var selectedMeetupRequest: MeetupRequestDocument? = nil @State private var isShowingChatRequests = false diff --git a/Orbit/UI/Screens/Meetup/MeetuprequestDetailedView.swift b/Orbit/UI/Screens/Meetup/MeetuprequestDetailedView.swift index 75c00b5..fd8283e 100644 --- a/Orbit/UI/Screens/Meetup/MeetuprequestDetailedView.swift +++ b/Orbit/UI/Screens/Meetup/MeetuprequestDetailedView.swift @@ -142,7 +142,12 @@ struct MeetupRequestDetailedView: View { } HStack(spacing: 16) { - Button(action: declineMeetupRequest) { + Button(action: { + Task { + await meetupApprovalVM.declineMeetup(meetupRequest: meetupRequest) + dismiss() + } + }) { HStack { Image(systemName: "xmark.circle.fill") Text("Decline") @@ -154,6 +159,7 @@ struct MeetupRequestDetailedView: View { .background(Color.red) .cornerRadius(16) } + Button(action: approveMeetupRequest) { HStack { Image(systemName: "checkmark.circle.fill") @@ -173,18 +179,23 @@ struct MeetupRequestDetailedView: View { .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { - Button { - dismiss() - } label: { - Image(systemName: "person.crop.circle.badge.xmark") //Other potential icons: "nosign", "shield.lefthalf.filled" - .foregroundColor( - ColorPalette.secondaryText(for: colorScheme)) - #warning( - "TODO: Add block functionality" - ) + Menu { + Button(role: .destructive) { + Task { + if let userId = meetupRequest.createdByUser?.id { + await userVM.blockUser(userId: userId) + dismiss() // Dismiss after blocking + } + } + } label: { + Label("Block User", systemImage: "nosign") + } + } label: { + Image(systemName: "ellipsis.circle") // Three-dot menu + .foregroundColor(ColorPalette.secondaryText(for: colorScheme)) + } + } } - } - } } .onAppear { @@ -236,14 +247,7 @@ struct MeetupRequestDetailedView: View { } } - private func declineMeetupRequest() { - Task { - #warning( - "TODO: Implement decline functionality" - ) - dismiss() - } - } + } #if DEBUG diff --git a/Orbit/UI/ViewModels/MeetupApprovalViewModel.swift b/Orbit/UI/ViewModels/MeetupApprovalViewModel.swift index 994a14a..74e43b3 100644 --- a/Orbit/UI/ViewModels/MeetupApprovalViewModel.swift +++ b/Orbit/UI/ViewModels/MeetupApprovalViewModel.swift @@ -83,25 +83,31 @@ class MeetupApprovalViewModel: ObservableObject { ) } } + /// Declines (removes) a meetup approval request + func declineMeetup(meetupRequest: MeetupRequestModel) async { + isLoading = true + defer { isLoading = false } + + do { + // Find the approval linked to this meetup request + if let approval = approvals.first(where: { $0.data.meetupRequest?.id == meetupRequest.id }) { + // Call the service to delete it + try await meetupApprovalService.deleteApproval(approvalId: approval.id) - /// Reject (or remove) a meetup approval - // func removeApproval(_ approval: MeetupApprovalModel) async { - // isLoading = true - // defer { isLoading = false } - // - // do { - // try await meetupApprovalService.deleteApproval( - // approvalId: approval.meetupRequest.title) - // self.approvals.removeAll { - // $0.meetupRequest.title == approval.meetupRequest.title - // } - // } catch { - // self.error = error.localizedDescription - // print( - // "MeetupApprovalViewModel - removeApproval: Error: \(error.localizedDescription)" - // ) - // } - // } + // Update UI by removing the declined approval + await MainActor.run { + self.approvals.removeAll { $0.id == approval.id } + } + + print("Successfully declined meetup request.") + } else { + print("Error: Approval not found for meetup request \(meetupRequest.id)") + } + } catch { + self.error = error.localizedDescription + print("MeetupApprovalViewModel - declineMeetup: Error: \(error.localizedDescription)") + } + } #if DEBUG static func mock() -> MeetupApprovalViewModel { @@ -113,3 +119,4 @@ class MeetupApprovalViewModel: ObservableObject { } #endif } + diff --git a/Orbit/UI/ViewModels/MeetupRequestViewModel.swift b/Orbit/UI/ViewModels/MeetupRequestViewModel.swift index 550f179..694830b 100644 --- a/Orbit/UI/ViewModels/MeetupRequestViewModel.swift +++ b/Orbit/UI/ViewModels/MeetupRequestViewModel.swift @@ -18,8 +18,9 @@ class MeetupRequestViewModel: ObservableObject { private var meetupService: MeetupRequestServiceProtocol = MeetupRequestService() - - init() { + private var userVM: UserViewModel + + init(userVM: UserViewModel) { if !isPreviewMode { Task { await fetchAllMeetups() @@ -35,13 +36,17 @@ class MeetupRequestViewModel: ObservableObject { do { let meetups = try await meetupService.listMeetups(queries: nil) self.meetupRequests = meetups - } catch { - self.error = error.localizedDescription - print( - "MeetupRequestViewModel - fetchAllMeetups: Error: \(error.localizedDescription)" - ) - } - } + await MainActor.run { + self.meetupRequests = meetups.filter { request in + guard let userId = request.data.createdByUser?.id else { return true } + return !userVM.isUserBlocked(userId: userId) + } + } + } catch { + self.error = error.localizedDescription + print("MeetupRequestViewModel - fetchAllMeetups: Error: \(error.localizedDescription)") + } + } /// Fetch a specific meetup by ID func fetchMeetup(by id: String) async { diff --git a/Orbit/UI/ViewModels/UserViewModel.swift b/Orbit/UI/ViewModels/UserViewModel.swift index 85ee94d..0f66639 100644 --- a/Orbit/UI/ViewModels/UserViewModel.swift +++ b/Orbit/UI/ViewModels/UserViewModel.swift @@ -41,6 +41,7 @@ class UserViewModel: NSObject, ObservableObject, PreciseLocationManagerDelegate, @Published var selectedRadius: Double = 10.0 @Published var isOnCampus = false // Track if the user is inside campus @Published var allUsers: [UserModel] = [] + @Published var blockedUsers: Set = [] private var userManagementService: UserManagementServiceProtocol = UserManagementService() @@ -51,6 +52,7 @@ class UserViewModel: NSObject, ObservableObject, PreciseLocationManagerDelegate, var lastFetchedAreaId: String? var lastFetchedTimestamp: Date? private var subscribeToLocationUpdates: RealtimeSubscription? + private let appwriteService = AppwriteService.shared init( campusLocationManager: CampusLocationManager = CampusLocationManager() @@ -73,6 +75,57 @@ class UserViewModel: NSObject, ObservableObject, PreciseLocationManagerDelegate, // await subscribeToRealtimeUpdates() // self.allUsers = await getAllUsers() // await fetchAllUsernames() + await fetchBlockedUsers() + } + @MainActor + func fetchBlockedUsers() async { + do { + let blockedUsersList = try await appwriteService.databases.listDocuments( + databaseId: "orbit", + collectionId: "blockedUsers" + ) + let blockedIds = blockedUsersList.documents.compactMap { document in + document.data["blockedUserId"].map {String(describing: $0)} + } + + // Update local blockedUsers list + self.blockedUsers = Set(blockedIds) + + print("Fetched blocked users: \(self.blockedUsers)") + } catch { + print("Error fetching blocked users: \(error.localizedDescription)") + } + } + + func isUserBlocked(userId: String) -> Bool { + return blockedUsers.contains(userId) + } + + @MainActor + func blockUser(userId: String) async { + guard !blockedUsers.contains(userId) else { + print("User is already blocked.") + return + } + + do { + // Store the blocked user in the Appwrite database + let createdDocument = try await appwriteService.databases.createDocument( + databaseId: "orbit", + collectionId: "blockedUsers", + documentId: ID.unique(), + data: ["blockedUserId": userId, "blockingUserId": currentUser?.id ?? ""] + ) + + print("Block entry created with ID:\(createdDocument)") + + // Update local state + self.blockedUsers.insert(userId) + + print("User \(userId) has been blocked.") + } catch { + print("Error blocking user: \(error)") + } } @MainActor @@ -637,6 +690,7 @@ class UserViewModel: NSObject, ObservableObject, PreciseLocationManagerDelegate, self.error = error.localizedDescription } } + @MainActor func handleRealtimeUserUpdate(_ updatedUser: UserModel) {