From 78355a8afcaea0b8d3d0d79ead5c0071c4547093 Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Wed, 10 Feb 2021 18:04:04 -0800 Subject: [PATCH] Block / mute UI --- DB/Sources/DB/Entities/CollectionItem.swift | 2 + Localizations/Localizable.strings | 2 + Metatext.xcodeproj/project.pbxproj | 8 +-- .../AccountsEndpoint+Extensions.swift | 4 ++ .../View Models/NavigationViewModel.swift | 16 +++++ Views/SwiftUI/PreferencesView.swift | 15 +++-- Views/UIKit/AccountHeaderView.swift | 14 ++++- ...llowsYouLabel.swift => CapsuleLabel.swift} | 4 +- Views/UIKit/Content Views/AccountView.swift | 59 +++++++++++++++++++ 9 files changed, 111 insertions(+), 13 deletions(-) rename Views/UIKit/{FollowsYouLabel.swift => CapsuleLabel.swift} (94%) diff --git a/DB/Sources/DB/Entities/CollectionItem.swift b/DB/Sources/DB/Entities/CollectionItem.swift index 3f7632d..f85227a 100644 --- a/DB/Sources/DB/Entities/CollectionItem.swift +++ b/DB/Sources/DB/Entities/CollectionItem.swift @@ -42,6 +42,8 @@ public extension CollectionItem { case withNote case withoutNote case followRequest + case mute + case block } var itemId: Id? { diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index a9d0000..57e5731 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -10,6 +10,7 @@ "account.block" = "Block"; "account.block-and-report" = "Block & report"; "account.block.confirm-%@" = "Block %@?"; +"account.blocked" = "Blocked"; "account.domain-block-%@" = "Block domain %@"; "account.domain-block.confirm-%@" = "Block domain %@?"; "account.domain-unblock-%@" = "Unblock domain %@"; @@ -30,6 +31,7 @@ "account.mute.confirm.hide-notifications" = "Hide notifications from this user?"; "account.mute.confirm.duration" = "Duration"; "account.mute.target-%@" = "Muting %@"; +"account.muted" = "Muted"; "account.reject-follow-request-button.accessibility-label" = "Reject follow request"; "account.request" = "Request"; "account.request.cancel" = "Cancel follow request"; diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index a54f48c..417eb39 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -62,7 +62,7 @@ D03D87F425C23C44004DCBB2 /* SecondaryNavigationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03D87F325C23C44004DCBB2 /* SecondaryNavigationTitleView.swift */; }; D0477F1525C68BAC005C5368 /* PrefetchRequestModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0477F1425C68BAC005C5368 /* PrefetchRequestModifier.swift */; }; D0477F2C25C6EBAD005C5368 /* OpenInDefaultBrowserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0477F2B25C6EBAD005C5368 /* OpenInDefaultBrowserActivity.swift */; }; - D0477F4625C72E50005C5368 /* FollowsYouLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0477F4525C72E50005C5368 /* FollowsYouLabel.swift */; }; + D0477F4625C72E50005C5368 /* CapsuleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0477F4525C72E50005C5368 /* CapsuleLabel.swift */; }; D04F9E8E259E9C950081B0C9 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D04F9E8D259E9C950081B0C9 /* ViewModels */; }; D05936CF25A8D79800754FDF /* EditAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05936CE25A8D79800754FDF /* EditAttachmentViewController.swift */; }; D05936D025A8D79800754FDF /* EditAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05936CE25A8D79800754FDF /* EditAttachmentViewController.swift */; }; @@ -271,7 +271,7 @@ D03D87F325C23C44004DCBB2 /* SecondaryNavigationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondaryNavigationTitleView.swift; sourceTree = ""; }; D0477F1425C68BAC005C5368 /* PrefetchRequestModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefetchRequestModifier.swift; sourceTree = ""; }; D0477F2B25C6EBAD005C5368 /* OpenInDefaultBrowserActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInDefaultBrowserActivity.swift; sourceTree = ""; }; - D0477F4525C72E50005C5368 /* FollowsYouLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowsYouLabel.swift; sourceTree = ""; }; + D0477F4525C72E50005C5368 /* CapsuleLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsuleLabel.swift; sourceTree = ""; }; D047FA8C24C3E21200AF17C5 /* Metatext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metatext.app; sourceTree = BUILT_PRODUCTS_DIR; }; D05936CE25A8D79800754FDF /* EditAttachmentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAttachmentViewController.swift; sourceTree = ""; }; D05936DD25A937EC00754FDF /* EditThumbnailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditThumbnailView.swift; sourceTree = ""; }; @@ -459,7 +459,7 @@ D05936DD25A937EC00754FDF /* EditThumbnailView.swift */, D07EC7FC25B16994006DF726 /* EmojiCategoryHeaderView.swift */, D0DDA77E25C6058300FA0F91 /* ExploreSectionHeaderView.swift */, - D0477F4525C72E50005C5368 /* FollowsYouLabel.swift */, + D0477F4525C72E50005C5368 /* CapsuleLabel.swift */, D0BE97D625D0863E0057E161 /* ImagePastableTextView.swift */, D0D2AC6625BD0484003D5DF2 /* LineChartView.swift */, D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */, @@ -1114,7 +1114,7 @@ D0E1F583251F13EC00D45315 /* WebfingerIndicatorView.swift in Sources */, D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */, D0DDA76B25C5F20800FA0F91 /* ExploreDataSource.swift in Sources */, - D0477F4625C72E50005C5368 /* FollowsYouLabel.swift in Sources */, + D0477F4625C72E50005C5368 /* CapsuleLabel.swift in Sources */, D035F88725B8016000DC75ED /* NavigationViewModel+Extensions.swift in Sources */, D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */, D09D972225C65682007E6394 /* SeparatorConfiguredCollectionViewListCell.swift in Sources */, diff --git a/ServiceLayer/Sources/ServiceLayer/Extensions/AccountsEndpoint+Extensions.swift b/ServiceLayer/Sources/ServiceLayer/Extensions/AccountsEndpoint+Extensions.swift index 2272ebf..e184087 100644 --- a/ServiceLayer/Sources/ServiceLayer/Extensions/AccountsEndpoint+Extensions.swift +++ b/ServiceLayer/Sources/ServiceLayer/Extensions/AccountsEndpoint+Extensions.swift @@ -5,6 +5,10 @@ import MastodonAPI extension AccountsEndpoint { var configuration: CollectionItem.AccountConfiguration { switch self { + case .mutes: + return .mute + case .blocks: + return .block case .followRequests: return .followRequest default: diff --git a/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift b/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift index 6c65a5d..9dcb3ad 100644 --- a/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/NavigationViewModel.swift @@ -114,6 +114,22 @@ public extension NavigationViewModel { titleComponents: ["follow-requests"]))) } + func navigateToMutedUsers() { + presentingSecondaryNavigation = false + presentedNewStatusViewModel = nil + navigationsSubject.send(.collection(identityContext.service.service( + accountList: .mutes, + titleComponents: ["preferences.muted-users"]))) + } + + func navigateToBlockedUsers() { + presentingSecondaryNavigation = false + presentedNewStatusViewModel = nil + navigationsSubject.send(.collection(identityContext.service.service( + accountList: .blocks, + titleComponents: ["preferences.blocked-users"]))) + } + func navigate(pushNotification: PushNotification) { switch pushNotification.notificationType { case .followRequest: diff --git a/Views/SwiftUI/PreferencesView.swift b/Views/SwiftUI/PreferencesView.swift index 5975956..6bf2f5a 100644 --- a/Views/SwiftUI/PreferencesView.swift +++ b/Views/SwiftUI/PreferencesView.swift @@ -7,6 +7,7 @@ import ViewModels struct PreferencesView: View { @StateObject var viewModel: PreferencesViewModel @StateObject var identityContext: IdentityContext + @EnvironmentObject var rootViewModel: RootViewModel @Environment(\.accessibilityReduceMotion) var accessibilityReduceMotion init(viewModel: PreferencesViewModel) { @@ -27,12 +28,14 @@ struct PreferencesView: View { destination: NotificationTypesPreferencesView( viewModel: .init(identityContext: viewModel.identityContext))) } - NavigationLink("preferences.muted-users", - destination: TableView(viewModelClosure: viewModel.mutedUsersViewModel) - .navigationTitle(Text("preferences.muted-users"))) - NavigationLink("preferences.blocked-users", - destination: TableView(viewModelClosure: viewModel.blockedUsersViewModel) - .navigationTitle(Text("preferences.blocked-users"))) + Button("preferences.muted-users") { + rootViewModel.navigationViewModel?.navigateToMutedUsers() + } + .foregroundColor(.primary) + Button("preferences.blocked-users") { + rootViewModel.navigationViewModel?.navigateToBlockedUsers() + } + .foregroundColor(.primary) NavigationLink("preferences.blocked-domains", destination: DomainBlocksView(viewModel: viewModel.domainBlocksViewModel())) Toggle("preferences.use-preferences-from-server", diff --git a/Views/UIKit/AccountHeaderView.swift b/Views/UIKit/AccountHeaderView.swift index 99b3a58..93d759a 100644 --- a/Views/UIKit/AccountHeaderView.swift +++ b/Views/UIKit/AccountHeaderView.swift @@ -19,7 +19,9 @@ final class AccountHeaderView: UIView { let accountStackView = UIStackView() let accountLabel = UILabel() let lockedImageView = UIImageView() - let followsYouLabel = FollowsYouLabel() + let followsYouLabel = CapsuleLabel() + let mutedLabel = CapsuleLabel() + let blockedLabel = CapsuleLabel() let fieldsStackView = UIStackView() let noteTextView = TouchFallthroughTextView() let followStackView = UIStackView() @@ -48,6 +50,8 @@ final class AccountHeaderView: UIView { if !accountViewModel.isSelf, let relationship = accountViewModel.relationship { followsYouLabel.isHidden = !relationship.followedBy + mutedLabel.isHidden = !relationship.muting + blockedLabel.isHidden = !relationship.blocking followButton.setTitle( NSLocalizedString( accountViewModel.isLocked ? "account.request" : "account.follow", @@ -316,6 +320,14 @@ private extension AccountHeaderView { followsYouLabel.text = NSLocalizedString("account.follows-you", comment: "") followsYouLabel.isHidden = true + accountStackView.addArrangedSubview(mutedLabel) + mutedLabel.text = NSLocalizedString("account.muted", comment: "") + mutedLabel.isHidden = true + + accountStackView.addArrangedSubview(blockedLabel) + blockedLabel.text = NSLocalizedString("account.blocked", comment: "") + blockedLabel.isHidden = true + accountStackView.addArrangedSubview(UIView()) baseStackView.addArrangedSubview(fieldsStackView) diff --git a/Views/UIKit/FollowsYouLabel.swift b/Views/UIKit/CapsuleLabel.swift similarity index 94% rename from Views/UIKit/FollowsYouLabel.swift rename to Views/UIKit/CapsuleLabel.swift index 63c2c39..48a28c8 100644 --- a/Views/UIKit/FollowsYouLabel.swift +++ b/Views/UIKit/CapsuleLabel.swift @@ -2,7 +2,7 @@ import UIKit -final class FollowsYouLabel: UILabel { +final class CapsuleLabel: UILabel { override init(frame: CGRect) { super.init(frame: frame) @@ -34,7 +34,7 @@ final class FollowsYouLabel: UILabel { } } -private extension FollowsYouLabel { +private extension CapsuleLabel { var inset: CGFloat { bounds.height / 2 } func initialSetup() { diff --git a/Views/UIKit/Content Views/AccountView.swift b/Views/UIKit/Content Views/AccountView.swift index acc1dd5..8e8153b 100644 --- a/Views/UIKit/Content Views/AccountView.swift +++ b/Views/UIKit/Content Views/AccountView.swift @@ -12,6 +12,10 @@ final class AccountView: UIView { let noteTextView = TouchFallthroughTextView() let acceptFollowRequestButton = UIButton() let rejectFollowRequestButton = UIButton() + let muteButton = UIButton(type: .system) + let unmuteButton = UIButton(type: .system) + let blockButton = UIButton(type: .system) + let unblockButton = UIButton(type: .system) private var accountConfiguration: AccountContentConfiguration @@ -135,6 +139,38 @@ private extension AccountView { UIAction { [weak self] _ in self?.accountConfiguration.viewModel.rejectFollowRequest() }, for: .touchUpInside) + stackView.addArrangedSubview(muteButton) + muteButton.setTitle(NSLocalizedString("account.mute", comment: ""), for: .normal) + muteButton.titleLabel?.adjustsFontForContentSizeCategory = true + muteButton.titleLabel?.font = .preferredFont(forTextStyle: .callout) + muteButton.addAction( + UIAction { [weak self] _ in self?.accountConfiguration.viewModel.confirmMute() }, + for: .touchUpInside) + + stackView.addArrangedSubview(unmuteButton) + unmuteButton.setTitle(NSLocalizedString("account.unmute", comment: ""), for: .normal) + unmuteButton.titleLabel?.adjustsFontForContentSizeCategory = true + unmuteButton.titleLabel?.font = .preferredFont(forTextStyle: .callout) + unmuteButton.addAction( + UIAction { [weak self] _ in self?.accountConfiguration.viewModel.confirmUnmute() }, + for: .touchUpInside) + + stackView.addArrangedSubview(blockButton) + blockButton.setTitle(NSLocalizedString("account.block", comment: ""), for: .normal) + blockButton.titleLabel?.adjustsFontForContentSizeCategory = true + blockButton.titleLabel?.font = .preferredFont(forTextStyle: .callout) + blockButton.addAction( + UIAction { [weak self] _ in self?.accountConfiguration.viewModel.confirmBlock() }, + for: .touchUpInside) + + stackView.addArrangedSubview(unblockButton) + unblockButton.setTitle(NSLocalizedString("account.unblock", comment: ""), for: .normal) + unblockButton.titleLabel?.adjustsFontForContentSizeCategory = true + unblockButton.titleLabel?.font = .preferredFont(forTextStyle: .callout) + unblockButton.addAction( + UIAction { [weak self] _ in self?.accountConfiguration.viewModel.confirmUnblock() }, + for: .touchUpInside) + NSLayoutConstraint.activate([ avatarImageView.widthAnchor.constraint(equalToConstant: .avatarDimension), avatarImageView.heightAnchor.constraint(equalToConstant: .avatarDimension), @@ -142,6 +178,10 @@ private extension AccountView { acceptFollowRequestButton.heightAnchor.constraint(greaterThanOrEqualToConstant: .avatarDimension), rejectFollowRequestButton.widthAnchor.constraint(greaterThanOrEqualToConstant: .avatarDimension), rejectFollowRequestButton.heightAnchor.constraint(greaterThanOrEqualToConstant: .avatarDimension), + muteButton.heightAnchor.constraint(greaterThanOrEqualToConstant: .avatarDimension), + unmuteButton.heightAnchor.constraint(greaterThanOrEqualToConstant: .avatarDimension), + blockButton.heightAnchor.constraint(greaterThanOrEqualToConstant: .avatarDimension), + unblockButton.heightAnchor.constraint(greaterThanOrEqualToConstant: .avatarDimension), stackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), stackView.topAnchor.constraint(equalTo: readableContentGuide.topAnchor), stackView.bottomAnchor.constraint(equalTo: readableContentGuide.bottomAnchor), @@ -191,6 +231,25 @@ private extension AccountView { acceptFollowRequestButton.isHidden = !isFollowRequest rejectFollowRequestButton.isHidden = !isFollowRequest + if let relationship = viewModel.relationship { + if viewModel.configuration == .mute { + muteButton.isHidden = relationship.muting + unmuteButton.isHidden = !relationship.muting + blockButton.isHidden = true + unblockButton.isHidden = true + } else if viewModel.configuration == .block { + muteButton.isHidden = true + unmuteButton.isHidden = true + blockButton.isHidden = relationship.blocking + unblockButton.isHidden = !relationship.blocking + } + } else { + muteButton.isHidden = true + unmuteButton.isHidden = true + blockButton.isHidden = true + unblockButton.isHidden = true + } + let accessibilityAttributedLabel = NSMutableAttributedString(string: "") if !displayNameLabel.isHidden, let displayName = displayNameLabel.attributedText {