From 76304e59e518868a4b2bae189bd3988267ad1302 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 26 Dec 2023 16:05:48 +0100 Subject: [PATCH] Configure Profile-button based on relationship and accounts (IOS-192) Also `me` is not optional anymore as we need it --- Mastodon/Coordinator/SceneCoordinator.swift | 4 ++- .../Provider/DataSourceFacade+Profile.swift | 6 ++-- .../Header/ProfileHeaderViewController.swift | 6 ++-- .../Header/ProfileHeaderViewModel.swift | 5 ++- .../View/ProfileHeaderView+ViewModel.swift | 32 ++++++++++++------- .../Header/View/ProfileHeaderView.swift | 4 +-- .../Scene/Profile/ProfileViewController.swift | 11 ++++--- Mastodon/Scene/Profile/ProfileViewModel.swift | 10 +++--- .../Root/MainTab/MainTabBarController.swift | 2 +- Mastodon/Supporting Files/SceneDelegate.swift | 10 ++++-- .../ProfileRelationshipActionButton.swift | 27 ++++++++++++++++ 11 files changed, 83 insertions(+), 34 deletions(-) diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 1b9e99521..8f1c9cc3d 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -98,6 +98,7 @@ final public class SceneCoordinator { // show notification related content guard let type = Mastodon.Entity.Notification.NotificationType(rawValue: pushNotification.notificationType) else { return } guard let authContext = self.authContext else { return } + guard let me = authContext.mastodonAuthenticationBox.authentication.account() else { return } let notificationID = String(pushNotification.notificationID) switch type { @@ -114,7 +115,8 @@ final public class SceneCoordinator { context: appContext, authContext: authContext, account: account, - relationship: relationship + relationship: relationship, + me: me ) _ = self.present( scene: .profile(viewModel: profileViewModel), diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift index 24e059f1c..49aa61316 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Profile.swift @@ -104,13 +104,15 @@ extension DataSourceFacade { Task { @MainActor in - guard let relationship = try? await provider.context.apiService.relationship(forAccounts: [account], authenticationBox: provider.authContext.mastodonAuthenticationBox).value.first else { return } + guard let me = provider.authContext.mastodonAuthenticationBox.authentication.account(), + let relationship = try? await provider.context.apiService.relationship(forAccounts: [account], authenticationBox: provider.authContext.mastodonAuthenticationBox).value.first else { return } let profileViewModel = ProfileViewModel( context: provider.context, authContext: provider.authContext, account: account, - relationship: relationship + relationship: relationship, + me: me ) _ = provider.coordinator.present( diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift index 0458ed9ae..a4f8af02d 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewController.swift @@ -82,11 +82,11 @@ final class ProfileHeaderViewController: UIViewController, NeedsDependency, Medi return documentPickerController }() - init(context: AppContext, authContext: AuthContext, account: Mastodon.Entity.Account, coordinator: SceneCoordinator) { + init(context: AppContext, authContext: AuthContext, coordinator: SceneCoordinator, profileViewModel: ProfileViewModel) { self.context = context self.coordinator = coordinator - self.viewModel = ProfileHeaderViewModel(context: context, authContext: authContext, account: account) - self.profileHeaderView = ProfileHeaderView(account: account) + self.viewModel = ProfileHeaderViewModel(context: context, authContext: authContext, account: profileViewModel.account, me: profileViewModel.me, relationship: profileViewModel.relationship) + self.profileHeaderView = ProfileHeaderView(account: profileViewModel.account, me: profileViewModel.me, relationship: profileViewModel.relationship) super.init(nibName: nil, bundle: nil) } diff --git a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift index f8db96e06..11a499109 100644 --- a/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift +++ b/Mastodon/Scene/Profile/Header/ProfileHeaderViewModel.swift @@ -26,6 +26,7 @@ final class ProfileHeaderViewModel { let context: AppContext let authContext: AuthContext + @Published var me: Mastodon.Entity.Account @Published var account: Mastodon.Entity.Account @Published var relationship: Mastodon.Entity.Relationship? @@ -44,10 +45,12 @@ final class ProfileHeaderViewModel { @Published var isTitleViewDisplaying = false @Published var isTitleViewContentOffsetSet = false - init(context: AppContext, authContext: AuthContext, account: Mastodon.Entity.Account) { + init(context: AppContext, authContext: AuthContext, account: Mastodon.Entity.Account, me: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) { self.context = context self.authContext = authContext self.account = account + self.me = me + self.relationship = relationship $accountForEdit .receive(on: DispatchQueue.main) diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift index aa1ff777d..aabb4f9d5 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView+ViewModel.swift @@ -46,13 +46,16 @@ extension ProfileHeaderView { @Published var fields: [MastodonField] = [] + @Published var me: Mastodon.Entity.Account @Published var account: Mastodon.Entity.Account @Published var relationship: Mastodon.Entity.Relationship? @Published var isRelationshipActionButtonHidden = false @Published var isMyself = false - init(account: Mastodon.Entity.Account) { + init(account: Mastodon.Entity.Account, me: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) { self.account = account + self.me = me + self.relationship = relationship #warning("TODO: Implement") // $relationshipActionOptionSet @@ -103,7 +106,8 @@ extension ProfileHeaderView.ViewModel { // follows you Publishers.CombineLatest($relationship, $isMyself) .map { relationship, isMyself in - (relationship?.following ?? false) && (isMyself == false) } + return (relationship?.following ?? false) && (isMyself == false) + } .receive(on: DispatchQueue.main) .sink { isFollowing in view.followsYouBlurEffectView.isHidden = (isFollowing == false) @@ -196,7 +200,9 @@ extension ProfileHeaderView.ViewModel { Publishers.CombineLatest($relationship, $account) .compactMap { relationship, account in + guard let relationship else { return nil } + let isBlocking = relationship.blocking let isBlockedBy = relationship.blockedBy ?? false let isSuspended = account.suspended ?? false @@ -263,13 +269,17 @@ extension ProfileHeaderView.ViewModel { .assign(to: \.isHidden, on: view.relationshipActionButtonShadowContainer) .store(in: &disposeBag) #warning("TODO: Implement") -// Publishers.CombineLatest2( -// $relationshipActionOptionSet, -// $isEditing, -// $isUpdating -// ) -// .receive(on: DispatchQueue.main) -// .sink { relationshipActionOptionSet, isEditing, isUpdating in + Publishers.CombineLatest3( + Publishers.CombineLatest3($me, $account, $relationship).eraseToAnyPublisher(), + $isEditing, + $isUpdating + ) + .receive(on: DispatchQueue.main) + .sink { tuple, isEditing, isUpdating in + let (me, account, relationship) = tuple + guard let relationship else { return } + + view.relationshipActionButton.configure(relationship: relationship, between: account, and: me, isEditing: isEditing, isUpdating: isUpdating) // if relationshipActionOptionSet.contains(.edit) { // // check .edit state and set .editing when isEditing // view.relationshipActionButton.configure(actionOptionSet: isUpdating ? .updating : (isEditing ? .editing : .edit)) @@ -277,8 +287,8 @@ extension ProfileHeaderView.ViewModel { // } else { // view.relationshipActionButton.configure(actionOptionSet: relationshipActionOptionSet) // } -// } -// .store(in: &disposeBag) + } + .store(in: &disposeBag) } } diff --git a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift index fe7dcaae0..7b0190b10 100644 --- a/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift +++ b/Mastodon/Scene/Profile/Header/View/ProfileHeaderView.swift @@ -235,9 +235,9 @@ final class ProfileHeaderView: UIView { return metaText }() - init(account: Mastodon.Entity.Account) { + init(account: Mastodon.Entity.Account, me: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) { - viewModel = ViewModel(account: account) + viewModel = ViewModel(account: account, me: me, relationship: relationship) super.init(frame: .zero) diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index d06e3d0a2..62dfb018d 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -119,7 +119,7 @@ final class ProfileViewController: UIViewController, NeedsDependency, MediaPrevi private(set) lazy var tabBarPagerController = TabBarPagerController() private(set) lazy var profileHeaderViewController: ProfileHeaderViewController = { - let viewController = ProfileHeaderViewController(context: context, authContext: authContext, account: viewModel.account, coordinator: coordinator) + let viewController = ProfileHeaderViewController(context: context, authContext: authContext, coordinator: coordinator, profileViewModel: viewModel) return viewController }() @@ -691,7 +691,7 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate { #warning("TODO: Implement") // handle edit logic for editable profile // handle relationship logic for non-editable profile - if let me = viewModel.me, me == viewModel.account { + if viewModel.me == viewModel.account { // // do nothing when updating guard !viewModel.isUpdating else { return } @@ -706,12 +706,13 @@ extension ProfileViewController: ProfileHeaderViewControllerDelegate { Task { @MainActor in do { // TODO: handle error - _ = try await viewModel.updateProfileInfo( + 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, diff --git a/Mastodon/Scene/Profile/ProfileViewModel.swift b/Mastodon/Scene/Profile/ProfileViewModel.swift index 84038435b..68e13bde0 100644 --- a/Mastodon/Scene/Profile/ProfileViewModel.swift +++ b/Mastodon/Scene/Profile/ProfileViewModel.swift @@ -34,7 +34,7 @@ class ProfileViewModel: NSObject { let context: AppContext let authContext: AuthContext - @Published var me: Mastodon.Entity.Account? + @Published var me: Mastodon.Entity.Account @Published var account: Mastodon.Entity.Account @Published var relationship: Mastodon.Entity.Relationship? @@ -56,11 +56,12 @@ class ProfileViewModel: NSObject { // let needsPagePinToTop = CurrentValueSubject(false) @MainActor - init(context: AppContext, authContext: AuthContext, account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) { + init(context: AppContext, authContext: AuthContext, account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?, me: Mastodon.Entity.Account) { self.context = context self.authContext = authContext self.account = account self.relationship = relationship + self.me = me self.postsUserTimelineViewModel = UserTimelineViewModel( context: context, @@ -82,9 +83,6 @@ class ProfileViewModel: NSObject { ) self.profileAboutViewModel = ProfileAboutViewModel(context: context, account: account) super.init() - - // bind me - self.me = authContext.mastodonAuthenticationBox.authentication.account() // bind user $account @@ -176,7 +174,7 @@ class ProfileViewModel: NSObject { // fetch profile info before edit func fetchEditProfileInfo() -> AnyPublisher, Error> { - guard let me, let domain = me.domain else { + guard let domain = me.domain else { return Fail(error: APIService.APIError.implicit(.authenticationMissing)).eraseToAnyPublisher() } diff --git a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift index f247b83cc..d4e587758 100644 --- a/Mastodon/Scene/Root/MainTab/MainTabBarController.swift +++ b/Mastodon/Scene/Root/MainTab/MainTabBarController.swift @@ -119,7 +119,7 @@ class MainTabBarController: UITabBarController { let _viewController = ProfileViewController() _viewController.context = context _viewController.coordinator = coordinator - _viewController.viewModel = ProfileViewModel(context: context, authContext: authContext, account: me, relationship: nil) + _viewController.viewModel = ProfileViewModel(context: context, authContext: authContext, account: me, relationship: nil, me: me) viewController = _viewController } viewController.title = self.title diff --git a/Mastodon/Supporting Files/SceneDelegate.swift b/Mastodon/Supporting Files/SceneDelegate.swift index 3bff14995..6cfb3b482 100644 --- a/Mastodon/Supporting Files/SceneDelegate.swift +++ b/Mastodon/Supporting Files/SceneDelegate.swift @@ -141,6 +141,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let domain = authContext.mastodonAuthenticationBox.domain let authenticationBox = authContext.mastodonAuthenticationBox + guard let me = authenticationBox.authentication.account() else { return } + guard let account = try await AppContext.shared.apiService.search( query: .init(q: incomingURL.absoluteString, type: .accounts, resolve: true), authenticationBox: authenticationBox @@ -155,7 +157,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { context: AppContext.shared, authContext: authContext, account: account, - relationship: relationship + relationship: relationship, + me: me ) _ = self.coordinator?.present( scene: .profile(viewModel: profileViewModel), @@ -285,6 +288,8 @@ extension SceneDelegate { let domain = authContext.mastodonAuthenticationBox.domain let authenticationBox = authContext.mastodonAuthenticationBox + guard let me = authContext.mastodonAuthenticationBox.authentication.account() else { return } + guard let account = try await AppContext.shared.apiService.search( query: .init(q: components[1], type: .accounts, resolve: true), authenticationBox: authenticationBox @@ -299,7 +304,8 @@ extension SceneDelegate { context: AppContext.shared, authContext: authContext, account: account, - relationship: relationship + relationship: relationship, + me: me ) self.coordinator?.present( diff --git a/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift index 46ec8b5dd..56ef81b01 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Control/ProfileRelationshipActionButton.swift @@ -7,6 +7,7 @@ import UIKit import MastodonAsset +import MastodonSDK import MastodonLocalization public final class ProfileRelationshipActionButton: RoundedEdgesButton { @@ -55,6 +56,32 @@ extension ProfileRelationshipActionButton { } extension ProfileRelationshipActionButton { + + public func configure(relationship: Mastodon.Entity.Relationship, between user: Mastodon.Entity.Account, and me: Mastodon.Entity.Account, isEditing: Bool = false, isUpdating: Bool = false) { + + let isMyself = (user == me) + let title: String + + if isMyself { + if isEditing { + title = "SAVE" + } else { + title = "EDIT" + } + } else if relationship.following { + title = L10n.Common.Controls.Friendship.follow + } else { + title = "TITLE" + } + setTitle(title, for: .normal) + + if relationship.blocking || user.suspended ?? false { + isEnabled = false + } else { + isEnabled = true + } + } + public func configure(actionOptionSet: RelationshipActionOptionSet) { setTitle(actionOptionSet.title, for: .normal)