forked from zelo72/mastodon-ios
feat: add expires duration selector for poll
This commit is contained in:
parent
3eb2b916a7
commit
d05f97951b
|
@ -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 what’s 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue