From d455da85d27004cbfa5d1cf73d072dd380995d82 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Fri, 2 Jun 2023 18:18:11 +0200 Subject: [PATCH] Add follow/unfollow-option to user-section (IOS-103) --- Localization/app.json | 4 ++- .../Provider/DataSourceFacade+Status.swift | 7 +++++ .../NotificationView+Configuration.swift | 16 +++++++++++ .../Generated/Strings.swift | 8 ++++++ .../Resources/Base.lproj/Localizable.strings | 2 ++ .../Content/NotificationView+ViewModel.swift | 11 +++++--- .../View/Content/StatusAuthorView.swift | 7 +++++ .../Content/StatusView+Configuration.swift | 13 +++++++++ .../View/Content/StatusView+ViewModel.swift | 9 ++++-- .../MastodonUI/View/Content/StatusView.swift | 1 - .../MastodonUI/View/Menu/MastodonMenu.swift | 28 +++++++++++++++++++ 11 files changed, 97 insertions(+), 9 deletions(-) diff --git a/Localization/app.json b/Localization/app.json index b5d25f48b..0aa701147 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -102,8 +102,10 @@ "unknown_language": "Unknown" }, "edit_post": "Edit", - "bookmark": "Bookmark" + "bookmark": "Bookmark", "remove_bookmark": "Remove Bookmark", + "follow" = "Follow %s", + "unfollow" = "Unfollow %s" }, "tabs": { "home": "Home", diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index dacec8381..044e9b7a4 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -384,6 +384,13 @@ extension DataSourceFacade { composeContext: .editStatus(status: status, statusSource: statusSource), destination: .topLevel) _ = dependency.coordinator.present(scene: .editStatus(viewModel: editStatusViewModel), transition: .modal(animated: true)) + + case .followUser(_): + + guard let author = menuContext.author else { return } + + try await DataSourceFacade.responseToUserFollowAction(dependency: dependency, + user: author) } } // end func } diff --git a/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift b/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift index 018628473..011435fff 100644 --- a/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift +++ b/Mastodon/Scene/Share/View/Content/NotificationView+Configuration.swift @@ -187,6 +187,7 @@ extension NotificationView { } .assign(to: \.isBlocking, on: viewModel) .store(in: &disposeBag) + // isMyself Publishers.CombineLatest( author.publisher(for: \.domain), @@ -199,12 +200,27 @@ extension NotificationView { } .assign(to: \.isMyself, on: viewModel) .store(in: &disposeBag) + // follow request state notification.publisher(for: \.followRequestState) .assign(to: \.followRequestState, on: viewModel) .store(in: &disposeBag) + notification.publisher(for: \.transientFollowRequestState) .assign(to: \.transientFollowRequestState, on: viewModel) .store(in: &disposeBag) + + // Following + author.publisher(for: \.followingBy) + .map { [weak viewModel] followingBy in + guard let viewModel = viewModel else { return false } + guard let authContext = viewModel.authContext else { return false } + return followingBy.contains(where: { + $0.id == authContext.mastodonAuthenticationBox.userID && $0.domain == authContext.mastodonAuthenticationBox.domain + }) + } + .assign(to: \.isFollowed, on: viewModel) + .store(in: &disposeBag) + } } diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index bbda9cad2..65bb3abbe 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -138,6 +138,10 @@ public enum L10n { public static let editPost = L10n.tr("Localizable", "Common.Controls.Actions.EditPost", fallback: "Edit") /// Find people to follow public static let findPeople = L10n.tr("Localizable", "Common.Controls.Actions.FindPeople", fallback: "Find people to follow") + /// Follow %@ + public static func follow(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Actions.Follow", String(describing: p1), fallback: "Follow %@") + } /// Manually search instead public static let manuallySearch = L10n.tr("Localizable", "Common.Controls.Actions.ManuallySearch", fallback: "Manually search instead") /// Next @@ -192,6 +196,10 @@ public enum L10n { public static func unblockDomain(_ p1: Any) -> String { return L10n.tr("Localizable", "Common.Controls.Actions.UnblockDomain", String(describing: p1), fallback: "Unblock %@") } + /// Unfollow %@ + public static func unfollow(_ p1: Any) -> String { + return L10n.tr("Localizable", "Common.Controls.Actions.Unfollow", String(describing: p1), fallback: "Unfollow %@") + } public enum TranslatePost { /// Translate from %@ public static func title(_ p1: Any) -> String { diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index ca51a011d..971de9623 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -69,6 +69,8 @@ Please check your internet connection."; "Common.Controls.Actions.TranslatePost.UnknownLanguage" = "Unknown"; "Common.Controls.Actions.TryAgain" = "Try Again"; "Common.Controls.Actions.UnblockDomain" = "Unblock %@"; +"Common.Controls.Actions.Follow" = "Follow %@"; +"Common.Controls.Actions.Unfollow" = "Unfollow %@"; "Common.Controls.Friendship.Block" = "Block"; "Common.Controls.Friendship.BlockDomain" = "Block %@"; "Common.Controls.Friendship.BlockUser" = "Block %@"; diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift index ed038f47f..68b13fed5 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift @@ -39,6 +39,7 @@ extension NotificationView { @Published public var isMuting = false @Published public var isBlocking = false @Published public var isTranslated = false + @Published public var isFollowed = false @Published public var timestamp: Date? @@ -208,18 +209,19 @@ extension NotificationView.ViewModel { $authorName, $isMuting, $isBlocking, - Publishers.CombineLatest( + Publishers.CombineLatest3( $isMyself, - $isTranslated + $isTranslated, + $isFollowed ) ) - .sink { [weak self] authorName, isMuting, isBlocking, isMyselfIsTranslated in + .sink { [weak self] authorName, isMuting, isBlocking, isMyselfIsTranslatedIsFollowed in guard let name = authorName?.string else { notificationView.menuButton.menu = nil return } - let (isMyself, isTranslated) = isMyselfIsTranslated + let (isMyself, isTranslated, isFollowed) = isMyselfIsTranslatedIsFollowed lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = { guard @@ -243,6 +245,7 @@ extension NotificationView.ViewModel { isBlocking: isBlocking, isMyself: isMyself, isBookmarking: false, // no bookmark action display for notification item + isFollowed: isFollowed, isTranslationEnabled: instanceConfigurationV2?.translation?.enabled == true, isTranslated: isTranslated, statusLanguage: "" diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift index 81bd6f5a6..f1972a5e8 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift @@ -150,6 +150,7 @@ extension StatusAuthorView { public let isBlocking: Bool public let isMyself: Bool public let isBookmarking: Bool + public let isFollowed: Bool public let isTranslationEnabled: Bool public let isTranslated: Bool @@ -175,6 +176,12 @@ extension StatusAuthorView { postActions.append(.shareStatus) if menuContext.isMyself == false { + + userActions.append(.followUser(.init( + name: menuContext.name, + isFollowing: menuContext.isFollowed + ))) + userActions.append(.muteUser(.init( name: menuContext.name, isMuting: menuContext.isMuting diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift index 03eff8c27..5ccc88e83 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift @@ -258,6 +258,18 @@ extension StatusView { } .assign(to: \.isMyself, on: viewModel) .store(in: &disposeBag) + + // Following + author.publisher(for: \.followingBy) + .map { [weak viewModel] followingBy in + guard let viewModel = viewModel else { return false } + guard let authContext = viewModel.authContext else { return false } + return followingBy.contains(where: { + $0.id == authContext.mastodonAuthenticationBox.userID && $0.domain == authContext.mastodonAuthenticationBox.domain + }) + } + .assign(to: \.isFollowed, on: viewModel) + .store(in: &disposeBag) } private func configureTimestamp(timestamp: AnyPublisher) { @@ -280,6 +292,7 @@ extension StatusView { func revertTranslation() { guard let originalStatus = viewModel.originalStatus else { return } + viewModel.translatedFromLanguage = nil viewModel.translatedUsingProvider = nil originalStatus.reblog?.update(translatedContent: nil) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index f466fd819..f52728da1 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -45,6 +45,7 @@ extension StatusView { @Published public var isMyself = false @Published public var isMuting = false @Published public var isBlocking = false + @Published public var isFollowed = false // Translation @Published public var isCurrentlyTranslating = false @@ -656,10 +657,11 @@ extension StatusView.ViewModel { $authorName, $isMyself ) - let publishersTwo = Publishers.CombineLatest3( + let publishersTwo = Publishers.CombineLatest4( $isMuting, $isBlocking, - $isBookmark + $isBookmark, + $isFollowed ) let publishersThree = Publishers.CombineLatest( $translatedFromLanguage, @@ -673,7 +675,7 @@ extension StatusView.ViewModel { ).eraseToAnyPublisher() .sink { tupleOne, tupleTwo, tupleThree in let (authorName, isMyself) = tupleOne - let (isMuting, isBlocking, isBookmark) = tupleTwo + let (isMuting, isBlocking, isBookmark, isFollowed) = tupleTwo let (translatedFromLanguage, language) = tupleThree guard let name = authorName?.string else { @@ -704,6 +706,7 @@ extension StatusView.ViewModel { isBlocking: isBlocking, isMyself: isMyself, isBookmarking: isBookmark, + isFollowed: isFollowed, isTranslationEnabled: instanceConfigurationV2?.translation?.enabled == true, isTranslated: translatedFromLanguage != nil, statusLanguage: language diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift index 652c8c545..ed1d5ed8c 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift @@ -792,7 +792,6 @@ extension StatusView: StatusMetricViewDelegate { // MARK: - MastodonMenuDelegate extension StatusView: MastodonMenuDelegate { public func menuAction(_ action: MastodonMenu.Action) { - logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)") delegate?.statusView(self, menuButton: authorView.menuButton, didSelectAction: action) } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift b/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift index 3d06ffa23..faef873d1 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Menu/MastodonMenu.swift @@ -57,6 +57,7 @@ extension MastodonMenu { case shareStatus case deleteStatus case editStatus + case followUser(FollowUserActionContext) func build(delegate: MastodonMenuDelegate) -> LabeledAction { switch self { @@ -171,6 +172,22 @@ extension MastodonMenu { } return editStatusAction + case .followUser(let context): + let title: String + let image: UIImage? + if context.isFollowing { + title = L10n.Common.Controls.Actions.unfollow(context.name) + image = UIImage(systemName: "person.fill.badge.minus") + } else { + title = L10n.Common.Controls.Actions.follow(context.name) + image = UIImage(systemName: "person.fill.badge.plus") + } + let action = LabeledAction(title: title, image: image) { [weak delegate] in + guard let delegate = delegate else { return } + delegate.menuAction(self) + } + return action + } // end switch } // end func build } // end enum Action @@ -236,4 +253,15 @@ extension MastodonMenu { self.language = language } } + + public struct FollowUserActionContext { + + public let name: String + public let isFollowing: Bool + + init(name: String, isFollowing: Bool) { + self.name = name + self.isFollowing = isFollowing + } + } }