feat: add expires duration selector for poll

This commit is contained in:
CMK 2021-03-24 14:49:27 +08:00
parent 3eb2b916a7
commit d05f97951b
12 changed files with 234 additions and 46 deletions

View File

@ -207,6 +207,15 @@
"attachment_broken": "This %s is broken and can't be\nuploaded to Mastodon.",
"description_photo": "Describe photo for low vision people...",
"description_video": "Describe whats happening for low vision people..."
},
"poll": {
"duration_time": "Duration: %s",
"thirty_minutes": "30 minutes",
"one_hour": "1 Hour",
"six_hours": "6 Hours",
"one_day": "1 Day",
"three_days": "3 Days",
"seven_days": "7 Days"
}
}
}

View File

@ -123,6 +123,7 @@
DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; };
DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; };
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; };
DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */; };
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; };
DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; };
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; };
@ -189,7 +190,7 @@
DB8190C62601FF0400020C08 /* AttachmentContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */; };
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */; };
DB87D44B2609C11900D12C0D /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D44A2609C11900D12C0D /* PollOptionView.swift */; };
DB87D4512609CF1E00D12C0D /* ComposeStatusNewPollOptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4502609CF1E00D12C0D /* ComposeStatusNewPollOptionCollectionViewCell.swift */; };
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */; };
DB87D4572609DD5300D12C0D /* DeleteBackwardResponseTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB87D4562609DD5300D12C0D /* DeleteBackwardResponseTextField.swift */; };
DB89B9F725C10FD0008580ED /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; };
DB89B9FE25C10FD0008580ED /* CoreDataStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89B9FD25C10FD0008580ED /* CoreDataStackTests.swift */; };
@ -418,6 +419,7 @@
DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = "<group>"; };
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = "<group>"; };
DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollExpiresOptionCollectionViewCell.swift; sourceTree = "<group>"; };
DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = "<group>"; };
DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -487,7 +489,7 @@
DB8190C52601FF0400020C08 /* AttachmentContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentContainerView.swift; sourceTree = "<group>"; };
DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollOptionCollectionViewCell.swift; sourceTree = "<group>"; };
DB87D44A2609C11900D12C0D /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = "<group>"; };
DB87D4502609CF1E00D12C0D /* ComposeStatusNewPollOptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusNewPollOptionCollectionViewCell.swift; sourceTree = "<group>"; };
DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusPollOptionAppendEntryCollectionViewCell.swift; sourceTree = "<group>"; };
DB87D4562609DD5300D12C0D /* DeleteBackwardResponseTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteBackwardResponseTextField.swift; sourceTree = "<group>"; };
DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataStack.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DB89B9F025C10FD0008580ED /* CoreDataStack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreDataStack.h; sourceTree = "<group>"; };
@ -1163,7 +1165,8 @@
DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift */,
DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentTableViewCell.swift */,
DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */,
DB87D4502609CF1E00D12C0D /* ComposeStatusNewPollOptionCollectionViewCell.swift */,
DB87D4502609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift */,
DB2FF50F260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift */,
);
path = CollectionViewCell;
sourceTree = "<group>";
@ -1826,6 +1829,7 @@
DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */,
2D61335825C188A000CAE157 /* APIService+Persist+Status.swift in Sources */,
DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */,
DB2FF510260B113300ADA9FE /* ComposeStatusPollExpiresOptionCollectionViewCell.swift in Sources */,
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */,
2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Status.swift in Sources */,
DB4481C625EE2ADA00BEFB67 /* PollSection.swift in Sources */,
@ -1953,7 +1957,7 @@
DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */,
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */,
DB71FD2C25F86A5100512AE1 /* AvatarStackContainerButton.swift in Sources */,
DB87D4512609CF1E00D12C0D /* ComposeStatusNewPollOptionCollectionViewCell.swift in Sources */,
DB87D4512609CF1E00D12C0D /* ComposeStatusPollOptionAppendEntryCollectionViewCell.swift in Sources */,
DB9A489026035963008B817C /* APIService+Media.swift in Sources */,
2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */,
DB49A62525FF334C00B98345 /* EmojiService+CustomEmojiViewModel+LoadState.swift in Sources */,

View File

@ -14,8 +14,9 @@ enum ComposeStatusItem {
case replyTo(statusObjectID: NSManagedObjectID)
case input(replyToStatusObjectID: NSManagedObjectID?, attribute: ComposeStatusAttribute)
case attachment(attachmentService: MastodonAttachmentService)
case poll(attribute: ComposePollAttribute)
case newPoll
case pollOption(attribute: ComposePollOptionAttribute)
case pollOptionAppendEntry
case pollExpiresOption(attribute: ComposePollExpiresOptionAttribute)
}
extension ComposeStatusItem: Equatable { }
@ -44,16 +45,16 @@ extension ComposeStatusItem {
}
}
protocol ComposeStatusItemDelegate: class {
func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollAttribute, pollOptionDidChange: String?)
protocol ComposePollAttributeDelegate: class {
func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollOptionAttribute, pollOptionDidChange: String?)
}
extension ComposeStatusItem {
final class ComposePollAttribute: Equatable, Hashable {
final class ComposePollOptionAttribute: Equatable, Hashable {
private let id = UUID()
var disposeBag = Set<AnyCancellable>()
weak var delegate: ComposeStatusItemDelegate?
weak var delegate: ComposePollAttributeDelegate?
let option = CurrentValueSubject<String, Never>("")
@ -70,7 +71,7 @@ extension ComposeStatusItem {
disposeBag.removeAll()
}
static func == (lhs: ComposePollAttribute, rhs: ComposePollAttribute) -> Bool {
static func == (lhs: ComposePollOptionAttribute, rhs: ComposePollOptionAttribute) -> Bool {
return lhs.id == rhs.id &&
lhs.option.value == rhs.option.value
}
@ -80,3 +81,52 @@ extension ComposeStatusItem {
}
}
}
extension ComposeStatusItem {
final class ComposePollExpiresOptionAttribute: Equatable, Hashable {
private let id = UUID()
let expiresOption = CurrentValueSubject<ExpiresOption, Never>(.thirtyMinutes)
static func == (lhs: ComposePollExpiresOptionAttribute, rhs: ComposePollExpiresOptionAttribute) -> Bool {
return lhs.id == rhs.id &&
lhs.expiresOption.value == rhs.expiresOption.value
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
enum ExpiresOption: Equatable, Hashable, CaseIterable {
case thirtyMinutes
case oneHour
case sixHours
case oneDay
case threeDays
case sevenDays
var title: String {
switch self {
case .thirtyMinutes: return L10n.Scene.Compose.Poll.thirtyMinutes
case .oneHour: return L10n.Scene.Compose.Poll.oneHour
case .sixHours: return L10n.Scene.Compose.Poll.sixHours
case .oneDay: return L10n.Scene.Compose.Poll.oneDay
case .threeDays: return L10n.Scene.Compose.Poll.threeDays
case .sevenDays: return L10n.Scene.Compose.Poll.sevenDays
}
}
var seconds: Int {
switch self {
case .thirtyMinutes: return 60 * 30
case .oneHour: return 60 * 60 * 1
case .sixHours: return 60 * 60 * 6
case .oneDay: return 60 * 60 * 24
case .threeDays: return 60 * 60 * 24 * 3
case .sevenDays: return 60 * 60 * 24 * 7
}
}
}
}
}

View File

@ -36,7 +36,8 @@ extension ComposeStatusSection {
textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate,
composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate,
composeStatusPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionCollectionViewCellDelegate,
composeStatusNewPollOptionCollectionViewCellDelegate: ComposeStatusNewPollOptionCollectionViewCellDelegate
composeStatusNewPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate,
composeStatusPollExpiresOptionCollectionViewCellDelegate: ComposeStatusPollExpiresOptionCollectionViewCellDelegate
) -> UICollectionViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem> {
UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item -> UICollectionViewCell? in
switch item {
@ -127,7 +128,7 @@ extension ComposeStatusSection {
}
.store(in: &cell.disposeBag)
return cell
case .poll(let attribute):
case .pollOption(let attribute):
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollOptionCollectionViewCell.self), for: indexPath) as! ComposeStatusPollOptionCollectionViewCell
cell.pollOptionView.optionTextField.text = attribute.option.value
cell.pollOption
@ -136,10 +137,21 @@ extension ComposeStatusSection {
.store(in: &cell.disposeBag)
cell.delegate = composeStatusPollOptionCollectionViewCellDelegate
return cell
case .newPoll:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusNewPollOptionCollectionViewCell.self), for: indexPath) as! ComposeStatusNewPollOptionCollectionViewCell
case .pollOptionAppendEntry:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollOptionAppendEntryCollectionViewCell.self), for: indexPath) as! ComposeStatusPollOptionAppendEntryCollectionViewCell
cell.delegate = composeStatusNewPollOptionCollectionViewCellDelegate
return cell
case .pollExpiresOption(let attribute):
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollExpiresOptionCollectionViewCell.self), for: indexPath) as! ComposeStatusPollExpiresOptionCollectionViewCell
cell.durationButton.setTitle(L10n.Scene.Compose.Poll.durationTime(attribute.expiresOption.value.title), for: .normal)
attribute.expiresOption
.receive(on: DispatchQueue.main)
.sink { expiresOption in
cell.durationButton.setTitle(L10n.Scene.Compose.Poll.durationTime(expiresOption.title), for: .normal)
}
.store(in: &cell.disposeBag)
cell.delegate = composeStatusPollExpiresOptionCollectionViewCellDelegate
return cell
}
}
}

View File

@ -170,6 +170,24 @@ internal enum L10n {
/// Photo Library
internal static let photoLibrary = L10n.tr("Localizable", "Scene.Compose.MediaSelection.PhotoLibrary")
}
internal enum Poll {
/// Duration: %@
internal static func durationTime(_ p1: Any) -> String {
return L10n.tr("Localizable", "Scene.Compose.Poll.DurationTime", String(describing: p1))
}
/// 1 Day
internal static let oneDay = L10n.tr("Localizable", "Scene.Compose.Poll.OneDay")
/// 1 Hour
internal static let oneHour = L10n.tr("Localizable", "Scene.Compose.Poll.OneHour")
/// 7 Days
internal static let sevenDays = L10n.tr("Localizable", "Scene.Compose.Poll.SevenDays")
/// 6 Hours
internal static let sixHours = L10n.tr("Localizable", "Scene.Compose.Poll.SixHours")
/// 30 minutes
internal static let thirtyMinutes = L10n.tr("Localizable", "Scene.Compose.Poll.ThirtyMinutes")
/// 3 Days
internal static let threeDays = L10n.tr("Localizable", "Scene.Compose.Poll.ThreeDays")
}
internal enum Title {
/// New Post
internal static let newPost = L10n.tr("Localizable", "Scene.Compose.Title.NewPost")

View File

@ -50,6 +50,13 @@ uploaded to Mastodon.";
"Scene.Compose.MediaSelection.Browse" = "Browse";
"Scene.Compose.MediaSelection.Camera" = "Take Photo";
"Scene.Compose.MediaSelection.PhotoLibrary" = "Photo Library";
"Scene.Compose.Poll.DurationTime" = "Duration: %@";
"Scene.Compose.Poll.OneDay" = "1 Day";
"Scene.Compose.Poll.OneHour" = "1 Hour";
"Scene.Compose.Poll.SevenDays" = "7 Days";
"Scene.Compose.Poll.SixHours" = "6 Hours";
"Scene.Compose.Poll.ThirtyMinutes" = "30 minutes";
"Scene.Compose.Poll.ThreeDays" = "3 Days";
"Scene.Compose.Title.NewPost" = "New Post";
"Scene.Compose.Title.NewReply" = "New Reply";
"Scene.ConfirmEmail.Button.DontReceiveEmail" = "I never got an email";

View File

@ -0,0 +1,74 @@
//
// ComposeStatusPollExpiresOptionCollectionViewCell.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-3-24.
//
import os.log
import UIKit
import Combine
protocol ComposeStatusPollExpiresOptionCollectionViewCellDelegate: class {
func composeStatusPollExpiresOptionCollectionViewCell(_ cell: ComposeStatusPollExpiresOptionCollectionViewCell, didSelectExpiresOption expiresOption: ComposeStatusItem.ComposePollExpiresOptionAttribute.ExpiresOption)
}
final class ComposeStatusPollExpiresOptionCollectionViewCell: UICollectionViewCell {
var disposeBag = Set<AnyCancellable>()
weak var delegate: ComposeStatusPollExpiresOptionCollectionViewCellDelegate?
let durationButton: UIButton = {
let button = HighlightDimmableButton()
button.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 12))
button.expandEdgeInsets = UIEdgeInsets(top: 0, left: -10, bottom: -20, right: -20)
button.setTitle(L10n.Scene.Compose.Poll.durationTime(L10n.Scene.Compose.Poll.thirtyMinutes), for: .normal)
button.setTitleColor(Asset.Colors.Button.normal.color, for: .normal)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
}
extension ComposeStatusPollExpiresOptionCollectionViewCell {
private typealias ExpiresOption = ComposeStatusItem.ComposePollExpiresOptionAttribute.ExpiresOption
private func _init() {
durationButton.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(durationButton)
NSLayoutConstraint.activate([
durationButton.topAnchor.constraint(equalTo: contentView.topAnchor),
durationButton.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor, constant: PollOptionView.checkmarkBackgroundLeadingMargin),
durationButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
let children = ExpiresOption.allCases.map { expiresOption -> UIAction in
UIAction(title: expiresOption.title, image: nil, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] action in
guard let self = self else { return }
self.expiresOptionActionHandler(action, expiresOption: expiresOption)
}
}
durationButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
durationButton.showsMenuAsPrimaryAction = true
}
}
extension ComposeStatusPollExpiresOptionCollectionViewCell {
private func expiresOptionActionHandler(_ sender: UIAction, expiresOption: ExpiresOption) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: select %s", ((#file as NSString).lastPathComponent), #line, #function, expiresOption.title)
delegate?.composeStatusPollExpiresOptionCollectionViewCell(self, didSelectExpiresOption: expiresOption)
}
}

View File

@ -1,5 +1,5 @@
//
// ComposeStatusNewPollOptionCollectionViewCell.swift
// ComposeStatusPollOptionAppendEntryCollectionViewCell.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021-3-23.
@ -8,11 +8,11 @@
import os.log
import UIKit
protocol ComposeStatusNewPollOptionCollectionViewCellDelegate: class {
func ComposeStatusNewPollOptionCollectionViewCellDidPressed(_ cell: ComposeStatusNewPollOptionCollectionViewCell)
protocol ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate: class {
func composeStatusPollOptionAppendEntryCollectionViewCellDidPressed(_ cell: ComposeStatusPollOptionAppendEntryCollectionViewCell)
}
final class ComposeStatusNewPollOptionCollectionViewCell: UICollectionViewCell {
final class ComposeStatusPollOptionAppendEntryCollectionViewCell: UICollectionViewCell {
let pollOptionView = PollOptionView()
@ -25,7 +25,7 @@ final class ComposeStatusNewPollOptionCollectionViewCell: UICollectionViewCell {
}
}
weak var delegate: ComposeStatusNewPollOptionCollectionViewCellDelegate?
weak var delegate: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate?
override func prepareForReuse() {
super.prepareForReuse()
@ -45,7 +45,7 @@ final class ComposeStatusNewPollOptionCollectionViewCell: UICollectionViewCell {
}
extension ComposeStatusNewPollOptionCollectionViewCell {
extension ComposeStatusPollOptionAppendEntryCollectionViewCell {
private func _init() {
pollOptionView.translatesAutoresizingMaskIntoConstraints = false
@ -67,7 +67,7 @@ extension ComposeStatusNewPollOptionCollectionViewCell {
setupBorderColor()
pollOptionView.addGestureRecognizer(singleTagGestureRecognizer)
singleTagGestureRecognizer.addTarget(self, action: #selector(ComposeStatusNewPollOptionCollectionViewCell.singleTagGestureRecognizerHandler(_:)))
singleTagGestureRecognizer.addTarget(self, action: #selector(ComposeStatusPollOptionAppendEntryCollectionViewCell.singleTagGestureRecognizerHandler(_:)))
}
private func setupBorderColor() {
@ -83,11 +83,11 @@ extension ComposeStatusNewPollOptionCollectionViewCell {
}
extension ComposeStatusNewPollOptionCollectionViewCell {
extension ComposeStatusPollOptionAppendEntryCollectionViewCell {
@objc private func singleTagGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
delegate?.ComposeStatusNewPollOptionCollectionViewCellDidPressed(self)
delegate?.composeStatusPollOptionAppendEntryCollectionViewCellDidPressed(self)
}
}
@ -100,7 +100,7 @@ struct ComposeStatusNewPollOptionCollectionViewCell_Previews: PreviewProvider {
static var controls: some View {
Group {
UIViewPreview() {
let cell = ComposeStatusNewPollOptionCollectionViewCell()
let cell = ComposeStatusPollOptionAppendEntryCollectionViewCell()
return cell
}
.previewLayout(.fixed(width: 375, height: 44 + 10))

View File

@ -46,7 +46,8 @@ final class ComposeViewController: UIViewController, NeedsDependency {
collectionView.register(ComposeStatusContentCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusContentCollectionViewCell.self))
collectionView.register(ComposeStatusAttachmentCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusAttachmentCollectionViewCell.self))
collectionView.register(ComposeStatusPollOptionCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollOptionCollectionViewCell.self))
collectionView.register(ComposeStatusNewPollOptionCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusNewPollOptionCollectionViewCell.self))
collectionView.register(ComposeStatusPollOptionAppendEntryCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollOptionAppendEntryCollectionViewCell.self))
collectionView.register(ComposeStatusPollExpiresOptionCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeStatusPollExpiresOptionCollectionViewCell.self))
collectionView.backgroundColor = Asset.Colors.Background.systemBackground.color
return collectionView
}()
@ -158,7 +159,8 @@ extension ComposeViewController {
textEditorViewTextAttributesDelegate: self,
composeStatusAttachmentTableViewCellDelegate: self,
composeStatusPollOptionCollectionViewCellDelegate: self,
composeStatusNewPollOptionCollectionViewCellDelegate: self
composeStatusNewPollOptionCollectionViewCellDelegate: self,
composeStatusPollExpiresOptionCollectionViewCellDelegate: self
)
// respond scrollView overlap change
@ -283,7 +285,7 @@ extension ComposeViewController {
}
private func pollOptionCollectionViewCell(of item: ComposeStatusItem) -> ComposeStatusPollOptionCollectionViewCell? {
guard case .poll = item else { return nil }
guard case .pollOption = item else { return nil }
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
guard let indexPath = diffableDataSource.indexPath(for: item),
let cell = collectionView.cellForItem(at: indexPath) as? ComposeStatusPollOptionCollectionViewCell else {
@ -297,7 +299,7 @@ extension ComposeViewController {
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
let items = diffableDataSource.snapshot().itemIdentifiers(inSection: .poll)
let firstPollItem = items.first { item -> Bool in
guard case .poll = item else { return false }
guard case .pollOption = item else { return false }
return true
}
@ -312,7 +314,7 @@ extension ComposeViewController {
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
let items = diffableDataSource.snapshot().itemIdentifiers(inSection: .poll)
let lastPollItem = items.last { item -> Bool in
guard case .poll = item else { return false }
guard case .pollOption = item else { return false }
return true
}
@ -570,7 +572,7 @@ extension ComposeViewController: ComposeToolbarViewDelegate {
// setup initial poll option if needs
if viewModel.isPollComposing.value, viewModel.pollAttributes.value.isEmpty {
viewModel.pollAttributes.value = [ComposeStatusItem.ComposePollAttribute(), ComposeStatusItem.ComposePollAttribute()]
viewModel.pollAttributes.value = [ComposeStatusItem.ComposePollOptionAttribute(), ComposeStatusItem.ComposePollOptionAttribute()]
}
if viewModel.isPollComposing.value {
@ -704,7 +706,7 @@ extension ComposeViewController: ComposeStatusPollOptionCollectionViewCellDelega
guard let diffableDataSource = viewModel.diffableDataSource else { return }
guard let indexPath = collectionView.indexPath(for: cell) else { return }
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
guard case let .poll(attribute) = item else { return }
guard case let .pollOption(attribute) = item else { return }
var pollAttributes = viewModel.pollAttributes.value
guard let index = pollAttributes.firstIndex(of: attribute) else { return }
@ -747,7 +749,7 @@ extension ComposeViewController: ComposeStatusPollOptionCollectionViewCellDelega
guard let diffableDataSource = viewModel.diffableDataSource else { return }
guard let indexPath = collectionView.indexPath(for: cell) else { return }
let pollItems = diffableDataSource.snapshot().itemIdentifiers(inSection: .poll).filter { item in
guard case .poll = item else { return false }
guard case .pollOption = item else { return false }
return true
}
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
@ -770,12 +772,19 @@ extension ComposeViewController: ComposeStatusPollOptionCollectionViewCellDelega
}
// MARK: - ComposeStatusNewPollOptionCollectionViewCellDelegate
extension ComposeViewController: ComposeStatusNewPollOptionCollectionViewCellDelegate {
func ComposeStatusNewPollOptionCollectionViewCellDidPressed(_ cell: ComposeStatusNewPollOptionCollectionViewCell) {
// MARK: - ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate
extension ComposeViewController: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate {
func composeStatusPollOptionAppendEntryCollectionViewCellDidPressed(_ cell: ComposeStatusPollOptionAppendEntryCollectionViewCell) {
viewModel.createNewPollOptionIfPossible()
DispatchQueue.main.async {
self.markLastPollOptionCollectionViewCellBecomeFirstResponser()
}
}
}
// MARK: - ComposeStatusPollExpiresOptionCollectionViewCellDelegate
extension ComposeViewController: ComposeStatusPollExpiresOptionCollectionViewCellDelegate {
func composeStatusPollExpiresOptionCollectionViewCell(_ cell: ComposeStatusPollExpiresOptionCollectionViewCell, didSelectExpiresOption expiresOption: ComposeStatusItem.ComposePollExpiresOptionAttribute.ExpiresOption) {
viewModel.pollExpiresOptionAttribute.expiresOption.value = expiresOption
}
}

View File

@ -16,7 +16,8 @@ extension ComposeViewModel {
textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate,
composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate,
composeStatusPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionCollectionViewCellDelegate,
composeStatusNewPollOptionCollectionViewCellDelegate: ComposeStatusNewPollOptionCollectionViewCellDelegate
composeStatusNewPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionAppendEntryCollectionViewCellDelegate,
composeStatusPollExpiresOptionCollectionViewCellDelegate: ComposeStatusPollExpiresOptionCollectionViewCellDelegate
) {
let diffableDataSource = ComposeStatusSection.collectionViewDiffableDataSource(
for: collectionView,
@ -26,7 +27,8 @@ extension ComposeViewModel {
textEditorViewTextAttributesDelegate: textEditorViewTextAttributesDelegate,
composeStatusAttachmentTableViewCellDelegate: composeStatusAttachmentTableViewCellDelegate,
composeStatusPollOptionCollectionViewCellDelegate: composeStatusPollOptionCollectionViewCellDelegate,
composeStatusNewPollOptionCollectionViewCellDelegate: composeStatusNewPollOptionCollectionViewCellDelegate
composeStatusNewPollOptionCollectionViewCellDelegate: composeStatusNewPollOptionCollectionViewCellDelegate,
composeStatusPollExpiresOptionCollectionViewCellDelegate: composeStatusPollExpiresOptionCollectionViewCellDelegate
)
// Note: do not allow reorder due to the images display order following the upload time

View File

@ -52,7 +52,8 @@ final class ComposeViewModel {
let attachmentServices = CurrentValueSubject<[MastodonAttachmentService], Never>([])
// polls
let pollAttributes = CurrentValueSubject<[ComposeStatusItem.ComposePollAttribute], Never>([])
let pollAttributes = CurrentValueSubject<[ComposeStatusItem.ComposePollOptionAttribute], Never>([])
let pollExpiresOptionAttribute = ComposeStatusItem.ComposePollExpiresOptionAttribute()
init(
context: AppContext,
@ -196,13 +197,14 @@ final class ComposeViewModel {
if isPollComposing {
var pollItems: [ComposeStatusItem] = []
for pollAttribute in pollAttributes {
let item = ComposeStatusItem.poll(attribute: pollAttribute)
let item = ComposeStatusItem.pollOption(attribute: pollAttribute)
pollItems.append(item)
}
snapshot.appendItems(pollItems, toSection: .poll)
if pollAttributes.count < 4 {
snapshot.appendItems([ComposeStatusItem.newPoll], toSection: .poll)
snapshot.appendItems([ComposeStatusItem.pollOptionAppendEntry], toSection: .poll)
}
snapshot.appendItems([ComposeStatusItem.pollExpiresOption(attribute: self.pollExpiresOptionAttribute)], toSection: .poll)
}
diffableDataSource.apply(snapshot)
@ -268,7 +270,7 @@ extension ComposeViewModel {
func createNewPollOptionIfPossible() {
guard pollAttributes.value.count < 4 else { return }
let attribute = ComposeStatusItem.ComposePollAttribute()
let attribute = ComposeStatusItem.ComposePollOptionAttribute()
pollAttributes.value = pollAttributes.value + [attribute]
}
}
@ -281,9 +283,9 @@ extension ComposeViewModel: MastodonAttachmentServiceDelegate {
}
}
// MARK: - ComposeStatusAttributeDelegate
extension ComposeViewModel: ComposeStatusItemDelegate {
func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollAttribute, pollOptionDidChange: String?) {
// MARK: - ComposePollAttributeDelegate
extension ComposeViewModel: ComposePollAttributeDelegate {
func composePollAttribute(_ attribute: ComposeStatusItem.ComposePollOptionAttribute, pollOptionDidChange: String?) {
// trigger update
pollAttributes.value = pollAttributes.value
}

View File

@ -14,6 +14,7 @@ final class PollOptionView: UIView {
static let optionHeight: CGFloat = 44
static let verticalMargin: CGFloat = 5
static let checkmarkImageSize = CGSize(width: 26, height: 26)
static let checkmarkBackgroundLeadingMargin: CGFloat = 9
private var viewStateDisposeBag = Set<AnyCancellable>()
@ -105,7 +106,7 @@ extension PollOptionView {
roundedBackgroundView.addSubview(checkmarkBackgroundView)
NSLayoutConstraint.activate([
checkmarkBackgroundView.topAnchor.constraint(equalTo: roundedBackgroundView.topAnchor, constant: 9),
checkmarkBackgroundView.leadingAnchor.constraint(equalTo: roundedBackgroundView.leadingAnchor, constant: 9),
checkmarkBackgroundView.leadingAnchor.constraint(equalTo: roundedBackgroundView.leadingAnchor, constant: PollOptionView.checkmarkBackgroundLeadingMargin),
roundedBackgroundView.bottomAnchor.constraint(equalTo: checkmarkBackgroundView.bottomAnchor, constant: 9),
checkmarkBackgroundView.widthAnchor.constraint(equalToConstant: PollOptionView.checkmarkImageSize.width).priority(.required - 1),
checkmarkBackgroundView.heightAnchor.constraint(equalToConstant: PollOptionView.checkmarkImageSize.height).priority(.required - 1),