feat: add status visibility selector for status compose scene
This commit is contained in:
parent
610ee36835
commit
00e7450bcc
|
@ -219,6 +219,12 @@
|
|||
},
|
||||
"content_warning": {
|
||||
"placeholder": "Write an accurate warning here..."
|
||||
},
|
||||
"visibility": {
|
||||
"public": "Public",
|
||||
"unlisted": "Unlisted",
|
||||
"private": "Followers only",
|
||||
"direct": "Only people I mention"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,8 @@ extension ComposeStatusSection {
|
|||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplacableTextInput: cell.textEditorView, disposeBag: &cell.disposeBag)
|
||||
ComposeStatusSection.configureCustomEmojiPicker(viewModel: customEmojiPickerInputViewModel, customEmojiReplacableTextInput: cell.statusContentWarningEditorView.textView, disposeBag: &cell.disposeBag)
|
||||
|
||||
return cell
|
||||
case .attachment(let attachmentService):
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self), for: indexPath) as! ComposeStatusAttachmentCollectionViewCell
|
||||
|
|
|
@ -198,6 +198,16 @@ internal enum L10n {
|
|||
/// New Reply
|
||||
internal static let newReply = L10n.tr("Localizable", "Scene.Compose.Title.NewReply")
|
||||
}
|
||||
internal enum Visibility {
|
||||
/// Only people I mention
|
||||
internal static let direct = L10n.tr("Localizable", "Scene.Compose.Visibility.Direct")
|
||||
/// Followers only
|
||||
internal static let `private` = L10n.tr("Localizable", "Scene.Compose.Visibility.Private")
|
||||
/// Public
|
||||
internal static let `public` = L10n.tr("Localizable", "Scene.Compose.Visibility.Public")
|
||||
/// Unlisted
|
||||
internal static let unlisted = L10n.tr("Localizable", "Scene.Compose.Visibility.Unlisted")
|
||||
}
|
||||
}
|
||||
internal enum ConfirmEmail {
|
||||
/// We just sent an email to %@,\ntap the link to confirm your account.
|
||||
|
|
|
@ -60,6 +60,10 @@ uploaded to Mastodon.";
|
|||
"Scene.Compose.Poll.ThreeDays" = "3 Days";
|
||||
"Scene.Compose.Title.NewPost" = "New Post";
|
||||
"Scene.Compose.Title.NewReply" = "New Reply";
|
||||
"Scene.Compose.Visibility.Direct" = "Only people I mention";
|
||||
"Scene.Compose.Visibility.Private" = "Followers only";
|
||||
"Scene.Compose.Visibility.Public" = "Public";
|
||||
"Scene.Compose.Visibility.Unlisted" = "Unlisted";
|
||||
"Scene.ConfirmEmail.Button.DontReceiveEmail" = "I never got an email";
|
||||
"Scene.ConfirmEmail.Button.OpenEmailApp" = "Open Email App";
|
||||
"Scene.ConfirmEmail.DontReceiveEmail.Description" = "Check if your email address is correct as well as your junk folder if you haven’t.";
|
||||
|
|
|
@ -270,6 +270,14 @@ extension ComposeViewController {
|
|||
self.resetImagePicker()
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
viewModel.selectedStatusVisibility
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] type in
|
||||
guard let self = self else { return }
|
||||
self.composeToolbarView.visibilityButton.setImage(type.image, for: .normal)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
|
@ -589,8 +597,8 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate {
|
|||
// MARK: - ComposeToolbarViewDelegate
|
||||
extension ComposeViewController: ComposeToolbarViewDelegate {
|
||||
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, cameraButtonDidPressed sender: UIButton, mediaSelectionType: ComposeToolbarView.MediaSelectionType) {
|
||||
switch mediaSelectionType {
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, cameraButtonDidPressed sender: UIButton, mediaSelectionType type: ComposeToolbarView.MediaSelectionType) {
|
||||
switch type {
|
||||
case .photoLibrary:
|
||||
present(imagePicker, animated: true, completion: nil)
|
||||
case .camera:
|
||||
|
@ -626,7 +634,8 @@ extension ComposeViewController: ComposeToolbarViewDelegate {
|
|||
viewModel.isContentWarningComposing.value.toggle()
|
||||
}
|
||||
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton) {
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType) {
|
||||
viewModel.selectedStatusVisibility.value = type
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -69,6 +69,7 @@ extension ComposeViewModel.PublishState {
|
|||
}
|
||||
return text
|
||||
}()
|
||||
let visibility = viewModel.selectedStatusVisibility.value.visibility
|
||||
|
||||
let updateMediaQuerySubscriptions: [AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Attachment>, Error>] = {
|
||||
var subscriptions: [AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Attachment>, Error>] = []
|
||||
|
@ -102,7 +103,8 @@ extension ComposeViewModel.PublishState {
|
|||
pollOptions: pollOptions,
|
||||
pollExpiresIn: pollExpiresIn,
|
||||
sensitive: sensitive,
|
||||
spoilerText: spoilerText
|
||||
spoilerText: spoilerText,
|
||||
visibility: visibility
|
||||
)
|
||||
return viewModel.context.apiService.publishStatus(
|
||||
domain: domain,
|
||||
|
|
|
@ -10,6 +10,7 @@ import Combine
|
|||
import CoreData
|
||||
import CoreDataStack
|
||||
import GameplayKit
|
||||
import MastodonSDK
|
||||
|
||||
final class ComposeViewModel {
|
||||
|
||||
|
@ -22,6 +23,7 @@ final class ComposeViewModel {
|
|||
let isPollComposing = CurrentValueSubject<Bool, Never>(false)
|
||||
let isCustomEmojiComposing = CurrentValueSubject<Bool, Never>(false)
|
||||
let isContentWarningComposing = CurrentValueSubject<Bool, Never>(false)
|
||||
let selectedStatusVisibility = CurrentValueSubject<ComposeToolbarView.VisibilitySelectionType, Never>(.public)
|
||||
let activeAuthentication: CurrentValueSubject<MastodonAuthentication?, Never>
|
||||
let activeAuthenticationBox: CurrentValueSubject<AuthenticationService.MastodonAuthenticationBox?, Never>
|
||||
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
|
||||
import os.log
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
|
||||
protocol ComposeToolbarViewDelegate: class {
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, cameraButtonDidPressed sender: UIButton, mediaSelectionType: ComposeToolbarView.MediaSelectionType)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, cameraButtonDidPressed sender: UIButton, mediaSelectionType type: ComposeToolbarView.MediaSelectionType)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, pollButtonDidPressed sender: UIButton)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, emojiButtonDidPressed sender: UIButton)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton, visibilitySelectionType type: ComposeToolbarView.VisibilitySelectionType)
|
||||
}
|
||||
|
||||
final class ComposeToolbarView: UIView {
|
||||
|
@ -106,7 +107,8 @@ extension ComposeToolbarView {
|
|||
pollButton.addTarget(self, action: #selector(ComposeToolbarView.pollButtonDidPressed(_:)), for: .touchUpInside)
|
||||
emojiButton.addTarget(self, action: #selector(ComposeToolbarView.emojiButtonDidPressed(_:)), for: .touchUpInside)
|
||||
contentWarningButton.addTarget(self, action: #selector(ComposeToolbarView.contentWarningButtonDidPressed(_:)), for: .touchUpInside)
|
||||
visibilityButton.addTarget(self, action: #selector(ComposeToolbarView.visibilityButtonDidPressed(_:)), for: .touchUpInside)
|
||||
visibilityButton.menu = createVisibilityContextMenu()
|
||||
visibilityButton.showsMenuAsPrimaryAction = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,6 +118,40 @@ extension ComposeToolbarView {
|
|||
case photoLibrary
|
||||
case browse
|
||||
}
|
||||
|
||||
enum VisibilitySelectionType: String, CaseIterable {
|
||||
case `public`
|
||||
case unlisted
|
||||
case `private`
|
||||
case direct
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .public: return L10n.Scene.Compose.Visibility.public
|
||||
case .unlisted: return L10n.Scene.Compose.Visibility.unlisted
|
||||
case .private: return L10n.Scene.Compose.Visibility.private
|
||||
case .direct: return L10n.Scene.Compose.Visibility.direct
|
||||
}
|
||||
}
|
||||
|
||||
var image: UIImage {
|
||||
switch self {
|
||||
case .public: return UIImage(systemName: "person.3", withConfiguration: UIImage.SymbolConfiguration(pointSize: 15, weight: .medium))!
|
||||
case .unlisted: return UIImage(systemName: "eye.slash", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium))!
|
||||
case .private: return UIImage(systemName: "person.crop.circle.badge.plus", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium))!
|
||||
case .direct: return UIImage(systemName: "at", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium))!
|
||||
}
|
||||
}
|
||||
|
||||
var visibility: Mastodon.Entity.Status.Visibility {
|
||||
switch self {
|
||||
case .public: return .public
|
||||
case .unlisted: return .unlisted
|
||||
case .private: return .private
|
||||
case .direct: return .direct
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeToolbarView {
|
||||
|
@ -154,9 +190,19 @@ extension ComposeToolbarView {
|
|||
return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
|
||||
}
|
||||
|
||||
private func createVisibilityContextMenu() -> UIMenu {
|
||||
let children: [UIMenuElement] = VisibilitySelectionType.allCases.map { type in
|
||||
UIAction(title: type.title, image: type.image, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: visibilitySelectionType: %s", ((#file as NSString).lastPathComponent), #line, #function, type.rawValue)
|
||||
self.delegate?.composeToolbarView(self, visibilityButtonDidPressed: self.visibilityButton, visibilitySelectionType: type)
|
||||
}
|
||||
}
|
||||
return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension ComposeToolbarView {
|
||||
|
||||
@objc private func pollButtonDidPressed(_ sender: UIButton) {
|
||||
|
@ -174,11 +220,6 @@ extension ComposeToolbarView {
|
|||
delegate?.composeToolbarView(self, contentWarningButtonDidPressed: sender)
|
||||
}
|
||||
|
||||
@objc private func visibilityButtonDidPressed(_ sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
delegate?.composeToolbarView(self, visibilityButtonDidPressed: sender)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if canImport(SwiftUI) && DEBUG
|
||||
|
|
|
@ -100,6 +100,7 @@ extension Mastodon.API.Statuses {
|
|||
public let pollExpiresIn: Int?
|
||||
public let sensitive: Bool?
|
||||
public let spoilerText: String?
|
||||
public let visibility: Mastodon.Entity.Status.Visibility?
|
||||
|
||||
public init(
|
||||
status: String?,
|
||||
|
@ -107,7 +108,8 @@ extension Mastodon.API.Statuses {
|
|||
pollOptions: [String]?,
|
||||
pollExpiresIn: Int?,
|
||||
sensitive: Bool?,
|
||||
spoilerText: String?
|
||||
spoilerText: String?,
|
||||
visibility: Mastodon.Entity.Status.Visibility?
|
||||
) {
|
||||
self.status = status
|
||||
self.mediaIDs = mediaIDs
|
||||
|
@ -115,6 +117,7 @@ extension Mastodon.API.Statuses {
|
|||
self.pollExpiresIn = pollExpiresIn
|
||||
self.sensitive = sensitive
|
||||
self.spoilerText = spoilerText
|
||||
self.visibility = visibility
|
||||
|
||||
}
|
||||
|
||||
|
@ -135,6 +138,7 @@ extension Mastodon.API.Statuses {
|
|||
pollExpiresIn.flatMap { data.append(Data.multipart(key: "poll[expires_in]", value: $0)) }
|
||||
sensitive.flatMap { data.append(Data.multipart(key: "sensitive", value: $0)) }
|
||||
spoilerText.flatMap { data.append(Data.multipart(key: "spoiler_text", value: $0)) }
|
||||
visibility.flatMap { data.append(Data.multipart(key: "visibility", value: $0.rawValue)) }
|
||||
|
||||
data.append(Data.multipartEnd())
|
||||
return data
|
||||
|
|
Loading…
Reference in New Issue