mirror of
https://github.com/metabolist/metatext.git
synced 2024-12-22 05:26:30 +00:00
Post polls
This commit is contained in:
parent
377bf6aecc
commit
5c5677507b
9 changed files with 343 additions and 11 deletions
|
@ -44,11 +44,13 @@
|
|||
"camera-access.open-system-settings" = "Open system settings";
|
||||
"cancel" = "Cancel";
|
||||
"compose.attachment.uploading" = "Uploading";
|
||||
"compose.prompt" = "What's on your mind?";
|
||||
"compose.browse" = "Browse";
|
||||
"compose.mark-media-sensitive" = "Mark media as sensitive";
|
||||
"compose.photo-library" = "Photo Library";
|
||||
"compose.poll.add-choice" = "Add a choice";
|
||||
"compose.poll.allow-multiple-choices" = "Allow multiple choices";
|
||||
"compose.prompt" = "What's on your mind?";
|
||||
"compose.take-photo-or-video" = "Take Photo or Video";
|
||||
"compose.browse" = "Browse";
|
||||
"error" = "Error";
|
||||
"favorites" = "Favorites";
|
||||
"registration.review-terms-of-use-and-privacy-policy-%@" = "Please review %@'s Terms of Use and Privacy Policy to continue";
|
||||
|
|
|
@ -23,6 +23,9 @@ public extension StatusEndpoint {
|
|||
public let mediaIds: [Attachment.Id]
|
||||
public let visibility: Status.Visibility
|
||||
public let sensitive: Bool
|
||||
public let pollOptions: [String]
|
||||
public let pollExpiresIn: Int
|
||||
public let pollMultipleChoice: Bool
|
||||
|
||||
public init(
|
||||
inReplyToId: Status.Id?,
|
||||
|
@ -30,13 +33,19 @@ public extension StatusEndpoint {
|
|||
spoilerText: String,
|
||||
mediaIds: [Attachment.Id],
|
||||
visibility: Status.Visibility,
|
||||
sensitive: Bool) {
|
||||
sensitive: Bool,
|
||||
pollOptions: [String],
|
||||
pollExpiresIn: Int,
|
||||
pollMultipleChoice: Bool) {
|
||||
self.inReplyToId = inReplyToId
|
||||
self.text = text
|
||||
self.spoilerText = spoilerText
|
||||
self.mediaIds = mediaIds
|
||||
self.visibility = visibility
|
||||
self.sensitive = sensitive
|
||||
self.pollOptions = pollOptions
|
||||
self.pollExpiresIn = pollExpiresIn
|
||||
self.pollMultipleChoice = pollMultipleChoice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +73,16 @@ extension StatusEndpoint.Components {
|
|||
params["sensitive"] = true
|
||||
}
|
||||
|
||||
if !pollOptions.isEmpty {
|
||||
var poll = [String: Any]()
|
||||
|
||||
poll["options"] = pollOptions
|
||||
poll["expires_in"] = pollExpiresIn
|
||||
poll["multiple"] = pollMultipleChoice
|
||||
|
||||
params["poll"] = poll
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,10 @@
|
|||
D05936F525AA66A600754FDF /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05936F325AA66A600754FDF /* UIView+Extensions.swift */; };
|
||||
D05936FF25AA94EA00754FDF /* MarkAttachmentsSensitiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05936FE25AA94EA00754FDF /* MarkAttachmentsSensitiveView.swift */; };
|
||||
D059370025AA94EA00754FDF /* MarkAttachmentsSensitiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05936FE25AA94EA00754FDF /* MarkAttachmentsSensitiveView.swift */; };
|
||||
D059373325AAEA7000754FDF /* CompositionPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D059373225AAEA7000754FDF /* CompositionPollView.swift */; };
|
||||
D059373425AAEA7000754FDF /* CompositionPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D059373225AAEA7000754FDF /* CompositionPollView.swift */; };
|
||||
D059373E25AB8D5200754FDF /* CompositionPollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D059373D25AB8D5200754FDF /* CompositionPollOptionView.swift */; };
|
||||
D059373F25AB8D5200754FDF /* CompositionPollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D059373D25AB8D5200754FDF /* CompositionPollOptionView.swift */; };
|
||||
D0625E59250F092900502611 /* StatusListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E58250F092900502611 /* StatusListCell.swift */; };
|
||||
D0625E5D250F0B5C00502611 /* StatusContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */; };
|
||||
D06BC5E625202AD90079541D /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06BC5E525202AD90079541D /* ProfileViewController.swift */; };
|
||||
|
@ -196,6 +200,8 @@
|
|||
D05936E825AA3F3D00754FDF /* EditAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAttachmentView.swift; sourceTree = "<group>"; };
|
||||
D05936F325AA66A600754FDF /* UIView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = "<group>"; };
|
||||
D05936FE25AA94EA00754FDF /* MarkAttachmentsSensitiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkAttachmentsSensitiveView.swift; sourceTree = "<group>"; };
|
||||
D059373225AAEA7000754FDF /* CompositionPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionPollView.swift; sourceTree = "<group>"; };
|
||||
D059373D25AB8D5200754FDF /* CompositionPollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionPollOptionView.swift; sourceTree = "<group>"; };
|
||||
D0625E58250F092900502611 /* StatusListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusListCell.swift; sourceTree = "<group>"; };
|
||||
D0625E5C250F0B5C00502611 /* StatusContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentConfiguration.swift; sourceTree = "<group>"; };
|
||||
D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -450,6 +456,8 @@
|
|||
D0CE9F86258B076900E3A6B6 /* AttachmentUploadView.swift */,
|
||||
D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */,
|
||||
D0E9F9A9258450B300EF503D /* CompositionInputAccessoryView.swift */,
|
||||
D059373D25AB8D5200754FDF /* CompositionPollOptionView.swift */,
|
||||
D059373225AAEA7000754FDF /* CompositionPollView.swift */,
|
||||
D08E52ED257D757100FA2C5F /* CompositionView.swift */,
|
||||
D007023D25562A2800F38136 /* ConversationAvatarsView.swift */,
|
||||
D00702352555F4C500F38136 /* ConversationContentConfiguration.swift */,
|
||||
|
@ -809,6 +817,7 @@
|
|||
D08B8D672540DEB200B1EBEF /* ZoomAnimatableView.swift in Sources */,
|
||||
D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */,
|
||||
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
|
||||
D059373325AAEA7000754FDF /* CompositionPollView.swift in Sources */,
|
||||
D08B8D8D2544E6EC00B1EBEF /* PollResultView.swift in Sources */,
|
||||
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */,
|
||||
D05936CF25A8D79800754FDF /* EditAttachmentViewController.swift in Sources */,
|
||||
|
@ -824,6 +833,7 @@
|
|||
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */,
|
||||
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
|
||||
D01EF22425182B1F00650C6B /* AccountHeaderView.swift in Sources */,
|
||||
D059373E25AB8D5200754FDF /* CompositionPollOptionView.swift in Sources */,
|
||||
D036AA17254CA824009094DF /* StatusBodyView.swift in Sources */,
|
||||
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */,
|
||||
D05936F425AA66A600754FDF /* UIView+Extensions.swift in Sources */,
|
||||
|
@ -870,6 +880,7 @@
|
|||
files = (
|
||||
D036EBC7259FE2B700EC1CFC /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
||||
D08E52A6257C61C000FA2C5F /* ShareExtensionNavigationViewController.swift in Sources */,
|
||||
D059373425AAEA7000754FDF /* CompositionPollView.swift in Sources */,
|
||||
D08E52D2257C811200FA2C5F /* ShareExtensionError+Extensions.swift in Sources */,
|
||||
D0E9F9AB258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
|
||||
D05936D025A8D79800754FDF /* EditAttachmentViewController.swift in Sources */,
|
||||
|
@ -881,6 +892,7 @@
|
|||
D015B14425A812F6006D88A8 /* PlayerCache.swift in Sources */,
|
||||
D05936F525AA66A600754FDF /* UIView+Extensions.swift in Sources */,
|
||||
D015B13F25A812EC006D88A8 /* PlayerView.swift in Sources */,
|
||||
D059373F25AB8D5200754FDF /* CompositionPollOptionView.swift in Sources */,
|
||||
D015B13A25A812E6006D88A8 /* AttachmentView.swift in Sources */,
|
||||
D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */,
|
||||
D036EBC2259FE2AD00EC1CFC /* UIVIewController+Extensions.swift in Sources */,
|
||||
|
|
|
@ -72,7 +72,6 @@ final class EditAttachmentViewController: UIViewController {
|
|||
stackView.addArrangedSubview(remainingCharactersLabel)
|
||||
remainingCharactersLabel.adjustsFontForContentSizeCategory = true
|
||||
remainingCharactersLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
remainingCharactersLabel.text = "1500"
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
|
||||
|
|
|
@ -12,6 +12,11 @@ public final class CompositionViewModel: AttachmentsRenderingViewModel, Observab
|
|||
@Published public var contentWarning = ""
|
||||
@Published public var displayContentWarning = false
|
||||
@Published public var sensitive = false
|
||||
@Published public var displayPoll = false
|
||||
@Published public var pollMultipleChoice = false
|
||||
@Published public var pollHideTotals = false
|
||||
@Published public var pollExpiresIn = PollExpiry.oneDay
|
||||
@Published public private(set) var pollOptions = [PollOption(text: ""), PollOption(text: "")]
|
||||
@Published public private(set) var attachmentViewModels = [AttachmentViewModel]()
|
||||
@Published public private(set) var attachmentUpload: AttachmentUpload?
|
||||
@Published public private(set) var isPostable = false
|
||||
|
@ -34,8 +39,8 @@ public final class CompositionViewModel: AttachmentsRenderingViewModel, Observab
|
|||
}
|
||||
.assign(to: &$isPostable)
|
||||
$attachmentViewModels
|
||||
.combineLatest($attachmentUpload)
|
||||
.map { $0.count < Self.maxAttachmentCount && $1 == nil }
|
||||
.combineLatest($attachmentUpload, $displayPoll)
|
||||
.map { $0.count < Self.maxAttachmentCount && $1 == nil && !$2 }
|
||||
.assign(to: &$canAddAttachment)
|
||||
$attachmentViewModels.map(\.isEmpty).assign(to: &$canAddNonImageAttachment)
|
||||
$text.map {
|
||||
|
@ -60,12 +65,35 @@ public final class CompositionViewModel: AttachmentsRenderingViewModel, Observab
|
|||
|
||||
public extension CompositionViewModel {
|
||||
static let maxCharacters = 500
|
||||
static let minPollOptionCount = 2
|
||||
static let maxPollOptionCount = 4
|
||||
|
||||
enum Event {
|
||||
case editAttachment(AttachmentViewModel, CompositionViewModel)
|
||||
case updateAttachment(AnyPublisher<Never, Error>)
|
||||
}
|
||||
|
||||
enum PollExpiry: Int, CaseIterable {
|
||||
case fiveMinutes = 300
|
||||
case thirtyMinutes = 1800
|
||||
case oneHour = 3600
|
||||
case sixHours = 21600
|
||||
case oneDay = 86400
|
||||
case threeDays = 259200
|
||||
case sevenDays = 604800
|
||||
}
|
||||
|
||||
class PollOption: ObservableObject {
|
||||
public let id = Id()
|
||||
@Published public var text: String
|
||||
@Published public private(set) var remainingCharacters = CompositionViewModel.maxCharacters
|
||||
|
||||
public init(text: String) {
|
||||
self.text = text
|
||||
$text.map { Self.maxCharacters - $0.count }.assign(to: &$remainingCharacters)
|
||||
}
|
||||
}
|
||||
|
||||
typealias Id = UUID
|
||||
|
||||
func components(inReplyToId: Status.Id?, visibility: Status.Visibility) -> StatusComponents {
|
||||
|
@ -75,7 +103,18 @@ public extension CompositionViewModel {
|
|||
spoilerText: displayContentWarning ? contentWarning : "",
|
||||
mediaIds: attachmentViewModels.map(\.attachment.id),
|
||||
visibility: visibility,
|
||||
sensitive: sensitive)
|
||||
sensitive: sensitive,
|
||||
pollOptions: displayPoll ? pollOptions.map(\.text) : [],
|
||||
pollExpiresIn: pollExpiresIn.rawValue,
|
||||
pollMultipleChoice: pollMultipleChoice)
|
||||
}
|
||||
|
||||
func addPollOption() {
|
||||
pollOptions.append(PollOption(text: ""))
|
||||
}
|
||||
|
||||
func remove(pollOption: PollOption) {
|
||||
pollOptions.removeAll { $0 === pollOption }
|
||||
}
|
||||
|
||||
func cancelUpload() {
|
||||
|
@ -100,6 +139,12 @@ public extension CompositionViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
public extension CompositionViewModel.PollOption {
|
||||
static let maxCharacters = 25
|
||||
|
||||
typealias Id = UUID
|
||||
}
|
||||
|
||||
extension CompositionViewModel {
|
||||
func attach(itemProvider: NSItemProvider, parentViewModel: NewStatusViewModel) {
|
||||
attachmentUploadCancellable = MediaProcessingService.dataAndMimeType(itemProvider: itemProvider)
|
||||
|
|
|
@ -83,7 +83,7 @@ private extension CompositionInputAccessoryView {
|
|||
attachmentButton.showsMenuAsPrimaryAction = true
|
||||
attachmentButton.menu = UIMenu(children: attachmentActions)
|
||||
|
||||
let pollButton = UIButton()
|
||||
let pollButton = UIButton(primaryAction: UIAction { [weak self] _ in self?.viewModel.displayPoll.toggle() })
|
||||
|
||||
stackView.addArrangedSubview(pollButton)
|
||||
pollButton.setImage(
|
||||
|
@ -134,6 +134,11 @@ private extension CompositionInputAccessoryView {
|
|||
.sink { attachmentButton.isEnabled = $0 }
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$attachmentViewModels
|
||||
.combineLatest(viewModel.$attachmentUpload)
|
||||
.sink { pollButton.isEnabled = $0.isEmpty && $1 == nil }
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$remainingCharacters.sink {
|
||||
charactersLabel.text = String($0)
|
||||
charactersLabel.textColor = $0 < 0 ? .systemRed : .label
|
||||
|
|
87
Views/CompositionPollOptionView.swift
Normal file
87
Views/CompositionPollOptionView.swift
Normal file
|
@ -0,0 +1,87 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class CompositionPollOptionView: UIView {
|
||||
let option: CompositionViewModel.PollOption
|
||||
let removeButton = UIButton(type: .close)
|
||||
private let viewModel: CompositionViewModel
|
||||
private let compositionInputAccessoryView: CompositionInputAccessoryView
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(viewModel: CompositionViewModel,
|
||||
option: CompositionViewModel.PollOption,
|
||||
inputAccessoryView: CompositionInputAccessoryView) {
|
||||
self.viewModel = viewModel
|
||||
self.option = option
|
||||
self.compositionInputAccessoryView = inputAccessoryView
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
initialSetup()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private extension CompositionPollOptionView {
|
||||
// swiftlint:disable:next function_body_length
|
||||
func initialSetup() {
|
||||
let stackView = UIStackView()
|
||||
let textField = UITextField()
|
||||
let remainingCharactersLabel = UILabel()
|
||||
|
||||
addSubview(stackView)
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.spacing = .defaultSpacing
|
||||
|
||||
stackView.addArrangedSubview(textField)
|
||||
textField.borderStyle = .roundedRect
|
||||
textField.adjustsFontForContentSizeCategory = true
|
||||
textField.font = .preferredFont(forTextStyle: .body)
|
||||
textField.inputAccessoryView = compositionInputAccessoryView
|
||||
textField.addAction(
|
||||
UIAction { [weak self] _ in
|
||||
self?.option.text = textField.text ?? "" },
|
||||
for: .editingChanged)
|
||||
|
||||
stackView.addArrangedSubview(remainingCharactersLabel)
|
||||
remainingCharactersLabel.adjustsFontForContentSizeCategory = true
|
||||
remainingCharactersLabel.font = .preferredFont(forTextStyle: .callout)
|
||||
remainingCharactersLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||
|
||||
stackView.addArrangedSubview(removeButton)
|
||||
removeButton.showsMenuAsPrimaryAction = true
|
||||
removeButton.menu = UIMenu(
|
||||
children: [
|
||||
UIAction(
|
||||
title: NSLocalizedString("remove", comment: ""),
|
||||
image: UIImage(systemName: "trash"),
|
||||
attributes: .destructive) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.viewModel.remove(pollOption: self.option)
|
||||
}])
|
||||
removeButton.setContentHuggingPriority(.required, for: .horizontal)
|
||||
removeButton.setContentHuggingPriority(.required, for: .vertical)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
stackView.topAnchor.constraint(equalTo: topAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
])
|
||||
|
||||
option.$remainingCharacters
|
||||
.sink {
|
||||
remainingCharactersLabel.text = String($0)
|
||||
remainingCharactersLabel.textColor = $0 < 0 ? .systemRed : .label
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
146
Views/CompositionPollView.swift
Normal file
146
Views/CompositionPollView.swift
Normal file
|
@ -0,0 +1,146 @@
|
|||
// Copyright © 2021 Metabolist. All rights reserved.
|
||||
|
||||
import Combine
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
final class CompositionPollView: UIView {
|
||||
private let viewModel: CompositionViewModel
|
||||
private let compositionInputAccessoryView: CompositionInputAccessoryView
|
||||
private let stackView = UIStackView()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(viewModel: CompositionViewModel, inputAccessoryView: CompositionInputAccessoryView) {
|
||||
self.viewModel = viewModel
|
||||
self.compositionInputAccessoryView = inputAccessoryView
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
initialSetup()
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private extension CompositionPollView {
|
||||
static let dateComponentsFormatter: DateComponentsFormatter = {
|
||||
let formatter = DateComponentsFormatter()
|
||||
|
||||
formatter.unitsStyle = .full
|
||||
|
||||
return formatter
|
||||
}()
|
||||
|
||||
static func format(expiry: CompositionViewModel.PollExpiry) -> String? {
|
||||
dateComponentsFormatter.string(from: TimeInterval(expiry.rawValue))
|
||||
}
|
||||
|
||||
var pollOptionViews: [CompositionPollOptionView] {
|
||||
stackView.arrangedSubviews.compactMap({ $0 as? CompositionPollOptionView })
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
func initialSetup() {
|
||||
addSubview(stackView)
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = .defaultSpacing
|
||||
|
||||
let buttonsStackView = UIStackView()
|
||||
|
||||
stackView.addArrangedSubview(buttonsStackView)
|
||||
buttonsStackView.distribution = .fillEqually
|
||||
|
||||
let addChoiceButton = UIButton(primaryAction: UIAction { [weak self] _ in self?.viewModel.addPollOption() })
|
||||
|
||||
buttonsStackView.addArrangedSubview(addChoiceButton)
|
||||
addChoiceButton.setImage(
|
||||
UIImage(systemName: "plus",
|
||||
withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
|
||||
for: .normal)
|
||||
addChoiceButton.setTitle(NSLocalizedString("compose.poll.add-choice", comment: ""), for: .normal)
|
||||
|
||||
let expiresInButton = UIButton(type: .system)
|
||||
|
||||
buttonsStackView.addArrangedSubview(expiresInButton)
|
||||
expiresInButton.setImage(
|
||||
UIImage(systemName: "clock",
|
||||
withConfiguration: UIImage.SymbolConfiguration(scale: .medium)),
|
||||
for: .normal)
|
||||
expiresInButton.showsMenuAsPrimaryAction = true
|
||||
expiresInButton.menu = UIMenu(children: CompositionViewModel.PollExpiry.allCases.map { expiry in
|
||||
UIAction(title: Self.format(expiry: expiry) ?? "") { [weak self] _ in
|
||||
self?.viewModel.pollExpiresIn = expiry
|
||||
}
|
||||
})
|
||||
|
||||
let switchStackView = UIStackView()
|
||||
|
||||
switchStackView.spacing = .defaultSpacing
|
||||
|
||||
stackView.addArrangedSubview(switchStackView)
|
||||
|
||||
let allowMultipleLabel = UILabel()
|
||||
|
||||
switchStackView.addArrangedSubview(allowMultipleLabel)
|
||||
allowMultipleLabel.adjustsFontForContentSizeCategory = true
|
||||
allowMultipleLabel.font = .preferredFont(forTextStyle: .callout)
|
||||
allowMultipleLabel.textColor = .secondaryLabel
|
||||
allowMultipleLabel.text = NSLocalizedString("compose.poll.allow-multiple-choices", comment: "")
|
||||
allowMultipleLabel.textAlignment = .right
|
||||
|
||||
let allowMultipleSwitch = UISwitch()
|
||||
|
||||
switchStackView.addArrangedSubview(allowMultipleSwitch)
|
||||
allowMultipleSwitch.addAction(
|
||||
UIAction { [weak self] _ in
|
||||
self?.viewModel.sensitive = allowMultipleSwitch.isOn
|
||||
},
|
||||
for: .valueChanged)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
stackView.topAnchor.constraint(equalTo: topAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
buttonsStackView.heightAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension)
|
||||
])
|
||||
|
||||
viewModel.$pollOptions.sink { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
addChoiceButton.isEnabled = $0.count < CompositionViewModel.maxPollOptionCount
|
||||
|
||||
for (index, option) in $0.enumerated() {
|
||||
if !self.pollOptionViews.contains(where: { $0.option === option }) {
|
||||
let optionView = CompositionPollOptionView(
|
||||
viewModel: self.viewModel,
|
||||
option: option,
|
||||
inputAccessoryView: self.compositionInputAccessoryView)
|
||||
|
||||
self.stackView.insertArrangedSubview(optionView, at: index)
|
||||
}
|
||||
}
|
||||
|
||||
for (index, optionView) in self.pollOptionViews.enumerated() {
|
||||
optionView.removeButton.isHidden = index < CompositionViewModel.minPollOptionCount
|
||||
|
||||
if !$0.contains(where: { $0 === optionView.option }) {
|
||||
optionView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$pollExpiresIn
|
||||
.sink { expiresInButton.setTitle(Self.format(expiry: $0), for: .normal) }
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$pollMultipleChoice
|
||||
.sink { allowMultipleSwitch.isEnabled = !$0 }
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
|
@ -13,8 +13,10 @@ final class CompositionView: UIView {
|
|||
let removeButton = UIButton(type: .close)
|
||||
let inReplyToView = UIView()
|
||||
let hasReplyFollowingView = UIView()
|
||||
let compositionInputAccessoryView: CompositionInputAccessoryView
|
||||
let attachmentsView = AttachmentsView()
|
||||
let attachmentUploadView: AttachmentUploadView
|
||||
let pollView: CompositionPollView
|
||||
let markAttachmentsSensitiveView: MarkAttachmentsSensitiveView
|
||||
|
||||
private let viewModel: CompositionViewModel
|
||||
|
@ -25,8 +27,12 @@ final class CompositionView: UIView {
|
|||
self.viewModel = viewModel
|
||||
self.parentViewModel = parentViewModel
|
||||
|
||||
compositionInputAccessoryView = CompositionInputAccessoryView(
|
||||
viewModel: viewModel,
|
||||
parentViewModel: parentViewModel)
|
||||
attachmentUploadView = AttachmentUploadView(viewModel: viewModel)
|
||||
markAttachmentsSensitiveView = MarkAttachmentsSensitiveView(viewModel: viewModel)
|
||||
pollView = CompositionPollView(viewModel: viewModel, inputAccessoryView: compositionInputAccessoryView)
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
|
@ -63,7 +69,6 @@ private extension CompositionView {
|
|||
avatarImageView.setContentHuggingPriority(.required, for: .horizontal)
|
||||
|
||||
let stackView = UIStackView()
|
||||
let inputAccessoryView = CompositionInputAccessoryView(viewModel: viewModel, parentViewModel: parentViewModel)
|
||||
|
||||
addSubview(stackView)
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -75,7 +80,7 @@ private extension CompositionView {
|
|||
spoilerTextField.adjustsFontForContentSizeCategory = true
|
||||
spoilerTextField.font = .preferredFont(forTextStyle: .body)
|
||||
spoilerTextField.placeholder = NSLocalizedString("status.spoiler-text-placeholder", comment: "")
|
||||
spoilerTextField.inputAccessoryView = inputAccessoryView
|
||||
spoilerTextField.inputAccessoryView = compositionInputAccessoryView
|
||||
spoilerTextField.addAction(
|
||||
UIAction { [weak self] _ in
|
||||
guard let self = self, let text = self.spoilerTextField.text else { return }
|
||||
|
@ -92,7 +97,7 @@ private extension CompositionView {
|
|||
textView.font = textViewFont
|
||||
textView.textContainerInset = .zero
|
||||
textView.textContainer.lineFragmentPadding = 0
|
||||
textView.inputAccessoryView = inputAccessoryView
|
||||
textView.inputAccessoryView = compositionInputAccessoryView
|
||||
textView.inputAccessoryView?.sizeToFit()
|
||||
textView.delegate = self
|
||||
|
||||
|
@ -109,6 +114,8 @@ private extension CompositionView {
|
|||
attachmentUploadView.isHidden = true
|
||||
stackView.addArrangedSubview(markAttachmentsSensitiveView)
|
||||
markAttachmentsSensitiveView.isHidden = true
|
||||
stackView.addArrangedSubview(pollView)
|
||||
pollView.isHidden = true
|
||||
|
||||
addSubview(removeButton)
|
||||
removeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -172,6 +179,16 @@ private extension CompositionView {
|
|||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
viewModel.$displayPoll
|
||||
.sink { [weak self] in
|
||||
if !$0 {
|
||||
self?.textView.becomeFirstResponder()
|
||||
}
|
||||
|
||||
self?.pollView.isHidden = !$0
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
let guide = UIDevice.current.userInterfaceIdiom == .pad ? readableContentGuide : layoutMarginsGuide
|
||||
let constraints = [
|
||||
avatarImageView.heightAnchor.constraint(equalToConstant: .avatarDimension),
|
||||
|
|
Loading…
Reference in a new issue