Add follow/unfollow-option to user-section (IOS-103)

This commit is contained in:
Nathan Mattes 2023-06-02 18:18:11 +02:00
parent 9b422a95ac
commit d455da85d2
11 changed files with 97 additions and 9 deletions

View File

@ -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",

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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 %@";

View File

@ -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: ""

View File

@ -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

View File

@ -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<Date, Never>) {
@ -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)

View File

@ -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

View File

@ -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)
}
}

View File

@ -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
}
}
}