diff --git a/View Controllers/NewStatusViewController.swift b/View Controllers/NewStatusViewController.swift index 7506bdd..c5eeab6 100644 --- a/View Controllers/NewStatusViewController.swift +++ b/View Controllers/NewStatusViewController.swift @@ -28,6 +28,11 @@ final class NewStatusViewController: UIViewController { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) + + NotificationCenter.default.publisher(for: UIResponder.keyboardDidChangeFrameNotification) + .merge(with: NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) + .sink { [weak self] in self?.adjustContentInset(notification: $0) } + .store(in: &cancellables) } @available(*, unavailable) @@ -66,7 +71,10 @@ final class NewStatusViewController: UIViewController { activityIndicatorView.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor) ]) - setupBarButtonItems(identityContext: viewModel.identityContext) + navigationItem.leftBarButtonItem = UIBarButtonItem( + systemItem: .cancel, + primaryAction: UIAction { [weak self] _ in self?.dismiss() }) + navigationItem.rightBarButtonItem = postButton postButton.primaryAction = UIAction(title: NSLocalizedString("post", comment: "")) { [weak self] _ in self?.viewModel.post() @@ -84,11 +92,6 @@ final class NewStatusViewController: UIViewController { } #endif - NotificationCenter.default.publisher(for: UIResponder.keyboardDidChangeFrameNotification) - .merge(with: NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) - .sink { [weak self] in self?.adjustContentInset(notification: $0) } - .store(in: &cancellables) - setupViewModelBindings() } } @@ -150,6 +153,8 @@ private extension NewStatusViewController { presentAttachmentEditor( attachmentViewModel: attachmentViewModel, compositionViewModel: compositionViewModel) + case let .changeIdentity(identity): + changeIdentity(identity) } } @@ -234,9 +239,6 @@ private extension NewStatusViewController { viewModel.$compositionViewModels .sink { [weak self] in self?.set(compositionViewModels: $0) } .store(in: &cancellables) - viewModel.$identityContext - .sink { [weak self] in self?.setupBarButtonItems(identityContext: $0) } - .store(in: &cancellables) viewModel.$postingState .sink { [weak self] in self?.apply(postingState: $0) } .store(in: &cancellables) @@ -250,18 +252,6 @@ private extension NewStatusViewController { .store(in: &cancellables) } - func setupBarButtonItems(identityContext: IdentityContext) { - let cancelButton = UIBarButtonItem( - systemItem: .cancel, - primaryAction: UIAction { [weak self] _ in self?.dismiss() }) - - navigationItem.leftBarButtonItem = cancelButton - navigationItem.titleView = viewModel.canChangeIdentity - ? changeIdentityButton(identityContext: identityContext) - : nil - navigationItem.rightBarButtonItem = postButton - } - func presentMediaPicker(compositionViewModel: CompositionViewModel) { mediaSelections.first().sink { [weak self] results in guard let self = self, let result = results.first else { return } @@ -422,44 +412,6 @@ private extension NewStatusViewController { } } - func changeIdentityButton(identityContext: IdentityContext) -> UIButton { - let changeIdentityButton = UIButton() - let downsampled = KingfisherOptionsInfo.downsampled( - dimension: .barButtonItemDimension, - scaleFactor: UIScreen.main.scale) - - let menuItems = viewModel.authenticatedIdentities - .filter { $0.id != identityContext.identity.id } - .map { identity in - UIDeferredMenuElement { completion in - let action = UIAction(title: identity.handle) { [weak self] _ in - self?.changeIdentity(identity) - } - - if let image = identity.image { - KingfisherManager.shared.retrieveImage(with: image, options: downsampled) { - if case let .success(value) = $0 { - action.image = value.image - } - - completion([action]) - } - } else { - completion([action]) - } - } - } - - changeIdentityButton.kf.setImage( - with: identityContext.identity.image, - for: .normal, - options: downsampled) - changeIdentityButton.showsMenuAsPrimaryAction = true - changeIdentityButton.menu = UIMenu(children: menuItems) - - return changeIdentityButton - } - func changeIdentity(_ identity: Identity) { if viewModel.compositionViewModels.contains(where: { !$0.attachmentViewModels.isEmpty }) { let alertController = UIAlertController( diff --git a/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift b/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift index ab6aa80..4d61790 100644 --- a/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift +++ b/ViewModels/Sources/ViewModels/View Models/NewStatusViewModel.swift @@ -67,6 +67,10 @@ public final class NewStatusViewModel: ObservableObject { allIdentitiesService.authenticatedIdentitiesPublisher() .assignErrorsToAlertItem(to: \.alertItem, on: self) + .combineLatest($identityContext) + .map { authenticatedIdentities, currentIdentity in + authenticatedIdentities.filter { $0.id != currentIdentity.identity.id } + } .assign(to: &$authenticatedIdentities) $compositionViewModels.flatMap { Publishers.MergeMany($0.map(\.$isPostable)) } .receive(on: DispatchQueue.main) // hack to punt to next run loop, consider refactoring @@ -87,6 +91,7 @@ public extension NewStatusViewModel { case presentDocumentPicker(CompositionViewModel) case presentEmojiPicker(Int) case editAttachment(AttachmentViewModel, CompositionViewModel) + case changeIdentity(Identity) } enum PostingState { @@ -159,6 +164,10 @@ public extension NewStatusViewModel { post(viewModel: unposted, inReplyToId: inReplyToViewModel?.id) } + + func changeIdentity(_ identity: Identity) { + eventsSubject.send(.changeIdentity(identity)) + } } private extension NewStatusViewModel { diff --git a/Views/CompositionView.swift b/Views/CompositionView.swift index 870c7da..5e3914a 100644 --- a/Views/CompositionView.swift +++ b/Views/CompositionView.swift @@ -6,7 +6,8 @@ import UIKit import ViewModels final class CompositionView: UIView { - let avatarImageView = UIImageView() + let avatarImageView = AnimatedImageView() + let changeIdentityButton = UIButton() let spoilerTextField = UITextField() let textView = UITextView() let textViewPlaceholder = UILabel() @@ -64,6 +65,13 @@ private extension CompositionView { avatarImageView.clipsToBounds = true avatarImageView.setContentHuggingPriority(.required, for: .horizontal) + changeIdentityButton.translatesAutoresizingMaskIntoConstraints = false + avatarImageView.addSubview(changeIdentityButton) + avatarImageView.isUserInteractionEnabled = true + changeIdentityButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted) + changeIdentityButton.showsMenuAsPrimaryAction = true + changeIdentityButton.menu = changeIdentityMenu(identities: parentViewModel.authenticatedIdentities) + let stackView = UIStackView() addSubview(stackView) @@ -179,6 +187,10 @@ private extension CompositionView { .sink { [weak self] in self?.avatarImageView.kf.setImage(with: $0) } .store(in: &cancellables) + parentViewModel.$authenticatedIdentities + .sink { [weak self] in self?.changeIdentityButton.menu = self?.changeIdentityMenu(identities: $0) } + .store(in: &cancellables) + viewModel.$attachmentViewModels .receive(on: RunLoop.main) .sink { [weak self] attachmentViewModels in @@ -209,6 +221,10 @@ private extension CompositionView { avatarImageView.topAnchor.constraint(equalTo: guide.topAnchor), avatarImageView.leadingAnchor.constraint(equalTo: guide.leadingAnchor), avatarImageView.bottomAnchor.constraint(lessThanOrEqualTo: guide.bottomAnchor), + changeIdentityButton.leadingAnchor.constraint(equalTo: avatarImageView.leadingAnchor), + changeIdentityButton.topAnchor.constraint(equalTo: avatarImageView.topAnchor), + changeIdentityButton.bottomAnchor.constraint(equalTo: avatarImageView.bottomAnchor), + changeIdentityButton.trailingAnchor.constraint(equalTo: avatarImageView.trailingAnchor), stackView.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: .defaultSpacing), stackView.topAnchor.constraint(greaterThanOrEqualTo: guide.topAnchor), stackView.bottomAnchor.constraint(lessThanOrEqualTo: guide.bottomAnchor), @@ -228,4 +244,30 @@ private extension CompositionView { NSLayoutConstraint.activate(constraints) } + + func changeIdentityMenu(identities: [Identity]) -> UIMenu { + UIMenu(children: identities.map { identity in + UIDeferredMenuElement { completion in + let action = UIAction(title: identity.handle) { [weak self] _ in + self?.parentViewModel.changeIdentity(identity) + } + + if let image = identity.image { + KingfisherManager.shared.retrieveImage( + with: image, + options: KingfisherOptionsInfo.downsampled( + dimension: .barButtonItemDimension, + scaleFactor: UIScreen.main.scale)) { + if case let .success(value) = $0 { + action.image = value.image + } + + completion([action]) + } + } else { + completion([action]) + } + } + }) + } }