From 6225c500082d3e0c6184b0b3f930be51e1f2906e Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Wed, 27 Dec 2023 23:48:16 +0100 Subject: [PATCH] Add some actions to Relationship-action-button (IOS-192) Well, it's basically just unblock, unmute or follow/unfollow --- .../Provider/DataSourceFacade+Block.swift | 23 +- .../Provider/DataSourceFacade+Status.swift | 2 +- .../Provider/DataSourceFacade+UserView.swift | 8 +- .../Scene/Profile/ProfileViewController.swift | 268 ++++++++++-------- .../ReportResultViewController.swift | 6 +- .../Persistence+MastodonUser.swift | 2 +- .../Service/API/APIService+Block.swift | 50 +--- .../Entity/Mastodon+Entity+Relationship.swift | 2 +- .../ProfileRelationshipActionButton.swift | 2 +- 9 files changed, 160 insertions(+), 203 deletions(-) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift index ff4a615a4..33dda8971 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Block.swift @@ -14,27 +14,6 @@ extension DataSourceFacade { static func responseToUserBlockAction( dependency: NeedsDependency & AuthContextProvider, account: Mastodon.Entity.Account - ) async throws { - let selectionFeedbackGenerator = await UISelectionFeedbackGenerator() - await selectionFeedbackGenerator.selectionChanged() - - let apiService = dependency.context.apiService - let authBox = dependency.authContext.mastodonAuthenticationBox - - _ = try await apiService.toggleBlock( - account: account, - authenticationBox: authBox - ) - - try await dependency.context.apiService.getBlocked( - authenticationBox: authBox - ) - dependency.context.authenticationService.fetchFollowingAndBlockedAsync() - } - - static func responseToUserBlockAction( - dependency: NeedsDependency & AuthContextProvider, - user: Mastodon.Entity.Account ) async throws -> Mastodon.Entity.Relationship { let selectionFeedbackGenerator = await UISelectionFeedbackGenerator() await selectionFeedbackGenerator.selectionChanged() @@ -43,7 +22,7 @@ extension DataSourceFacade { let authBox = dependency.authContext.mastodonAuthenticationBox let response = try await apiService.toggleBlock( - user: user, + account: account, authenticationBox: authBox ) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index f52968e27..8ab436994 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -229,7 +229,7 @@ extension DataSourceFacade { Task { let newRelationship = try await DataSourceFacade.responseToUserBlockAction( dependency: dependency, - user: menuContext.author + account: menuContext.author ) if let completion { diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift b/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift index 8cea808fa..097f430b5 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift @@ -21,21 +21,21 @@ extension DataSourceFacade { dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followingUserIds.append(account.id) case .request: - _ = try await DataSourceFacade.responseToUserFollowAction( + _ = try await DataSourceFacade.responseToUserFollowAction( dependency: dependency, account: account ) dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followRequestedUserIDs.append(account.id) case .unfollow: - _ = try await DataSourceFacade.responseToUserFollowAction( + _ = try await DataSourceFacade.responseToUserFollowAction( dependency: dependency, account: account ) dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followingUserIds.removeAll(where: { $0 == account.id }) case .blocked: - try await DataSourceFacade.responseToUserBlockAction( + _ = try await DataSourceFacade.responseToUserBlockAction( dependency: dependency, account: account ) @@ -43,7 +43,7 @@ extension DataSourceFacade { dependency.authContext.mastodonAuthenticationBox.inMemoryCache.blockedUserIds.append(account.id) case .pending: - _ = try await DataSourceFacade.responseToUserFollowAction( + _ = try await DataSourceFacade.responseToUserFollowAction( dependency: dependency, account: account ) diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index 62dfb018d..1e4dc12d0 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -686,62 +686,65 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate { profileHeaderView: ProfileHeaderView, relationshipButtonDidPressed button: ProfileRelationshipActionButton ) { - -// let relationshipActionSet = viewModel.relationshipViewModel.optionSet ?? .none -#warning("TODO: Implement") - // handle edit logic for editable profile - // handle relationship logic for non-editable profile if viewModel.me == viewModel.account { -// // do nothing when updating - guard !viewModel.isUpdating else { return } + editProfile() + } else { + editRelationship() + } + } - let profileHeaderViewModel = profileHeaderViewController.viewModel - guard let profileAboutViewModel = profilePagingViewController.viewModel.profileAboutViewController.viewModel else { return } - - let isEdited = profileHeaderViewModel.isEdited || profileAboutViewModel.isEdited - - if isEdited { - // update profile when edited - viewModel.isUpdating = true - Task { @MainActor in - do { - // TODO: handle error - let updatedAccount = try await viewModel.updateProfileInfo( - headerProfileInfo: profileHeaderViewModel.profileInfoEditing, - aboutProfileInfo: profileAboutViewModel.profileInfoEditing - ).value - self.viewModel.isEditing = false - self.viewModel.account = updatedAccount - } catch { - let alertController = UIAlertController( - for: error, - title: L10n.Common.Alerts.EditProfileFailure.title, - preferredStyle: .alert - ) - let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default) - alertController.addAction(okAction) - self.present(alertController, animated: true) + private func editProfile() { + // do nothing when updating + guard !viewModel.isUpdating else { return } + + let profileHeaderViewModel = profileHeaderViewController.viewModel + guard let profileAboutViewModel = profilePagingViewController.viewModel.profileAboutViewController.viewModel else { return } + + let isEdited = profileHeaderViewModel.isEdited || profileAboutViewModel.isEdited + + if isEdited { + // update profile when edited + viewModel.isUpdating = true + Task { @MainActor in + do { + // TODO: handle error + let updatedAccount = try await viewModel.updateProfileInfo( + headerProfileInfo: profileHeaderViewModel.profileInfoEditing, + aboutProfileInfo: profileAboutViewModel.profileInfoEditing + ).value + self.viewModel.isEditing = false + self.viewModel.account = updatedAccount + + } catch { + let alertController = UIAlertController( + for: error, + title: L10n.Common.Alerts.EditProfileFailure.title, + preferredStyle: .alert + ) + let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default) + alertController.addAction(okAction) + self.present(alertController, animated: true) + } + + // finish updating + self.viewModel.isUpdating = false + } + } else { + // set `updating` then toggle `edit` state + viewModel.isUpdating = true + viewModel.fetchEditProfileInfo() + .receive(on: DispatchQueue.main) + .sink { [weak self] completion in + guard let self = self else { return } + defer { + // finish updating + self.viewModel.isUpdating = false } - - // finish updating - self.viewModel.isUpdating = false - } // end Task - } else { - // set `updating` then toggle `edit` state - viewModel.isUpdating = true - viewModel.fetchEditProfileInfo() - .receive(on: DispatchQueue.main) - .sink { [weak self] completion in - guard let self = self else { return } - defer { - // finish updating - self.viewModel.isUpdating = false - } - switch completion { + switch completion { case .failure(let error): let alertController = UIAlertController(for: error, title: L10n.Common.Alerts.EditProfileFailure.title, preferredStyle: .alert) - let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) + let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default) alertController.addAction(okAction) _ = self.coordinator.present( scene: .alertController(alertController: alertController), @@ -751,82 +754,101 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate { case .finished: // enter editing mode self.viewModel.isEditing.toggle() - } - } receiveValue: { [weak self] response in - guard let self = self else { return } - self.viewModel.accountForEdit = response.value } - .store(in: &disposeBag) - } - } else { -// guard let relationshipAction = relationshipActionSet.highPriorityAction(except: .editOptions) else { return } - guard let relationship = viewModel.relationship else { return } - - print(relationship) -// switch relationshipAction { -// case .none: -// break -// case .follow, .request, .pending, .following: -// guard let user = viewModel.user else { return } -// let record = ManagedObjectRecord(objectID: user.objectID) -// Task { -// try await DataSourceFacade.responseToUserFollowAction( -// dependency: self, -// user: record -// ) -// } -// case .muting: -// guard let user = viewModel.user else { return } -// let name = user.displayNameWithFallback -// -// let alertController = UIAlertController( -// title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.title, -// message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.message(name), -// preferredStyle: .alert -// ) -// let record = ManagedObjectRecord(objectID: user.objectID) -// let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Friendship.unmute, style: .default) { [weak self] _ in -// guard let self = self else { return } -// Task { -// try await DataSourceFacade.responseToUserMuteAction( -// dependency: self, -// user: record -// ) -// } -// } -// alertController.addAction(unmuteAction) -// let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil) -// alertController.addAction(cancelAction) -// present(alertController, animated: true, completion: nil) -// case .blocking: -// guard let user = viewModel.user else { return } -// let name = user.displayNameWithFallback -// -// let alertController = UIAlertController( -// title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.title, -// message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.message(name), -// preferredStyle: .alert -// ) -// let record = ManagedObjectRecord(objectID: user.objectID) -// let unblockAction = UIAlertAction(title: L10n.Common.Controls.Friendship.unblock, style: .default) { [weak self] _ in -// guard let self = self else { return } -// Task { -// try await DataSourceFacade.responseToUserBlockAction( -// dependency: self, -// user: record -// ) -// } -// } -// alertController.addAction(unblockAction) -// let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil) -// alertController.addAction(cancelAction) -// present(alertController, animated: true, completion: nil) -// case .blocked, .showReblogs, .isMyself,.followingBy, .blockingBy, .suspended, .edit, .editing, .updating: -// break -// } + } receiveValue: { [weak self] response in + guard let self = self else { return } + self.viewModel.accountForEdit = response.value + } + .store(in: &disposeBag) } } - + + private func editRelationship() { + guard let relationship = viewModel.relationship else { return } + + let account = viewModel.account + + if relationship.blocking { + let name = account.displayNameWithFallback + + let alertController = UIAlertController( + title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.title, + message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnblockUser.message(name), + preferredStyle: .alert + ) + let unblockAction = UIAlertAction(title: L10n.Common.Controls.Friendship.unblock, style: .default) { [weak self] _ in + guard let self else { return } + Task { + let newRelationship = try await DataSourceFacade.responseToUserBlockAction( + dependency: self, + account: account + ) + + self.viewModel.relationship = newRelationship + } + } + alertController.addAction(unblockAction) + let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel) + alertController.addAction(cancelAction) + coordinator.present(scene: .alertController(alertController: alertController), transition: .alertController(animated: true)) + } else if relationship.muting { + let name = account.displayNameWithFallback + + let alertController = UIAlertController( + title: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.title, + message: L10n.Scene.Profile.RelationshipActionAlert.ConfirmUnmuteUser.message(name), + preferredStyle: .alert + ) + + let unmuteAction = UIAlertAction(title: L10n.Common.Controls.Friendship.unmute, style: .default) { [weak self] _ in + guard let self else { return } + Task { + let newRelationship = try await DataSourceFacade.responseToUserMuteAction( + dependency: self, + account: account + ) + + self.viewModel.relationship = newRelationship + } + } + alertController.addAction(unmuteAction) + let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel) + alertController.addAction(cancelAction) + coordinator.present(scene: .alertController(alertController: alertController), transition: .alertController(animated: true)) + } else { + Task { [weak self] in + guard let self else { return } + + let newRelationship = try await DataSourceFacade.responseToUserFollowAction( + dependency: self, + account: viewModel.account + ) + + self.viewModel.relationship = newRelationship + // update account? + // update me? + } + + } + + // switch relationshipAction { + // case .none: + // break + // case .follow, .request, .pending, .following: + // guard let user = viewModel.user else { return } + // let record = ManagedObjectRecord(objectID: user.objectID) + // Task { + // try await DataSourceFacade.responseToUserFollowAction( + // dependency: self, + // user: record + // ) + // } + // case .muting: + // case .blocked, .showReblogs, .isMyself,.followingBy, .blockingBy, .suspended, .edit, .editing, .updating: + // break + // } + } + func profileHeaderViewController( _ profileHeaderViewController: ProfileHeaderViewController, profileHeaderView: ProfileHeaderView, diff --git a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift index 5a6ec366f..77935cb9a 100644 --- a/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift +++ b/Mastodon/Scene/Report/ReportResult/ReportResultViewController.swift @@ -108,7 +108,7 @@ extension ReportResultViewController { guard !self.viewModel.isRequestMute else { return } self.viewModel.isRequestMute = true do { - try await DataSourceFacade.responseToUserMuteAction( + _ = try await DataSourceFacade.responseToUserMuteAction( dependency: self, account: self.viewModel.account ) @@ -128,9 +128,9 @@ extension ReportResultViewController { guard !self.viewModel.isRequestBlock else { return } self.viewModel.isRequestBlock = true do { - try await DataSourceFacade.responseToUserBlockAction( + _ = try await DataSourceFacade.responseToUserBlockAction( dependency: self, - user: self.viewModel.account + account: self.viewModel.account ) } catch { // handle error diff --git a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+MastodonUser.swift b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+MastodonUser.swift index a36863458..ae6ea3f0e 100644 --- a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+MastodonUser.swift +++ b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+MastodonUser.swift @@ -147,7 +147,7 @@ extension Persistence.MastodonUser { let me = context.me user.update(isFollowing: relationship.following, by: me) - relationship.requested.flatMap { user.update(isFollowRequested: $0, by: me) } + user.update(isFollowRequested: relationship.requested, by: me) // relationship.endorsed.flatMap { user.update(isEndorsed: $0, by: me) } me.update(isFollowing: relationship.followedBy, by: user) user.update(isMuting: relationship.muting, by: me) diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift index 8d70ead23..9640500ac 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Block.swift @@ -64,51 +64,7 @@ extension APIService { account: Mastodon.Entity.Account, authenticationBox: MastodonAuthenticationBox ) async throws -> Mastodon.Response.Content { - - guard let me = authenticationBox.authentication.account(), - let relationship = try await relationship(forAccounts: [account], authenticationBox: authenticationBox).value.first - else { throw APIError.implicit(.badRequest) } - - let blockContext = MastodonBlockContext( - sourceUserID: me.id, - targetUserID: account.id, - targetUsername: account.username, - isBlocking: relationship.blocking, - isFollowing: relationship.following - ) - - let result: Result, Error> - do { - if blockContext.isBlocking { - let response = try await Mastodon.API.Account.unblock( - session: session, - domain: authenticationBox.domain, - accountID: blockContext.targetUserID, - authorization: authenticationBox.userAuthorization - ).singleOutput() - result = .success(response) - } else { - let response = try await Mastodon.API.Account.block( - session: session, - domain: authenticationBox.domain, - accountID: blockContext.targetUserID, - authorization: authenticationBox.userAuthorization - ).singleOutput() - result = .success(response) - } - } catch { - result = .failure(error) - } - - let response = try result.get() - return response - } - - public func toggleBlock( - user: Mastodon.Entity.Account, - authenticationBox: MastodonAuthenticationBox - ) async throws -> Mastodon.Response.Content { - guard let relationship = try await relationship(forAccounts: [user], authenticationBox: authenticationBox).value.first else { + guard let relationship = try await relationship(forAccounts: [account], authenticationBox: authenticationBox).value.first else { throw APIError.implicit(.badRequest) } @@ -118,14 +74,14 @@ extension APIService { response = try await Mastodon.API.Account.unblock( session: session, domain: authenticationBox.domain, - accountID: user.id, + accountID: account.id, authorization: authenticationBox.userAuthorization ).singleOutput() } else { response = try await Mastodon.API.Account.block( session: session, domain: authenticationBox.domain, - accountID: user.id, + accountID: account.id, authorization: authenticationBox.userAuthorization ).singleOutput() } diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Relationship.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Relationship.swift index bd02f9070..ebda38749 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Relationship.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Relationship.swift @@ -22,7 +22,7 @@ extension Mastodon.Entity { /// Are you following this user? public let following: Bool /// Do you have a pending follow request for this user? - public let requested: Bool? + public let requested: Bool /// Are you featuring this user on your profile? public let endorsed: Bool /// Are you followed by this user? diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift index efc0a8f1f..892a82b0b 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift @@ -87,7 +87,7 @@ extension ProfileRelationshipActionButton { setTitle(title, for: .normal) - if relationship.blocking || account.suspended ?? false { + if relationship.blockedBy || account.suspended ?? false { isEnabled = false } else { isEnabled = true