forked from zelo72/mastodon-ios
feat: add poll UI/UX for compose scene
This commit is contained in:
parent
93fe9ce30c
commit
b8e062c92e
|
@ -187,6 +187,10 @@
|
|||
DB789A1C25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift */; };
|
||||
DB789A2B25F9F7AB0071ACA0 /* ComposeRepliedToTootContentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB789A2A25F9F7AB0071ACA0 /* ComposeRepliedToTootContentCollectionViewCell.swift */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
DB89BA0025C10FD0008580ED /* CoreDataStack.h in Headers */ = {isa = PBXBuildFile; fileRef = DB89B9F025C10FD0008580ED /* CoreDataStack.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
|
@ -481,6 +485,10 @@
|
|||
DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeStatusContentCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
DB789A2A25F9F7AB0071ACA0 /* ComposeRepliedToTootContentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeRepliedToTootContentCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
DB89B9F125C10FD0008580ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -665,6 +673,7 @@
|
|||
2D152A8B25C295CC009AA50C /* StatusView.swift */,
|
||||
2D694A7325F9EB4E0038ADDC /* ContentWarningOverlayView.swift */,
|
||||
2D571B2E26004EC000540450 /* NavigationBarProgressView.swift */,
|
||||
DB87D44A2609C11900D12C0D /* PollOptionView.swift */,
|
||||
);
|
||||
path = Content;
|
||||
sourceTree = "<group>";
|
||||
|
@ -752,7 +761,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
DB45FB0425CA87B4005A8AC7 /* APIService */,
|
||||
DB49A61925FF327D00B98345 /* EmojiService */,
|
||||
DB9A489B26036E19008B817C /* MastodonAttachmentService */,
|
||||
DB45FB0E25CA87D0005A8AC7 /* AuthenticationService.swift */,
|
||||
DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */,
|
||||
|
@ -843,6 +851,7 @@
|
|||
DB9D6C1325E4F97A0051B173 /* Container */,
|
||||
DBA9B90325F1D4420012E7B6 /* Control */,
|
||||
2D152A8A25C295B8009AA50C /* Content */,
|
||||
DB87D45C2609DE6600D12C0D /* TextField */,
|
||||
DB1D187125EF5BBD003F1F23 /* TableView */,
|
||||
2D7631A625C1533800929FB9 /* TableviewCell */,
|
||||
);
|
||||
|
@ -1153,10 +1162,20 @@
|
|||
DB789A2A25F9F7AB0071ACA0 /* ComposeRepliedToTootContentCollectionViewCell.swift */,
|
||||
DB789A1B25F9F76A0071ACA0 /* ComposeStatusContentCollectionViewCell.swift */,
|
||||
DB6B351D2601FAEE00DC1E11 /* ComposeStatusAttachmentTableViewCell.swift */,
|
||||
DB87D4442609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift */,
|
||||
DB87D4502609CF1E00D12C0D /* ComposeStatusNewPollOptionCollectionViewCell.swift */,
|
||||
);
|
||||
path = CollectionViewCell;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB87D45C2609DE6600D12C0D /* TextField */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB87D4562609DD5300D12C0D /* DeleteBackwardResponseTextField.swift */,
|
||||
);
|
||||
path = TextField;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB89B9EF25C10FD0008580ED /* CoreDataStack */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1792,6 +1811,7 @@
|
|||
DB9A488A26034D40008B817C /* ComposeViewModel+PublishState.swift in Sources */,
|
||||
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */,
|
||||
2DA504692601ADE7008F4E6C /* SawToothView.swift in Sources */,
|
||||
DB87D4572609DD5300D12C0D /* DeleteBackwardResponseTextField.swift in Sources */,
|
||||
2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarState.swift in Sources */,
|
||||
DBA0A10925FB3C2B0079C110 /* RoundedEdgesButton.swift in Sources */,
|
||||
DBABE3EC25ECAC4B00879EE5 /* WelcomeIllustrationView.swift in Sources */,
|
||||
|
@ -1818,6 +1838,7 @@
|
|||
DB98338825C945ED00AD9700 /* Assets.swift in Sources */,
|
||||
DB6B351E2601FAEE00DC1E11 /* ComposeStatusAttachmentTableViewCell.swift in Sources */,
|
||||
2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */,
|
||||
DB87D44B2609C11900D12C0D /* PollOptionView.swift in Sources */,
|
||||
DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */,
|
||||
2D206B7225F5D27F00143C56 /* AudioContainerView.swift in Sources */,
|
||||
DB9D6C2425E502C60051B173 /* MosaicImageViewModel.swift in Sources */,
|
||||
|
@ -1868,6 +1889,7 @@
|
|||
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
|
||||
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
|
||||
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */,
|
||||
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */,
|
||||
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
|
||||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
||||
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
|
||||
|
@ -1931,6 +1953,7 @@
|
|||
DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */,
|
||||
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */,
|
||||
DB71FD2C25F86A5100512AE1 /* AvatarStackContainerButton.swift in Sources */,
|
||||
DB87D4512609CF1E00D12C0D /* ComposeStatusNewPollOptionCollectionViewCell.swift in Sources */,
|
||||
DB9A489026035963008B817C /* APIService+Media.swift in Sources */,
|
||||
2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */,
|
||||
DB49A62525FF334C00B98345 /* EmojiService+CustomEmojiViewModel+LoadState.swift in Sources */,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import Foundation
|
||||
import MastodonSDK
|
||||
|
||||
/// Note: update Equatable when change case
|
||||
enum CategoryPickerItem {
|
||||
case all
|
||||
case category(category: Mastodon.Entity.Category)
|
||||
|
|
|
@ -9,12 +9,17 @@ import Foundation
|
|||
import Combine
|
||||
import CoreData
|
||||
|
||||
/// Note: update Equatable when change case
|
||||
enum ComposeStatusItem {
|
||||
case replyTo(statusObjectID: NSManagedObjectID)
|
||||
case input(replyToStatusObjectID: NSManagedObjectID?, attribute: ComposeStatusAttribute)
|
||||
case attachment(attachmentService: MastodonAttachmentService)
|
||||
case poll(attribute: ComposePollAttribute)
|
||||
case newPoll
|
||||
}
|
||||
|
||||
extension ComposeStatusItem: Equatable { }
|
||||
|
||||
extension ComposeStatusItem: Hashable { }
|
||||
|
||||
extension ComposeStatusItem {
|
||||
|
@ -38,3 +43,20 @@ extension ComposeStatusItem {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ComposeStatusItem {
|
||||
final class ComposePollAttribute: Equatable, Hashable {
|
||||
private let id = UUID()
|
||||
|
||||
let option = CurrentValueSubject<String, Never>("")
|
||||
|
||||
static func == (lhs: ComposePollAttribute, rhs: ComposePollAttribute) -> Bool {
|
||||
return lhs.id == rhs.id &&
|
||||
lhs.option.value == rhs.option.value
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
|
||||
/// Note: update Equatable when change case
|
||||
enum PollItem {
|
||||
case opion(objectID: NSManagedObjectID, attribute: Attribute)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ enum ComposeStatusSection: Equatable, Hashable {
|
|||
case repliedTo
|
||||
case status
|
||||
case attachment
|
||||
case poll
|
||||
}
|
||||
|
||||
extension ComposeStatusSection {
|
||||
|
@ -33,7 +34,9 @@ extension ComposeStatusSection {
|
|||
managedObjectContext: NSManagedObjectContext,
|
||||
composeKind: ComposeKind,
|
||||
textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate,
|
||||
composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate
|
||||
composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate,
|
||||
composeStatusPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionCollectionViewCellDelegate,
|
||||
composeStatusNewPollOptionCollectionViewCellDelegate: ComposeStatusNewPollOptionCollectionViewCellDelegate
|
||||
) -> UICollectionViewDiffableDataSource<ComposeStatusSection, ComposeStatusItem> {
|
||||
UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item -> UICollectionViewCell? in
|
||||
switch item {
|
||||
|
@ -54,11 +57,11 @@ extension ComposeStatusSection {
|
|||
}
|
||||
ComposeStatusSection.configure(cell: cell, attribute: attribute)
|
||||
cell.textEditorView.textAttributesDelegate = textEditorViewTextAttributesDelegate
|
||||
// self size input cell
|
||||
cell.composeContent
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { text in
|
||||
// self size input cell
|
||||
collectionView.collectionViewLayout.invalidateLayout()
|
||||
// bind input data
|
||||
attribute.composeContent.value = text
|
||||
|
@ -124,6 +127,19 @@ extension ComposeStatusSection {
|
|||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
return cell
|
||||
case .poll(let attribute):
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusPollOptionCollectionViewCell.self), for: indexPath) as! ComposeStatusPollOptionCollectionViewCell
|
||||
cell.pollOptionView.optionTextField.text = attribute.option.value
|
||||
cell.pollOption
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: \.value, on: attribute.option)
|
||||
.store(in: &cell.disposeBag)
|
||||
cell.delegate = composeStatusPollOptionCollectionViewCellDelegate
|
||||
return cell
|
||||
case .newPoll:
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ComposeStatusNewPollOptionCollectionViewCell.self), for: indexPath) as! ComposeStatusNewPollOptionCollectionViewCell
|
||||
cell.delegate = composeStatusNewPollOptionCollectionViewCellDelegate
|
||||
return cell
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ extension PollSection {
|
|||
pollOption option: PollOption,
|
||||
pollItemAttribute attribute: PollItem.Attribute
|
||||
) {
|
||||
cell.optionLabel.text = option.title
|
||||
cell.pollOptionView.optionTextField.text = option.title
|
||||
configure(cell: cell, selectState: attribute.selectState)
|
||||
configure(cell: cell, voteState: attribute.voteState)
|
||||
cell.attribute = attribute
|
||||
|
@ -52,35 +52,35 @@ extension PollSection {
|
|||
static func configure(cell: PollOptionTableViewCell, selectState state: PollItem.Attribute.SelectState) {
|
||||
switch state {
|
||||
case .none:
|
||||
cell.checkmarkBackgroundView.isHidden = true
|
||||
cell.checkmarkImageView.isHidden = true
|
||||
cell.pollOptionView.checkmarkBackgroundView.isHidden = true
|
||||
cell.pollOptionView.checkmarkImageView.isHidden = true
|
||||
case .off:
|
||||
cell.checkmarkBackgroundView.backgroundColor = .systemBackground
|
||||
cell.checkmarkBackgroundView.layer.borderColor = UIColor.systemGray3.cgColor
|
||||
cell.checkmarkBackgroundView.layer.borderWidth = 1
|
||||
cell.checkmarkBackgroundView.isHidden = false
|
||||
cell.checkmarkImageView.isHidden = true
|
||||
cell.pollOptionView.checkmarkBackgroundView.backgroundColor = .systemBackground
|
||||
cell.pollOptionView.checkmarkBackgroundView.layer.borderColor = UIColor.systemGray3.cgColor
|
||||
cell.pollOptionView.checkmarkBackgroundView.layer.borderWidth = 1
|
||||
cell.pollOptionView.checkmarkBackgroundView.isHidden = false
|
||||
cell.pollOptionView.checkmarkImageView.isHidden = true
|
||||
case .on:
|
||||
cell.checkmarkBackgroundView.backgroundColor = .systemBackground
|
||||
cell.checkmarkBackgroundView.layer.borderColor = UIColor.clear.cgColor
|
||||
cell.checkmarkBackgroundView.layer.borderWidth = 0
|
||||
cell.checkmarkBackgroundView.isHidden = false
|
||||
cell.checkmarkImageView.isHidden = false
|
||||
cell.pollOptionView.checkmarkBackgroundView.backgroundColor = .systemBackground
|
||||
cell.pollOptionView.checkmarkBackgroundView.layer.borderColor = UIColor.clear.cgColor
|
||||
cell.pollOptionView.checkmarkBackgroundView.layer.borderWidth = 0
|
||||
cell.pollOptionView.checkmarkBackgroundView.isHidden = false
|
||||
cell.pollOptionView.checkmarkImageView.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
static func configure(cell: PollOptionTableViewCell, voteState state: PollItem.Attribute.VoteState) {
|
||||
switch state {
|
||||
case .hidden:
|
||||
cell.optionPercentageLabel.isHidden = true
|
||||
cell.voteProgressStripView.isHidden = true
|
||||
cell.voteProgressStripView.setProgress(0.0, animated: false)
|
||||
cell.pollOptionView.optionPercentageLabel.isHidden = true
|
||||
cell.pollOptionView.voteProgressStripView.isHidden = true
|
||||
cell.pollOptionView.voteProgressStripView.setProgress(0.0, animated: false)
|
||||
case .reveal(let voted, let percentage, let animated):
|
||||
cell.optionPercentageLabel.isHidden = false
|
||||
cell.optionPercentageLabel.text = String(Int(100 * percentage)) + "%"
|
||||
cell.voteProgressStripView.isHidden = false
|
||||
cell.voteProgressStripView.tintColor = voted ? Asset.Colors.Background.Poll.highlight.color : Asset.Colors.Background.Poll.disabled.color
|
||||
cell.voteProgressStripView.setProgress(CGFloat(percentage), animated: animated)
|
||||
cell.pollOptionView.optionPercentageLabel.isHidden = false
|
||||
cell.pollOptionView.optionPercentageLabel.text = String(Int(100 * percentage)) + "%"
|
||||
cell.pollOptionView.voteProgressStripView.isHidden = false
|
||||
cell.pollOptionView.voteProgressStripView.tintColor = voted ? Asset.Colors.Background.Poll.highlight.color : Asset.Colors.Background.Poll.disabled.color
|
||||
cell.pollOptionView.voteProgressStripView.setProgress(CGFloat(percentage), animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ internal enum Asset {
|
|||
}
|
||||
internal enum Circles {
|
||||
internal static let plusCircleFill = ImageAsset(name: "Circles/plus.circle.fill")
|
||||
internal static let plusCircle = ImageAsset(name: "Circles/plus.circle")
|
||||
}
|
||||
internal enum Colors {
|
||||
internal enum Background {
|
||||
|
@ -46,6 +47,7 @@ internal enum Asset {
|
|||
internal static let systemBackground = ColorAsset(name: "Colors/Background/system.background")
|
||||
internal static let systemGroupedBackground = ColorAsset(name: "Colors/Background/system.grouped.background")
|
||||
internal static let tertiarySystemBackground = ColorAsset(name: "Colors/Background/tertiary.system.background")
|
||||
internal static let tertiarySystemGroupedBackground = ColorAsset(name: "Colors/Background/tertiary.system.grouped.background")
|
||||
}
|
||||
internal enum Button {
|
||||
internal static let actionToolbar = ColorAsset(name: "Colors/Button/action.toolbar")
|
||||
|
|
15
Mastodon/Resources/Assets.xcassets/Circles/plus.circle.imageset/Contents.json
vendored
Normal file
15
Mastodon/Resources/Assets.xcassets/Circles/plus.circle.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "plus.circle.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
95
Mastodon/Resources/Assets.xcassets/Circles/plus.circle.imageset/plus.circle.pdf
vendored
Normal file
95
Mastodon/Resources/Assets.xcassets/Circles/plus.circle.imageset/plus.circle.pdf
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
21.999905 0.000160 m
|
||||
34.035152 0.000160 44.000000 9.986404 44.000000 22.000080 c
|
||||
44.000000 34.035332 34.013599 44.000000 21.978350 44.000000 c
|
||||
9.964656 44.000000 0.000000 34.035332 0.000000 22.000080 c
|
||||
0.000000 9.986404 9.986255 0.000160 21.999905 0.000160 c
|
||||
h
|
||||
21.999905 3.666824 m
|
||||
11.819542 3.666824 3.688203 11.819717 3.688203 22.000080 c
|
||||
3.688203 32.180443 11.797986 40.333336 21.978350 40.333336 c
|
||||
32.158710 40.333336 40.311611 32.180443 40.333256 22.000080 c
|
||||
40.354897 11.819717 32.180267 3.666824 21.999905 3.666824 c
|
||||
h
|
||||
13.782296 20.188307 m
|
||||
20.166574 20.188307 l
|
||||
20.166574 13.760918 l
|
||||
20.166574 12.682493 20.899923 11.949142 21.956793 11.949142 c
|
||||
23.035217 11.949142 23.790121 12.682493 23.790121 13.760918 c
|
||||
23.790121 20.188307 l
|
||||
30.217514 20.188307 l
|
||||
31.295938 20.188307 32.029289 20.921658 32.029289 21.978525 c
|
||||
32.029289 23.056950 31.295938 23.811855 30.217514 23.811855 c
|
||||
23.790121 23.811855 l
|
||||
23.790121 30.196133 l
|
||||
23.790121 31.317715 23.035217 32.051018 21.956793 32.051018 c
|
||||
20.899923 32.051018 20.166574 31.296114 20.166574 30.196133 c
|
||||
20.166574 23.811855 l
|
||||
13.782296 23.811855 l
|
||||
12.660716 23.811855 11.927410 23.056950 11.927410 21.978525 c
|
||||
11.927410 20.921658 12.682316 20.188307 13.782296 20.188307 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1347
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 44.000000 44.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001437 00000 n
|
||||
0000001460 00000 n
|
||||
0000001633 00000 n
|
||||
0000001707 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1766
|
||||
%%EOF
|
|
@ -5,9 +5,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "255",
|
||||
"green" : "255",
|
||||
"red" : "255"
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
@ -23,9 +23,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.169",
|
||||
"green" : "0.141",
|
||||
"red" : "0.125"
|
||||
"blue" : "0x2B",
|
||||
"green" : "0x23",
|
||||
"red" : "0x1F"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "232",
|
||||
"green" : "225",
|
||||
"red" : "217"
|
||||
"blue" : "0xE8",
|
||||
"green" : "0xE1",
|
||||
"red" : "0xD9"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
@ -23,9 +23,9 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.169",
|
||||
"green" : "0.141",
|
||||
"red" : "0.125"
|
||||
"blue" : "0x2B",
|
||||
"green" : "0x23",
|
||||
"red" : "0x1F"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -5,9 +5,27 @@
|
|||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x43",
|
||||
"green" : "0x36",
|
||||
"red" : "0x32"
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x2B",
|
||||
"green" : "0x23",
|
||||
"red" : "0x1F"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xE8",
|
||||
"green" : "0xE1",
|
||||
"red" : "0xD9"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x2B",
|
||||
"green" : "0x23",
|
||||
"red" : "0x1F"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -66,7 +66,7 @@ extension ComposeStatusContentCollectionViewCell {
|
|||
textEditorView.topAnchor.constraint(equalTo: statusView.bottomAnchor, constant: 10),
|
||||
textEditorView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
||||
textEditorView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
|
||||
contentView.bottomAnchor.constraint(equalTo: textEditorView.bottomAnchor, constant: 20),
|
||||
contentView.bottomAnchor.constraint(equalTo: textEditorView.bottomAnchor, constant: 10),
|
||||
textEditorView.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh),
|
||||
])
|
||||
textEditorView.setContentCompressionResistancePriority(.required - 2, for: .vertical)
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
//
|
||||
// ComposeStatusNewPollOptionCollectionViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-23.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
|
||||
protocol ComposeStatusNewPollOptionCollectionViewCellDelegate: class {
|
||||
func ComposeStatusNewPollOptionCollectionViewCellDidPressed(_ cell: ComposeStatusNewPollOptionCollectionViewCell)
|
||||
}
|
||||
|
||||
final class ComposeStatusNewPollOptionCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
let pollOptionView = PollOptionView()
|
||||
|
||||
let singleTagGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||
|
||||
override var isHighlighted: Bool {
|
||||
didSet {
|
||||
pollOptionView.roundedBackgroundView.backgroundColor = isHighlighted ? Asset.Colors.Background.secondarySystemBackground.color : Asset.Colors.Background.systemBackground.color
|
||||
pollOptionView.plusCircleImageView.tintColor = isHighlighted ? Asset.Colors.Button.normal.color.withAlphaComponent(0.5) : Asset.Colors.Button.normal.color
|
||||
}
|
||||
}
|
||||
|
||||
weak var delegate: ComposeStatusNewPollOptionCollectionViewCellDelegate?
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
delegate = nil
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeStatusNewPollOptionCollectionViewCell {
|
||||
|
||||
private func _init() {
|
||||
pollOptionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(pollOptionView)
|
||||
NSLayoutConstraint.activate([
|
||||
pollOptionView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
pollOptionView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
||||
pollOptionView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
|
||||
pollOptionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
])
|
||||
|
||||
pollOptionView.checkmarkImageView.isHidden = true
|
||||
pollOptionView.checkmarkBackgroundView.isHidden = true
|
||||
pollOptionView.optionPercentageLabel.isHidden = true
|
||||
pollOptionView.optionTextField.isHidden = true
|
||||
pollOptionView.plusCircleImageView.isHidden = false
|
||||
|
||||
pollOptionView.roundedBackgroundView.backgroundColor = Asset.Colors.Background.systemBackground.color
|
||||
setupBorderColor()
|
||||
|
||||
pollOptionView.addGestureRecognizer(singleTagGestureRecognizer)
|
||||
singleTagGestureRecognizer.addTarget(self, action: #selector(ComposeStatusNewPollOptionCollectionViewCell.singleTagGestureRecognizerHandler(_:)))
|
||||
}
|
||||
|
||||
private func setupBorderColor() {
|
||||
pollOptionView.roundedBackgroundView.layer.borderWidth = 1
|
||||
pollOptionView.roundedBackgroundView.layer.borderColor = Asset.Colors.Background.secondarySystemBackground.color.cgColor
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
setupBorderColor()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeStatusNewPollOptionCollectionViewCell {
|
||||
|
||||
@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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if canImport(SwiftUI) && DEBUG
|
||||
import SwiftUI
|
||||
|
||||
struct ComposeStatusNewPollOptionCollectionViewCell_Previews: PreviewProvider {
|
||||
|
||||
static var controls: some View {
|
||||
Group {
|
||||
UIViewPreview() {
|
||||
let cell = ComposeStatusNewPollOptionCollectionViewCell()
|
||||
return cell
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 44 + 10))
|
||||
}
|
||||
}
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
controls.colorScheme(.light)
|
||||
controls.colorScheme(.dark)
|
||||
}
|
||||
.background(Color.gray)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,147 @@
|
|||
//
|
||||
// ComposeStatusPollOptionCollectionViewCell.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-23.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
protocol ComposeStatusPollOptionCollectionViewCellDelegate: class {
|
||||
func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textBeforeDeleteBackward text: String?)
|
||||
func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, pollOptionTextFieldDidReturn: UITextField)
|
||||
}
|
||||
|
||||
final class ComposeStatusPollOptionCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
weak var delegate: ComposeStatusPollOptionCollectionViewCellDelegate?
|
||||
|
||||
let pollOptionView = PollOptionView()
|
||||
|
||||
let singleTagGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
|
||||
|
||||
private var pollOptionSubscription: AnyCancellable?
|
||||
let pollOption = PassthroughSubject<String, Never>()
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
delegate = nil
|
||||
disposeBag.removeAll()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeStatusPollOptionCollectionViewCell {
|
||||
|
||||
private func _init() {
|
||||
pollOptionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(pollOptionView)
|
||||
NSLayoutConstraint.activate([
|
||||
pollOptionView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
pollOptionView.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
|
||||
pollOptionView.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
|
||||
pollOptionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
])
|
||||
|
||||
pollOptionView.checkmarkImageView.isHidden = true
|
||||
pollOptionView.optionPercentageLabel.isHidden = true
|
||||
pollOptionView.optionTextField.text = nil
|
||||
|
||||
pollOptionView.roundedBackgroundView.backgroundColor = Asset.Colors.Background.secondarySystemBackground.color
|
||||
pollOptionView.checkmarkBackgroundView.backgroundColor = Asset.Colors.Background.tertiarySystemBackground.color
|
||||
setupBorderColor()
|
||||
|
||||
pollOptionView.addGestureRecognizer(singleTagGestureRecognizer)
|
||||
singleTagGestureRecognizer.addTarget(self, action: #selector(ComposeStatusPollOptionCollectionViewCell.singleTagGestureRecognizerHandler(_:)))
|
||||
|
||||
pollOptionSubscription = NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: pollOptionView.optionTextField)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] notification in
|
||||
guard let self = self else { return }
|
||||
guard let textField = notification.object as? UITextField else { return }
|
||||
self.pollOption.send(textField.text ?? "")
|
||||
}
|
||||
pollOptionView.optionTextField.deleteBackwardDelegate = self
|
||||
pollOptionView.optionTextField.delegate = self
|
||||
}
|
||||
|
||||
private func setupBorderColor() {
|
||||
pollOptionView.checkmarkBackgroundView.layer.borderColor = UIColor.systemGray3.cgColor
|
||||
pollOptionView.checkmarkBackgroundView.layer.borderWidth = 1
|
||||
}
|
||||
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
setupBorderColor()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeStatusPollOptionCollectionViewCell {
|
||||
|
||||
@objc private func singleTagGestureRecognizerHandler(_ sender: UITapGestureRecognizer) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
pollOptionView.optionTextField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - DeleteBackwardResponseTextFieldDelegate
|
||||
extension ComposeStatusPollOptionCollectionViewCell: DeleteBackwardResponseTextFieldDelegate {
|
||||
func deleteBackwardResponseTextField(_ textField: DeleteBackwardResponseTextField, textBeforeDelete: String?) {
|
||||
delegate?.composeStatusPollOptionCollectionViewCell(self, textBeforeDeleteBackward: textBeforeDelete)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITextFieldDelegate
|
||||
extension ComposeStatusPollOptionCollectionViewCell: UITextFieldDelegate {
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
if textField === pollOptionView.optionTextField {
|
||||
delegate?.composeStatusPollOptionCollectionViewCell(self, pollOptionTextFieldDidReturn: textField)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(SwiftUI) && DEBUG
|
||||
import SwiftUI
|
||||
|
||||
struct ComposeStatusPollOptionCollectionViewCell_Previews: PreviewProvider {
|
||||
|
||||
static var controls: some View {
|
||||
Group {
|
||||
UIViewPreview() {
|
||||
let cell = ComposeStatusPollOptionCollectionViewCell()
|
||||
return cell
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 44 + 10))
|
||||
}
|
||||
}
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
controls.colorScheme(.light)
|
||||
controls.colorScheme(.dark)
|
||||
}
|
||||
.background(Color.gray)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -45,6 +45,8 @@ final class ComposeViewController: UIViewController, NeedsDependency {
|
|||
collectionView.register(ComposeRepliedToTootContentCollectionViewCell.self, forCellWithReuseIdentifier: String(describing: ComposeRepliedToTootContentCollectionViewCell.self))
|
||||
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.backgroundColor = Asset.Colors.Background.systemBackground.color
|
||||
return collectionView
|
||||
}()
|
||||
|
@ -154,7 +156,9 @@ extension ComposeViewController {
|
|||
for: collectionView,
|
||||
dependency: self,
|
||||
textEditorViewTextAttributesDelegate: self,
|
||||
composeStatusAttachmentTableViewCellDelegate: self
|
||||
composeStatusAttachmentTableViewCellDelegate: self,
|
||||
composeStatusPollOptionCollectionViewCellDelegate: self,
|
||||
composeStatusNewPollOptionCollectionViewCellDelegate: self
|
||||
)
|
||||
|
||||
// respond scrollView overlap change
|
||||
|
@ -206,6 +210,16 @@ extension ComposeViewController {
|
|||
.assign(to: \.isEnabled, on: publishBarButtonItem)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
viewModel.isMediaToolbarButtonEnabled
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: \.isEnabled, on: composeToolbarView.mediaButton)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
viewModel.isPollToolbarButtonEnabled
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assign(to: \.isEnabled, on: composeToolbarView.pollButton)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// bind custom emojis
|
||||
viewModel.customEmojiViewModel
|
||||
.compactMap { $0?.emojis }
|
||||
|
@ -268,6 +282,57 @@ extension ComposeViewController {
|
|||
textEditorView()?.isEditing = true
|
||||
}
|
||||
|
||||
private func pollOptionCollectionViewCell(of item: ComposeStatusItem) -> ComposeStatusPollOptionCollectionViewCell? {
|
||||
guard case .poll = 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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
private func firstPollOptionCollectionViewCell() -> ComposeStatusPollOptionCollectionViewCell? {
|
||||
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 }
|
||||
return true
|
||||
}
|
||||
|
||||
guard let item = firstPollItem else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return pollOptionCollectionViewCell(of: item)
|
||||
}
|
||||
|
||||
private func lastPollOptionCollectionViewCell() -> ComposeStatusPollOptionCollectionViewCell? {
|
||||
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 }
|
||||
return true
|
||||
}
|
||||
|
||||
guard let item = lastPollItem else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return pollOptionCollectionViewCell(of: item)
|
||||
}
|
||||
|
||||
private func markFirstPollOptionCollectionViewCellBecomeFirstResponser() {
|
||||
guard let cell = firstPollOptionCollectionViewCell() else { return }
|
||||
cell.pollOptionView.optionTextField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
private func markLastPollOptionCollectionViewCellBecomeFirstResponser() {
|
||||
guard let cell = lastPollOptionCollectionViewCell() else { return }
|
||||
cell.pollOptionView.optionTextField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
private func showDismissConfirmAlertController() {
|
||||
let alertController = UIAlertController(
|
||||
title: L10n.Common.Alerts.DiscardPostContent.title,
|
||||
|
@ -490,7 +555,6 @@ extension ComposeViewController: TextEditorViewTextAttributesDelegate {
|
|||
extension ComposeViewController: ComposeToolbarViewDelegate {
|
||||
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, cameraButtonDidPressed sender: UIButton, mediaSelectionType: ComposeToolbarView.MediaSelectionType) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: %s", ((#file as NSString).lastPathComponent), #line, #function, mediaSelectionType.rawValue)
|
||||
switch mediaSelectionType {
|
||||
case .photoLibrary:
|
||||
present(imagePicker, animated: true, completion: nil)
|
||||
|
@ -501,20 +565,31 @@ extension ComposeViewController: ComposeToolbarViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, gifButtonDidPressed sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, pollButtonDidPressed sender: UIButton) {
|
||||
viewModel.isPollComposing.value.toggle()
|
||||
|
||||
// setup initial poll option if needs
|
||||
if viewModel.isPollComposing.value, viewModel.pollAttributes.value.isEmpty {
|
||||
viewModel.pollAttributes.value = [ComposeStatusItem.ComposePollAttribute(), ComposeStatusItem.ComposePollAttribute()]
|
||||
}
|
||||
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, atButtonDidPressed sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
if viewModel.isPollComposing.value {
|
||||
// Magic RunLoop
|
||||
DispatchQueue.main.async {
|
||||
self.markFirstPollOptionCollectionViewCellBecomeFirstResponser()
|
||||
}
|
||||
} else {
|
||||
markTextEditorViewBecomeFirstResponser()
|
||||
}
|
||||
}
|
||||
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, topicButtonDidPressed sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, emojiButtonDidPressed sender: UIButton) {
|
||||
}
|
||||
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, locationButtonDidPressed sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton) {
|
||||
}
|
||||
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton) {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -622,3 +697,88 @@ extension ComposeViewController: ComposeStatusAttachmentCollectionViewCellDelega
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - ComposeStatusPollOptionCollectionViewCellDelegate
|
||||
extension ComposeViewController: ComposeStatusPollOptionCollectionViewCellDelegate {
|
||||
|
||||
// handle delete backward event for poll option input
|
||||
func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, textBeforeDeleteBackward text: String?) {
|
||||
guard (text ?? "").isEmpty else { return }
|
||||
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 }
|
||||
|
||||
var pollAttributes = viewModel.pollAttributes.value
|
||||
guard let index = pollAttributes.firstIndex(of: attribute) else { return }
|
||||
|
||||
// mark previous (fallback to next) item of removed middle poll option become first responder
|
||||
let pollItems = diffableDataSource.snapshot().itemIdentifiers(inSection: .poll)
|
||||
if let indexOfItem = pollItems.firstIndex(of: item), index > 0 {
|
||||
func cellBeforeRemoved() -> ComposeStatusPollOptionCollectionViewCell? {
|
||||
guard index > 0 else { return nil }
|
||||
let indexBeforeRemoved = pollItems.index(before: indexOfItem)
|
||||
let itemBeforeRemoved = pollItems[indexBeforeRemoved]
|
||||
return pollOptionCollectionViewCell(of: itemBeforeRemoved)
|
||||
}
|
||||
|
||||
func cellAfterRemoved() -> ComposeStatusPollOptionCollectionViewCell? {
|
||||
guard index < pollItems.count - 1 else { return nil }
|
||||
let indexAfterRemoved = pollItems.index(after: index)
|
||||
let itemAfterRemoved = pollItems[indexAfterRemoved]
|
||||
return pollOptionCollectionViewCell(of: itemAfterRemoved)
|
||||
}
|
||||
|
||||
var cell: ComposeStatusPollOptionCollectionViewCell? = cellBeforeRemoved()
|
||||
if cell == nil {
|
||||
cell = cellAfterRemoved()
|
||||
}
|
||||
cell?.pollOptionView.optionTextField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
guard pollAttributes.count > 2 else {
|
||||
return
|
||||
}
|
||||
pollAttributes.remove(at: index)
|
||||
|
||||
// update data source
|
||||
viewModel.pollAttributes.value = pollAttributes
|
||||
}
|
||||
|
||||
// handle keyboard return event for poll option input
|
||||
func composeStatusPollOptionCollectionViewCell(_ cell: ComposeStatusPollOptionCollectionViewCell, pollOptionTextFieldDidReturn: UITextField) {
|
||||
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 }
|
||||
return true
|
||||
}
|
||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
|
||||
guard let index = pollItems.firstIndex(of: item) else { return }
|
||||
|
||||
if index == pollItems.count - 1 {
|
||||
// is the last
|
||||
viewModel.createNewPollOptionIfPossible()
|
||||
DispatchQueue.main.async {
|
||||
self.markLastPollOptionCollectionViewCellBecomeFirstResponser()
|
||||
}
|
||||
} else {
|
||||
// not the last
|
||||
let indexAfter = pollItems.index(after: index)
|
||||
let itemAfter = pollItems[indexAfter]
|
||||
let cell = pollOptionCollectionViewCell(of: itemAfter)
|
||||
cell?.pollOptionView.optionTextField.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - ComposeStatusNewPollOptionCollectionViewCellDelegate
|
||||
extension ComposeViewController: ComposeStatusNewPollOptionCollectionViewCellDelegate {
|
||||
func ComposeStatusNewPollOptionCollectionViewCellDidPressed(_ cell: ComposeStatusNewPollOptionCollectionViewCell) {
|
||||
viewModel.createNewPollOptionIfPossible()
|
||||
DispatchQueue.main.async {
|
||||
self.markLastPollOptionCollectionViewCellBecomeFirstResponser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,9 @@ extension ComposeViewModel {
|
|||
for collectionView: UICollectionView,
|
||||
dependency: NeedsDependency,
|
||||
textEditorViewTextAttributesDelegate: TextEditorViewTextAttributesDelegate,
|
||||
composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate
|
||||
composeStatusAttachmentTableViewCellDelegate: ComposeStatusAttachmentCollectionViewCellDelegate,
|
||||
composeStatusPollOptionCollectionViewCellDelegate: ComposeStatusPollOptionCollectionViewCellDelegate,
|
||||
composeStatusNewPollOptionCollectionViewCellDelegate: ComposeStatusNewPollOptionCollectionViewCellDelegate
|
||||
) {
|
||||
let diffableDataSource = ComposeStatusSection.collectionViewDiffableDataSource(
|
||||
for: collectionView,
|
||||
|
@ -22,7 +24,9 @@ extension ComposeViewModel {
|
|||
managedObjectContext: context.managedObjectContext,
|
||||
composeKind: composeKind,
|
||||
textEditorViewTextAttributesDelegate: textEditorViewTextAttributesDelegate,
|
||||
composeStatusAttachmentTableViewCellDelegate: composeStatusAttachmentTableViewCellDelegate
|
||||
composeStatusAttachmentTableViewCellDelegate: composeStatusAttachmentTableViewCellDelegate,
|
||||
composeStatusPollOptionCollectionViewCellDelegate: composeStatusPollOptionCollectionViewCellDelegate,
|
||||
composeStatusNewPollOptionCollectionViewCellDelegate: composeStatusNewPollOptionCollectionViewCellDelegate
|
||||
)
|
||||
|
||||
// Note: do not allow reorder due to the images display order following the upload time
|
||||
|
@ -48,7 +52,7 @@ extension ComposeViewModel {
|
|||
|
||||
self.diffableDataSource = diffableDataSource
|
||||
var snapshot = NSDiffableDataSourceSnapshot<ComposeStatusSection, ComposeStatusItem>()
|
||||
snapshot.appendSections([.repliedTo, .status, .attachment])
|
||||
snapshot.appendSections([.repliedTo, .status, .attachment, .poll])
|
||||
switch composeKind {
|
||||
case .reply(let statusObjectID):
|
||||
snapshot.appendItems([.replyTo(statusObjectID: statusObjectID)], toSection: .repliedTo)
|
||||
|
|
|
@ -19,6 +19,7 @@ final class ComposeViewModel {
|
|||
let context: AppContext
|
||||
let composeKind: ComposeStatusSection.ComposeKind
|
||||
let composeStatusAttribute = ComposeStatusItem.ComposeStatusAttribute()
|
||||
let isPollComposing = CurrentValueSubject<Bool, Never>(false)
|
||||
let activeAuthentication: CurrentValueSubject<MastodonAuthentication?, Never>
|
||||
let activeAuthenticationBox: CurrentValueSubject<AuthenticationService.MastodonAuthenticationBox?, Never>
|
||||
|
||||
|
@ -41,6 +42,8 @@ final class ComposeViewModel {
|
|||
let title: CurrentValueSubject<String, Never>
|
||||
let shouldDismiss = CurrentValueSubject<Bool, Never>(true)
|
||||
let isPublishBarButtonItemEnabled = CurrentValueSubject<Bool, Never>(false)
|
||||
let isMediaToolbarButtonEnabled = CurrentValueSubject<Bool, Never>(true)
|
||||
let isPollToolbarButtonEnabled = CurrentValueSubject<Bool, Never>(true)
|
||||
|
||||
// custom emojis
|
||||
let customEmojiViewModel = CurrentValueSubject<EmojiService.CustomEmojiViewModel?, Never>(nil)
|
||||
|
@ -48,6 +51,9 @@ final class ComposeViewModel {
|
|||
// attachment
|
||||
let attachmentServices = CurrentValueSubject<[MastodonAttachmentService], Never>([])
|
||||
|
||||
// polls
|
||||
let pollAttributes = CurrentValueSubject<[ComposeStatusItem.ComposePollAttribute], Never>([])
|
||||
|
||||
init(
|
||||
context: AppContext,
|
||||
composeKind: ComposeStatusSection.ComposeKind
|
||||
|
@ -137,24 +143,42 @@ final class ComposeViewModel {
|
|||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// bind snapshot and drive service upload state
|
||||
attachmentServices
|
||||
// bind snapshot
|
||||
Publishers.CombineLatest3(
|
||||
attachmentServices.eraseToAnyPublisher(),
|
||||
isPollComposing.eraseToAnyPublisher(),
|
||||
pollAttributes.eraseToAnyPublisher()
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] attachmentServices in
|
||||
.sink { [weak self] attachmentServices, isPollComposing, pollAttributes in
|
||||
guard let self = self else { return }
|
||||
guard let diffableDataSource = self.diffableDataSource else { return }
|
||||
var snapshot = diffableDataSource.snapshot()
|
||||
|
||||
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .attachment))
|
||||
var items: [ComposeStatusItem] = []
|
||||
var attachmentItems: [ComposeStatusItem] = []
|
||||
for attachmentService in attachmentServices {
|
||||
let item = ComposeStatusItem.attachment(attachmentService: attachmentService)
|
||||
items.append(item)
|
||||
attachmentItems.append(item)
|
||||
}
|
||||
snapshot.appendItems(attachmentItems, toSection: .attachment)
|
||||
|
||||
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .poll))
|
||||
if isPollComposing {
|
||||
var pollItems: [ComposeStatusItem] = []
|
||||
for pollAttribute in pollAttributes {
|
||||
let item = ComposeStatusItem.poll(attribute: pollAttribute)
|
||||
pollItems.append(item)
|
||||
}
|
||||
snapshot.appendItems(pollItems, toSection: .poll)
|
||||
if pollAttributes.count < 4 {
|
||||
snapshot.appendItems([ComposeStatusItem.newPoll], toSection: .poll)
|
||||
}
|
||||
}
|
||||
snapshot.appendItems(items, toSection: .attachment)
|
||||
|
||||
diffableDataSource.apply(snapshot)
|
||||
|
||||
// drive service upload state
|
||||
// make image upload in the queue
|
||||
for attachmentService in attachmentServices {
|
||||
// skip when prefix N task when task finish OR fail OR uploading
|
||||
|
@ -176,10 +200,34 @@ final class ComposeViewModel {
|
|||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
Publishers.CombineLatest(
|
||||
isPollComposing.eraseToAnyPublisher(),
|
||||
attachmentServices.eraseToAnyPublisher()
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] isPollComposing, attachmentServices in
|
||||
guard let self = self else { return }
|
||||
let shouldMediaDisable = isPollComposing || attachmentServices.count >= 4
|
||||
let shouldPollDisable = attachmentServices.count > 0
|
||||
|
||||
self.isMediaToolbarButtonEnabled.value = !shouldMediaDisable
|
||||
self.isPollToolbarButtonEnabled.value = !shouldPollDisable
|
||||
})
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ComposeViewModel {
|
||||
func createNewPollOptionIfPossible() {
|
||||
guard pollAttributes.value.count < 4 else { return }
|
||||
|
||||
let attribute = ComposeStatusItem.ComposePollAttribute()
|
||||
pollAttributes.value = pollAttributes.value + [attribute]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - MastodonAttachmentServiceDelegate
|
||||
extension ComposeViewModel: MastodonAttachmentServiceDelegate {
|
||||
func mastodonAttachmentService(_ service: MastodonAttachmentService, uploadStateDidChange state: MastodonAttachmentService.UploadState?) {
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
// Created by MainasuK Cirno on 2021-3-12.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
|
||||
protocol ComposeToolbarViewDelegate: class {
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, cameraButtonDidPressed sender: UIButton, mediaSelectionType: ComposeToolbarView.MediaSelectionType)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, gifButtonDidPressed sender: UIButton)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, atButtonDidPressed sender: UIButton)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, topicButtonDidPressed sender: UIButton)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, locationButtonDidPressed sender: UIButton)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, pollButtonDidPressed sender: UIButton)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, emojiButtonDidPressed sender: UIButton)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, contentWarningButtonDidPressed sender: UIButton)
|
||||
func composeToolbarView(_ composeToolbarView: ComposeToolbarView, visibilityButtonDidPressed sender: UIButton)
|
||||
}
|
||||
|
||||
final class ComposeToolbarView: UIView {
|
||||
|
@ -102,10 +103,10 @@ extension ComposeToolbarView {
|
|||
|
||||
mediaButton.menu = createMediaContextMenu()
|
||||
mediaButton.showsMenuAsPrimaryAction = true
|
||||
pollButton.addTarget(self, action: #selector(ComposeToolbarView.gifButtonDidPressed(_:)), for: .touchUpInside)
|
||||
emojiButton.addTarget(self, action: #selector(ComposeToolbarView.atButtonDidPressed(_:)), for: .touchUpInside)
|
||||
contentWarningButton.addTarget(self, action: #selector(ComposeToolbarView.topicButtonDidPressed(_:)), for: .touchUpInside)
|
||||
visibilityButton.addTarget(self, action: #selector(ComposeToolbarView.locationButtonDidPressed(_:)), for: .touchUpInside)
|
||||
pollButton.addTarget(self, action: #selector(ComposeToolbarView.pollButtonDidPressed(_:)), for: .touchUpInside)
|
||||
emojiButton.addTarget(self, action: #selector(ComposeToolbarView.emojiButtonDidPressed(_:)), for: .touchUpInside)
|
||||
contentWarningButton.addTarget(self, action: #selector(ComposeToolbarView.contentWarningButtonDidPressed(_:)), for: .touchUpInside)
|
||||
visibilityButton.addTarget(self, action: #selector(ComposeToolbarView.visibilityButtonDidPressed(_:)), for: .touchUpInside)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,18 +132,21 @@ extension ComposeToolbarView {
|
|||
var children: [UIMenuElement] = []
|
||||
let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .photoLibaray", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
self.delegate?.composeToolbarView(self, cameraButtonDidPressed: self.mediaButton, mediaSelectionType: .photoLibrary)
|
||||
}
|
||||
children.append(photoLibraryAction)
|
||||
if UIImagePickerController.isSourceTypeAvailable(.camera) {
|
||||
let cameraAction = UIAction(title: L10n.Scene.Compose.MediaSelection.camera, image: UIImage(systemName: "camera"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .camera", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
self.delegate?.composeToolbarView(self, cameraButtonDidPressed: self.mediaButton, mediaSelectionType: .camera)
|
||||
})
|
||||
children.append(cameraAction)
|
||||
}
|
||||
let browseAction = UIAction(title: L10n.Scene.Compose.MediaSelection.browse, image: UIImage(systemName: "ellipsis"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: mediaSelectionType: .browse", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
self.delegate?.composeToolbarView(self, cameraButtonDidPressed: self.mediaButton, mediaSelectionType: .browse)
|
||||
}
|
||||
children.append(browseAction)
|
||||
|
@ -155,20 +159,24 @@ extension ComposeToolbarView {
|
|||
|
||||
extension ComposeToolbarView {
|
||||
|
||||
@objc private func gifButtonDidPressed(_ sender: UIButton) {
|
||||
delegate?.composeToolbarView(self, gifButtonDidPressed: sender)
|
||||
@objc private func pollButtonDidPressed(_ sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
delegate?.composeToolbarView(self, pollButtonDidPressed: sender)
|
||||
}
|
||||
|
||||
@objc private func atButtonDidPressed(_ sender: UIButton) {
|
||||
delegate?.composeToolbarView(self, atButtonDidPressed: sender)
|
||||
@objc private func emojiButtonDidPressed(_ sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
delegate?.composeToolbarView(self, emojiButtonDidPressed: sender)
|
||||
}
|
||||
|
||||
@objc private func topicButtonDidPressed(_ sender: UIButton) {
|
||||
delegate?.composeToolbarView(self, topicButtonDidPressed: sender)
|
||||
@objc private func contentWarningButtonDidPressed(_ sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
delegate?.composeToolbarView(self, contentWarningButtonDidPressed: sender)
|
||||
}
|
||||
|
||||
@objc private func locationButtonDidPressed(_ sender: UIButton) {
|
||||
delegate?.composeToolbarView(self, locationButtonDidPressed: sender)
|
||||
@objc private func visibilityButtonDidPressed(_ sender: UIButton) {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
delegate?.composeToolbarView(self, visibilityButtonDidPressed: sender)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
//
|
||||
// PollOptionView.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
final class PollOptionView: UIView {
|
||||
|
||||
static let height: CGFloat = optionHeight + 2 * verticalMargin
|
||||
static let optionHeight: CGFloat = 44
|
||||
static let verticalMargin: CGFloat = 5
|
||||
static let checkmarkImageSize = CGSize(width: 26, height: 26)
|
||||
|
||||
private var viewStateDisposeBag = Set<AnyCancellable>()
|
||||
|
||||
let roundedBackgroundView = UIView()
|
||||
let voteProgressStripView: StripProgressView = {
|
||||
let view = StripProgressView()
|
||||
view.tintColor = Asset.Colors.Background.Poll.highlight.color
|
||||
return view
|
||||
}()
|
||||
|
||||
let checkmarkBackgroundView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .systemBackground
|
||||
return view
|
||||
}()
|
||||
|
||||
let checkmarkImageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
let image = UIImage(systemName: "checkmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 14, weight: .bold))!
|
||||
imageView.image = image.withRenderingMode(.alwaysTemplate)
|
||||
imageView.tintColor = Asset.Colors.Button.normal.color
|
||||
return imageView
|
||||
}()
|
||||
|
||||
let plusCircleImageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
let image = Asset.Circles.plusCircle.image
|
||||
imageView.image = image.withRenderingMode(.alwaysTemplate)
|
||||
imageView.tintColor = Asset.Colors.Button.normal.color
|
||||
return imageView
|
||||
}()
|
||||
|
||||
let optionTextField: DeleteBackwardResponseTextField = {
|
||||
let textField = DeleteBackwardResponseTextField()
|
||||
textField.font = .systemFont(ofSize: 15, weight: .medium)
|
||||
textField.textColor = Asset.Colors.Label.primary.color
|
||||
textField.text = "Option"
|
||||
textField.textAlignment = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? .left : .right
|
||||
return textField
|
||||
}()
|
||||
|
||||
let optionLabelMiddlePaddingView = UIView()
|
||||
|
||||
let optionPercentageLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 13, weight: .regular)
|
||||
label.textColor = Asset.Colors.Label.primary.color
|
||||
label.text = "50%"
|
||||
label.textAlignment = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? .right : .left
|
||||
return label
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PollOptionView {
|
||||
private func _init() {
|
||||
roundedBackgroundView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
||||
|
||||
roundedBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(roundedBackgroundView)
|
||||
NSLayoutConstraint.activate([
|
||||
roundedBackgroundView.topAnchor.constraint(equalTo: topAnchor, constant: 5),
|
||||
roundedBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
roundedBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
bottomAnchor.constraint(equalTo: roundedBackgroundView.bottomAnchor, constant: 5),
|
||||
roundedBackgroundView.heightAnchor.constraint(equalToConstant: PollOptionView.optionHeight).priority(.defaultHigh),
|
||||
])
|
||||
|
||||
voteProgressStripView.translatesAutoresizingMaskIntoConstraints = false
|
||||
roundedBackgroundView.addSubview(voteProgressStripView)
|
||||
NSLayoutConstraint.activate([
|
||||
voteProgressStripView.topAnchor.constraint(equalTo: roundedBackgroundView.topAnchor),
|
||||
voteProgressStripView.leadingAnchor.constraint(equalTo: roundedBackgroundView.leadingAnchor),
|
||||
voteProgressStripView.trailingAnchor.constraint(equalTo: roundedBackgroundView.trailingAnchor),
|
||||
voteProgressStripView.bottomAnchor.constraint(equalTo: roundedBackgroundView.bottomAnchor),
|
||||
])
|
||||
|
||||
checkmarkBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
roundedBackgroundView.addSubview(checkmarkBackgroundView)
|
||||
NSLayoutConstraint.activate([
|
||||
checkmarkBackgroundView.topAnchor.constraint(equalTo: roundedBackgroundView.topAnchor, constant: 9),
|
||||
checkmarkBackgroundView.leadingAnchor.constraint(equalTo: roundedBackgroundView.leadingAnchor, constant: 9),
|
||||
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),
|
||||
])
|
||||
|
||||
checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
checkmarkBackgroundView.addSubview(checkmarkImageView)
|
||||
NSLayoutConstraint.activate([
|
||||
checkmarkImageView.topAnchor.constraint(equalTo: checkmarkBackgroundView.topAnchor, constant: 5),
|
||||
checkmarkImageView.leadingAnchor.constraint(equalTo: checkmarkBackgroundView.leadingAnchor, constant: 5),
|
||||
checkmarkBackgroundView.trailingAnchor.constraint(equalTo: checkmarkImageView.trailingAnchor, constant: 5),
|
||||
checkmarkBackgroundView.bottomAnchor.constraint(equalTo: checkmarkImageView.bottomAnchor, constant: 5),
|
||||
])
|
||||
|
||||
plusCircleImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(plusCircleImageView)
|
||||
NSLayoutConstraint.activate([
|
||||
plusCircleImageView.topAnchor.constraint(equalTo: checkmarkBackgroundView.topAnchor),
|
||||
plusCircleImageView.leadingAnchor.constraint(equalTo: checkmarkBackgroundView.leadingAnchor),
|
||||
plusCircleImageView.trailingAnchor.constraint(equalTo: checkmarkBackgroundView.trailingAnchor),
|
||||
plusCircleImageView.bottomAnchor.constraint(equalTo: checkmarkBackgroundView.bottomAnchor),
|
||||
])
|
||||
|
||||
optionTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||
roundedBackgroundView.addSubview(optionTextField)
|
||||
NSLayoutConstraint.activate([
|
||||
optionTextField.leadingAnchor.constraint(equalTo: checkmarkBackgroundView.trailingAnchor, constant: 14),
|
||||
optionTextField.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor),
|
||||
optionTextField.widthAnchor.constraint(greaterThanOrEqualToConstant: 44).priority(.defaultHigh),
|
||||
])
|
||||
|
||||
optionLabelMiddlePaddingView.translatesAutoresizingMaskIntoConstraints = false
|
||||
roundedBackgroundView.addSubview(optionLabelMiddlePaddingView)
|
||||
NSLayoutConstraint.activate([
|
||||
optionLabelMiddlePaddingView.leadingAnchor.constraint(equalTo: optionTextField.trailingAnchor),
|
||||
optionLabelMiddlePaddingView.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor),
|
||||
optionLabelMiddlePaddingView.heightAnchor.constraint(equalToConstant: 4).priority(.defaultHigh),
|
||||
optionLabelMiddlePaddingView.widthAnchor.constraint(greaterThanOrEqualToConstant: 8).priority(.defaultLow),
|
||||
])
|
||||
optionLabelMiddlePaddingView.setContentHuggingPriority(.required - 1, for: .horizontal)
|
||||
optionLabelMiddlePaddingView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
|
||||
optionPercentageLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
roundedBackgroundView.addSubview(optionPercentageLabel)
|
||||
NSLayoutConstraint.activate([
|
||||
optionPercentageLabel.leadingAnchor.constraint(equalTo: optionLabelMiddlePaddingView.trailingAnchor),
|
||||
roundedBackgroundView.trailingAnchor.constraint(equalTo: optionPercentageLabel.trailingAnchor, constant: 18),
|
||||
optionPercentageLabel.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor),
|
||||
])
|
||||
optionPercentageLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
optionPercentageLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
|
||||
|
||||
plusCircleImageView.isHidden = true
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
updateCornerRadius()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PollOptionView {
|
||||
private func updateCornerRadius() {
|
||||
roundedBackgroundView.layer.masksToBounds = true
|
||||
roundedBackgroundView.layer.cornerRadius = PollOptionView.optionHeight * 0.5
|
||||
roundedBackgroundView.layer.cornerCurve = .circular
|
||||
|
||||
checkmarkBackgroundView.layer.masksToBounds = true
|
||||
checkmarkBackgroundView.layer.cornerRadius = PollOptionView.checkmarkImageSize.width * 0.5
|
||||
checkmarkBackgroundView.layer.cornerCurve = .circular
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(SwiftUI) && DEBUG
|
||||
import SwiftUI
|
||||
|
||||
struct PollOptionView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
UIViewPreview(width: 375) {
|
||||
PollOptionView()
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 100))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -10,55 +10,9 @@ import Combine
|
|||
|
||||
final class PollOptionTableViewCell: UITableViewCell {
|
||||
|
||||
static let height: CGFloat = optionHeight + 2 * verticalMargin
|
||||
static let optionHeight: CGFloat = 44
|
||||
static let verticalMargin: CGFloat = 5
|
||||
static let checkmarkImageSize = CGSize(width: 26, height: 26)
|
||||
|
||||
private var viewStateDisposeBag = Set<AnyCancellable>()
|
||||
let pollOptionView = PollOptionView()
|
||||
var attribute: PollItem.Attribute?
|
||||
|
||||
let roundedBackgroundView = UIView()
|
||||
let voteProgressStripView: StripProgressView = {
|
||||
let view = StripProgressView()
|
||||
view.tintColor = Asset.Colors.Background.Poll.highlight.color
|
||||
return view
|
||||
}()
|
||||
|
||||
let checkmarkBackgroundView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .systemBackground
|
||||
return view
|
||||
}()
|
||||
|
||||
let checkmarkImageView: UIView = {
|
||||
let imageView = UIImageView()
|
||||
let image = UIImage(systemName: "checkmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 14, weight: .bold))!
|
||||
imageView.image = image.withRenderingMode(.alwaysTemplate)
|
||||
imageView.tintColor = Asset.Colors.Button.normal.color
|
||||
return imageView
|
||||
}()
|
||||
|
||||
let optionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 15, weight: .medium)
|
||||
label.textColor = Asset.Colors.Label.primary.color
|
||||
label.text = "Option"
|
||||
label.textAlignment = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? .left : .right
|
||||
return label
|
||||
}()
|
||||
|
||||
let optionLabelMiddlePaddingView = UIView()
|
||||
|
||||
let optionPercentageLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .systemFont(ofSize: 13, weight: .regular)
|
||||
label.textColor = Asset.Colors.Label.primary.color
|
||||
label.text = "50%"
|
||||
label.textAlignment = UIApplication.shared.userInterfaceLayoutDirection == .leftToRight ? .right : .left
|
||||
return label
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
_init()
|
||||
|
@ -76,7 +30,7 @@ final class PollOptionTableViewCell: UITableViewCell {
|
|||
switch voteState {
|
||||
case .hidden:
|
||||
let color = Asset.Colors.Background.systemGroupedBackground.color
|
||||
self.roundedBackgroundView.backgroundColor = isHighlighted ? color.withAlphaComponent(0.8) : color
|
||||
pollOptionView.roundedBackgroundView.backgroundColor = isHighlighted ? color.withAlphaComponent(0.8) : color
|
||||
case .reveal:
|
||||
break
|
||||
}
|
||||
|
@ -89,7 +43,7 @@ final class PollOptionTableViewCell: UITableViewCell {
|
|||
switch voteState {
|
||||
case .hidden:
|
||||
let color = Asset.Colors.Background.systemGroupedBackground.color
|
||||
self.roundedBackgroundView.backgroundColor = isHighlighted ? color.withAlphaComponent(0.8) : color
|
||||
pollOptionView.roundedBackgroundView.backgroundColor = isHighlighted ? color.withAlphaComponent(0.8) : color
|
||||
case .reveal:
|
||||
break
|
||||
}
|
||||
|
@ -102,125 +56,55 @@ extension PollOptionTableViewCell {
|
|||
private func _init() {
|
||||
selectionStyle = .none
|
||||
backgroundColor = .clear
|
||||
roundedBackgroundView.backgroundColor = Asset.Colors.Background.systemGroupedBackground.color
|
||||
pollOptionView.optionTextField.isUserInteractionEnabled = false
|
||||
|
||||
roundedBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(roundedBackgroundView)
|
||||
pollOptionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(pollOptionView)
|
||||
NSLayoutConstraint.activate([
|
||||
roundedBackgroundView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5),
|
||||
roundedBackgroundView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
roundedBackgroundView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
contentView.bottomAnchor.constraint(equalTo: roundedBackgroundView.bottomAnchor, constant: 5),
|
||||
roundedBackgroundView.heightAnchor.constraint(equalToConstant: PollOptionTableViewCell.optionHeight).priority(.defaultHigh),
|
||||
pollOptionView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
pollOptionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
pollOptionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
||||
pollOptionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
])
|
||||
|
||||
voteProgressStripView.translatesAutoresizingMaskIntoConstraints = false
|
||||
roundedBackgroundView.addSubview(voteProgressStripView)
|
||||
NSLayoutConstraint.activate([
|
||||
voteProgressStripView.topAnchor.constraint(equalTo: roundedBackgroundView.topAnchor),
|
||||
voteProgressStripView.leadingAnchor.constraint(equalTo: roundedBackgroundView.leadingAnchor),
|
||||
voteProgressStripView.trailingAnchor.constraint(equalTo: roundedBackgroundView.trailingAnchor),
|
||||
voteProgressStripView.bottomAnchor.constraint(equalTo: roundedBackgroundView.bottomAnchor),
|
||||
])
|
||||
|
||||
checkmarkBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
roundedBackgroundView.addSubview(checkmarkBackgroundView)
|
||||
NSLayoutConstraint.activate([
|
||||
checkmarkBackgroundView.topAnchor.constraint(equalTo: roundedBackgroundView.topAnchor, constant: 9),
|
||||
checkmarkBackgroundView.leadingAnchor.constraint(equalTo: roundedBackgroundView.leadingAnchor, constant: 9),
|
||||
roundedBackgroundView.bottomAnchor.constraint(equalTo: checkmarkBackgroundView.bottomAnchor, constant: 9),
|
||||
checkmarkBackgroundView.widthAnchor.constraint(equalToConstant: PollOptionTableViewCell.checkmarkImageSize.width).priority(.defaultHigh),
|
||||
checkmarkBackgroundView.heightAnchor.constraint(equalToConstant: PollOptionTableViewCell.checkmarkImageSize.height).priority(.defaultHigh),
|
||||
])
|
||||
|
||||
checkmarkImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
checkmarkBackgroundView.addSubview(checkmarkImageView)
|
||||
NSLayoutConstraint.activate([
|
||||
checkmarkImageView.topAnchor.constraint(equalTo: checkmarkBackgroundView.topAnchor, constant: 5),
|
||||
checkmarkImageView.leadingAnchor.constraint(equalTo: checkmarkBackgroundView.leadingAnchor, constant: 5),
|
||||
checkmarkBackgroundView.trailingAnchor.constraint(equalTo: checkmarkImageView.trailingAnchor, constant: 5),
|
||||
checkmarkBackgroundView.bottomAnchor.constraint(equalTo: checkmarkImageView.bottomAnchor, constant: 5),
|
||||
])
|
||||
|
||||
optionLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
roundedBackgroundView.addSubview(optionLabel)
|
||||
NSLayoutConstraint.activate([
|
||||
optionLabel.leadingAnchor.constraint(equalTo: checkmarkBackgroundView.trailingAnchor, constant: 14),
|
||||
optionLabel.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor),
|
||||
])
|
||||
|
||||
optionLabelMiddlePaddingView.translatesAutoresizingMaskIntoConstraints = false
|
||||
roundedBackgroundView.addSubview(optionLabelMiddlePaddingView)
|
||||
NSLayoutConstraint.activate([
|
||||
optionLabelMiddlePaddingView.leadingAnchor.constraint(equalTo: optionLabel.trailingAnchor),
|
||||
optionLabelMiddlePaddingView.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor),
|
||||
optionLabelMiddlePaddingView.heightAnchor.constraint(equalToConstant: 4).priority(.defaultHigh),
|
||||
optionLabelMiddlePaddingView.widthAnchor.constraint(greaterThanOrEqualToConstant: 8).priority(.defaultLow),
|
||||
])
|
||||
optionLabelMiddlePaddingView.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
|
||||
optionPercentageLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
roundedBackgroundView.addSubview(optionPercentageLabel)
|
||||
NSLayoutConstraint.activate([
|
||||
optionPercentageLabel.leadingAnchor.constraint(equalTo: optionLabelMiddlePaddingView.trailingAnchor),
|
||||
roundedBackgroundView.trailingAnchor.constraint(equalTo: optionPercentageLabel.trailingAnchor, constant: 18),
|
||||
optionPercentageLabel.centerYAnchor.constraint(equalTo: roundedBackgroundView.centerYAnchor),
|
||||
])
|
||||
optionPercentageLabel.setContentHuggingPriority(.required - 1, for: .horizontal)
|
||||
optionPercentageLabel.setContentCompressionResistancePriority(.required - 1, for: .horizontal)
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
updateCornerRadius()
|
||||
updateTextAppearance()
|
||||
}
|
||||
|
||||
private func updateCornerRadius() {
|
||||
roundedBackgroundView.layer.masksToBounds = true
|
||||
roundedBackgroundView.layer.cornerRadius = PollOptionTableViewCell.optionHeight * 0.5
|
||||
roundedBackgroundView.layer.cornerCurve = .circular
|
||||
|
||||
checkmarkBackgroundView.layer.masksToBounds = true
|
||||
checkmarkBackgroundView.layer.cornerRadius = PollOptionTableViewCell.checkmarkImageSize.width * 0.5
|
||||
checkmarkBackgroundView.layer.cornerCurve = .circular
|
||||
}
|
||||
|
||||
func updateTextAppearance() {
|
||||
guard let voteState = attribute?.voteState else {
|
||||
optionLabel.textColor = Asset.Colors.Label.primary.color
|
||||
optionLabel.layer.removeShadow()
|
||||
pollOptionView.optionTextField.textColor = Asset.Colors.Label.primary.color
|
||||
pollOptionView.optionTextField.layer.removeShadow()
|
||||
return
|
||||
}
|
||||
|
||||
switch voteState {
|
||||
case .hidden:
|
||||
optionLabel.textColor = Asset.Colors.Label.primary.color
|
||||
optionLabel.layer.removeShadow()
|
||||
pollOptionView.optionTextField.textColor = Asset.Colors.Label.primary.color
|
||||
pollOptionView.optionTextField.layer.removeShadow()
|
||||
case .reveal(_, let percentage, _):
|
||||
if CGFloat(percentage) * voteProgressStripView.frame.width > optionLabelMiddlePaddingView.frame.minX {
|
||||
optionLabel.textColor = .white
|
||||
optionLabel.layer.setupShadow(x: 0, y: 0, blur: 4, spread: 0)
|
||||
if CGFloat(percentage) * pollOptionView.voteProgressStripView.frame.width > pollOptionView.optionLabelMiddlePaddingView.frame.minX {
|
||||
pollOptionView.optionTextField.textColor = .white
|
||||
pollOptionView.optionTextField.layer.setupShadow(x: 0, y: 0, blur: 4, spread: 0)
|
||||
} else {
|
||||
optionLabel.textColor = Asset.Colors.Label.primary.color
|
||||
optionLabel.layer.removeShadow()
|
||||
pollOptionView.optionTextField.textColor = Asset.Colors.Label.primary.color
|
||||
pollOptionView.optionTextField.layer.removeShadow()
|
||||
}
|
||||
|
||||
if CGFloat(percentage) * voteProgressStripView.frame.width > optionLabelMiddlePaddingView.frame.maxX {
|
||||
optionPercentageLabel.textColor = .white
|
||||
optionPercentageLabel.layer.setupShadow(x: 0, y: 0, blur: 4, spread: 0)
|
||||
if CGFloat(percentage) * pollOptionView.voteProgressStripView.frame.width > pollOptionView.optionLabelMiddlePaddingView.frame.maxX {
|
||||
pollOptionView.optionPercentageLabel.textColor = .white
|
||||
pollOptionView.optionPercentageLabel.layer.setupShadow(x: 0, y: 0, blur: 4, spread: 0)
|
||||
} else {
|
||||
optionPercentageLabel.textColor = Asset.Colors.Label.primary.color
|
||||
optionPercentageLabel.layer.removeShadow()
|
||||
pollOptionView.optionPercentageLabel.textColor = Asset.Colors.Label.primary.color
|
||||
pollOptionView.optionPercentageLabel.layer.removeShadow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
updateTextAppearance()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#if canImport(SwiftUI) && DEBUG
|
||||
import SwiftUI
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// DeleteBackwardResponseTextField.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by MainasuK Cirno on 2021-3-23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol DeleteBackwardResponseTextFieldDelegate: class {
|
||||
func deleteBackwardResponseTextField(_ textField: DeleteBackwardResponseTextField, textBeforeDelete: String?)
|
||||
}
|
||||
|
||||
final class DeleteBackwardResponseTextField: UITextField {
|
||||
|
||||
weak var deleteBackwardDelegate: DeleteBackwardResponseTextFieldDelegate?
|
||||
|
||||
override func deleteBackward() {
|
||||
let text = self.text
|
||||
super.deleteBackward()
|
||||
deleteBackwardDelegate?.deleteBackwardResponseTextField(self, textBeforeDelete: text)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue