From a0141f7750b98826ccee98bf7f99e56cdcf2bec1 Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Mon, 1 Mar 2021 16:53:36 -0800 Subject: [PATCH] DM button --- Localizations/Localizable.strings | 1 + View Controllers/TableViewController.swift | 9 ++--- .../Entities/CollectionItemEvent.swift | 2 +- .../CollectionItemsViewModel.swift | 9 +++++ .../View Models/NewStatusViewModel.swift | 4 +++ .../View Models/ProfileViewModel.swift | 6 ++++ .../View Models/RootViewModel.swift | 4 ++- .../ShareExtensionNavigationViewModel.swift | 1 + .../View Models/StatusViewModel.swift | 2 +- Views/UIKit/AccountHeaderView.swift | 36 +++++++++++++++---- 10 files changed, 61 insertions(+), 13 deletions(-) diff --git a/Localizations/Localizable.strings b/Localizations/Localizable.strings index 2b89104..c30c36f 100644 --- a/Localizations/Localizable.strings +++ b/Localizations/Localizable.strings @@ -11,6 +11,7 @@ "account.block-and-report" = "Block & report"; "account.block.confirm-%@" = "Block %@?"; "account.blocked" = "Blocked"; +"account.direct-message" = "Direct message"; "account.domain-block-%@" = "Block domain %@"; "account.domain-block.confirm-%@" = "Block domain %@?"; "account.domain-unblock-%@" = "Unblock domain %@"; diff --git a/View Controllers/TableViewController.swift b/View Controllers/TableViewController.swift index 8f874d7..30384ce 100644 --- a/View Controllers/TableViewController.swift +++ b/View Controllers/TableViewController.swift @@ -495,8 +495,8 @@ private extension TableViewController { handle(navigation: navigation) case let .attachment(attachmentViewModel, statusViewModel): present(attachmentViewModel: attachmentViewModel, statusViewModel: statusViewModel) - case let .compose(inReplyToViewModel, redraft): - compose(inReplyToViewModel: inReplyToViewModel, redraft: redraft) + case let .compose(inReplyToViewModel, redraft, directMessageTo): + compose(inReplyToViewModel: inReplyToViewModel, redraft: redraft, directMessageTo: directMessageTo) case let .confirmDelete(statusViewModel, redraft): confirmDelete(statusViewModel: statusViewModel, redraft: redraft) case let .confirmUnfollow(accountViewModel): @@ -595,11 +595,12 @@ private extension TableViewController { } } - func compose(inReplyToViewModel: StatusViewModel?, redraft: Status?) { + func compose(inReplyToViewModel: StatusViewModel?, redraft: Status?, directMessageTo: AccountViewModel?) { rootViewModel?.navigationViewModel?.presentedNewStatusViewModel = rootViewModel?.newStatusViewModel( identityContext: viewModel.identityContext, inReplyTo: inReplyToViewModel, - redraft: redraft) + redraft: redraft, + directMessageTo: directMessageTo) } func confirmDelete(statusViewModel: StatusViewModel, redraft: Bool) { diff --git a/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift b/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift index 9341c7a..3aec958 100644 --- a/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift +++ b/ViewModels/Sources/ViewModels/Entities/CollectionItemEvent.swift @@ -9,7 +9,7 @@ public enum CollectionItemEvent { case refresh case navigation(Navigation) case attachment(AttachmentViewModel, StatusViewModel) - case compose(inReplyTo: StatusViewModel?, redraft: Status?) + case compose(inReplyTo: StatusViewModel? = nil, redraft: Status? = nil, directMessageTo: AccountViewModel? = nil) case confirmDelete(StatusViewModel, redraft: Bool) case confirmUnfollow(AccountViewModel) case confirmHideReblogs(AccountViewModel) diff --git a/ViewModels/Sources/ViewModels/View Models/CollectionItemsViewModel.swift b/ViewModels/Sources/ViewModels/View Models/CollectionItemsViewModel.swift index 0fc1b51..26c4736 100644 --- a/ViewModels/Sources/ViewModels/View Models/CollectionItemsViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/CollectionItemsViewModel.swift @@ -358,6 +358,15 @@ extension CollectionItemsViewModel: CollectionViewModel { } } +extension CollectionItemsViewModel { + func sendDirectMessage(accountViewModel: AccountViewModel) { + eventsSubject.send( + Just(.compose(directMessageTo: accountViewModel)) + .setFailureType(to: Error.self) + .eraseToAnyPublisher()) + } +} + private extension CollectionItemsViewModel { private static let lastReadIdDebounceInterval: TimeInterval = 0.5 diff --git a/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift b/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift index 2b13f82..5ee1b52 100644 --- a/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift @@ -29,6 +29,7 @@ public final class NewStatusViewModel: ObservableObject { environment: AppEnvironment, inReplyTo: StatusViewModel?, redraft: Status?, + directMessageTo: AccountViewModel?, extensionContext: NSExtensionContext?) { self.allIdentitiesService = allIdentitiesService self.identityContext = identityContext @@ -78,6 +79,9 @@ public final class NewStatusViewModel: ObservableObject { .map("@".appending)) compositionViewModel.text = mentions.joined(separator: " ").appending(" ") + } else if let directMessageTo = directMessageTo { + compositionViewModel.text = directMessageTo.accountName.appending(" ") + visibility = .direct } compositionViewModels = [compositionViewModel] diff --git a/ViewModels/Sources/ViewModels/View Models/ProfileViewModel.swift b/ViewModels/Sources/ViewModels/View Models/ProfileViewModel.swift index f615361..c9a4fbd 100644 --- a/ViewModels/Sources/ViewModels/View Models/ProfileViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/ProfileViewModel.swift @@ -76,6 +76,12 @@ public extension ProfileViewModel { func fetchProfile() -> AnyPublisher { profileService.fetchProfile().assignErrorsToAlertItem(to: \.alertItem, on: self) } + + func sendDirectMessage() { + guard let accountViewModel = accountViewModel else { return } + + collectionViewModel.value.sendDirectMessage(accountViewModel: accountViewModel) + } } extension ProfileViewModel: CollectionViewModel { diff --git a/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift b/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift index a1f9d37..592cbb5 100644 --- a/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/RootViewModel.swift @@ -68,13 +68,15 @@ public extension RootViewModel { func newStatusViewModel( identityContext: IdentityContext, inReplyTo: StatusViewModel? = nil, - redraft: Status? = nil) -> NewStatusViewModel { + redraft: Status? = nil, + directMessageTo: AccountViewModel? = nil) -> NewStatusViewModel { NewStatusViewModel( allIdentitiesService: allIdentitiesService, identityContext: identityContext, environment: environment, inReplyTo: inReplyTo, redraft: redraft, + directMessageTo: directMessageTo, extensionContext: nil) } } diff --git a/ViewModels/Sources/ViewModels/View Models/ShareExtensionNavigationViewModel.swift b/ViewModels/Sources/ViewModels/View Models/ShareExtensionNavigationViewModel.swift index 0430f61..b2ca759 100644 --- a/ViewModels/Sources/ViewModels/View Models/ShareExtensionNavigationViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/ShareExtensionNavigationViewModel.swift @@ -39,6 +39,7 @@ public extension ShareExtensionNavigationViewModel { environment: environment, inReplyTo: nil, redraft: nil, + directMessageTo: nil, extensionContext: extensionContext) } } diff --git a/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift b/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift index e902451..d68045e 100644 --- a/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/StatusViewModel.swift @@ -250,7 +250,7 @@ public extension StatusViewModel { replyViewModel.configuration = configuration.reply() eventsSubject.send( - Just(.compose(inReplyTo: replyViewModel, redraft: nil)) + Just(.compose(inReplyTo: replyViewModel)) .setFailureType(to: Error.self) .eraseToAnyPublisher()) } diff --git a/Views/UIKit/AccountHeaderView.swift b/Views/UIKit/AccountHeaderView.swift index ee47437..931475f 100644 --- a/Views/UIKit/AccountHeaderView.swift +++ b/Views/UIKit/AccountHeaderView.swift @@ -13,6 +13,7 @@ final class AccountHeaderView: UIView { let avatarImageView = SDAnimatedImageView() let avatarButton = UIButton() let relationshipButtonsStackView = UIStackView() + let directMessageButton = UIButton() let followButton = UIButton(type: .system) let unfollowButton = UIButton(type: .system) let notifyButton = UIButton() @@ -216,10 +217,18 @@ final class AccountHeaderView: UIView { override func layoutSubviews() { super.layoutSubviews() - for button in [followButton, unfollowButton, notifyButton, unnotifyButton] { - let inset = (followButton.bounds.height - (button.titleLabel?.bounds.height ?? 0)) / 2 + if let pointSize = followingButton.titleLabel?.font.pointSize { + relationshipButtonsStackView.heightAnchor + .constraint(equalToConstant: pointSize + .defaultSpacing * 2).isActive = true + } + + for button in [followButton, unfollowButton] { + let inset = (button.bounds.height - (button.titleLabel?.bounds.height ?? 0)) button.contentEdgeInsets = .init(top: 0, left: inset, bottom: 0, right: inset) + } + + for button in [directMessageButton, followButton, unfollowButton, notifyButton, unnotifyButton] { button.layer.cornerRadius = button.bounds.height / 2 } } @@ -299,19 +308,30 @@ private extension AccountHeaderView { relationshipButtonsStackView.spacing = .defaultSpacing relationshipButtonsStackView.addArrangedSubview(UIView()) - for button in [followButton, unfollowButton, notifyButton, unnotifyButton] { + for button in [directMessageButton, notifyButton, unnotifyButton, followButton, unfollowButton] { relationshipButtonsStackView.addArrangedSubview(button) button.titleLabel?.font = .preferredFont(forTextStyle: .headline) button.titleLabel?.adjustsFontForContentSizeCategory = true button.backgroundColor = .secondarySystemBackground } + directMessageButton.setImage( + UIImage( + systemName: "envelope", + withConfiguration: UIImage.SymbolConfiguration(scale: .small)), + for: .normal) + directMessageButton.accessibilityLabel = NSLocalizedString("account.direct-message", comment: "") + directMessageButton.addAction( + UIAction { [weak self] _ in self?.viewModel.sendDirectMessage() }, + for: .touchUpInside) + followButton.setImage( UIImage( systemName: "person.badge.plus", withConfiguration: UIImage.SymbolConfiguration(scale: .small)), for: .normal) followButton.isHidden = true + followButton.titleLabel?.adjustsFontSizeToFitWidth = true followButton.addAction( UIAction { [weak self] _ in self?.viewModel.accountViewModel?.follow() }, for: .touchUpInside) @@ -323,14 +343,16 @@ private extension AccountHeaderView { for: .normal) unfollowButton.setTitle(NSLocalizedString("account.following", comment: ""), for: .normal) unfollowButton.isHidden = true + unfollowButton.titleLabel?.adjustsFontSizeToFitWidth = true unfollowButton.addAction( UIAction { [weak self] _ in self?.viewModel.accountViewModel?.confirmUnfollow() }, for: .touchUpInside) notifyButton.setImage( UIImage(systemName: "bell", - withConfiguration: UIImage.SymbolConfiguration(scale: .large)), + withConfiguration: UIImage.SymbolConfiguration(scale: .medium)), for: .normal) + notifyButton.imageView?.contentMode = .scaleAspectFit notifyButton.accessibilityLabel = NSLocalizedString("account.notify", comment: "") notifyButton.tintColor = .secondaryLabel notifyButton.isHidden = true @@ -340,7 +362,7 @@ private extension AccountHeaderView { unnotifyButton.setImage( UIImage(systemName: "bell.fill", - withConfiguration: UIImage.SymbolConfiguration(scale: .large)), + withConfiguration: UIImage.SymbolConfiguration(scale: .medium)), for: .normal) unnotifyButton.accessibilityLabel = NSLocalizedString("account.unnotify", comment: "") unnotifyButton.isHidden = true @@ -505,7 +527,9 @@ private extension AccountHeaderView { equalTo: headerImageView.bottomAnchor, constant: .defaultSpacing), relationshipButtonsStackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor), - relationshipButtonsStackView.bottomAnchor.constraint(equalTo: avatarBackgroundView.bottomAnchor), + directMessageButton.widthAnchor.constraint(equalTo: directMessageButton.heightAnchor), + notifyButton.widthAnchor.constraint(equalTo: notifyButton.heightAnchor), + unnotifyButton.widthAnchor.constraint(equalTo: unnotifyButton.heightAnchor), baseStackView.topAnchor.constraint(equalTo: avatarBackgroundView.bottomAnchor, constant: .defaultSpacing), baseStackView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor), baseStackView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),