Compare commits
7 Commits
ae8191ca0e
...
b37e5fffbf
Author | SHA1 | Date |
---|---|---|
Shadowfacts | b37e5fffbf | |
Shadowfacts | 8c27a9368f | |
Shadowfacts | 735659dee6 | |
Shadowfacts | bf02b185ed | |
Shadowfacts | 4ccf5d21a4 | |
Shadowfacts | 9ac1c43511 | |
Shadowfacts | 76b9496fe6 |
|
@ -24,7 +24,9 @@ public final class CollapseState: Sendable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func copy() -> CollapseState {
|
public func copy() -> CollapseState {
|
||||||
return CollapseState(collapsible: self.collapsible, collapsed: self.collapsed)
|
let new = CollapseState(collapsible: self.collapsible, collapsed: self.collapsed)
|
||||||
|
new.statusPropertiesHash = self.statusPropertiesHash
|
||||||
|
return new
|
||||||
}
|
}
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
|
|
@ -301,6 +301,7 @@
|
||||||
D6D79F2B2A0D5D5C00AB2315 /* StatusEditCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2A2A0D5D5C00AB2315 /* StatusEditCollectionViewCell.swift */; };
|
D6D79F2B2A0D5D5C00AB2315 /* StatusEditCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2A2A0D5D5C00AB2315 /* StatusEditCollectionViewCell.swift */; };
|
||||||
D6D79F2D2A0D61B400AB2315 /* StatusEditContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2C2A0D61B400AB2315 /* StatusEditContentTextView.swift */; };
|
D6D79F2D2A0D61B400AB2315 /* StatusEditContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2C2A0D61B400AB2315 /* StatusEditContentTextView.swift */; };
|
||||||
D6D79F2F2A0D6A7F00AB2315 /* StatusEditPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */; };
|
D6D79F2F2A0D6A7F00AB2315 /* StatusEditPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */; };
|
||||||
|
D6D79F532A0FFE3200AB2315 /* ToggleableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */; };
|
||||||
D6D94955298963A900C59229 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D94954298963A900C59229 /* Colors.swift */; };
|
D6D94955298963A900C59229 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D94954298963A900C59229 /* Colors.swift */; };
|
||||||
D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; };
|
D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; };
|
||||||
D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; };
|
D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; };
|
||||||
|
@ -700,6 +701,7 @@
|
||||||
D6D79F2A2A0D5D5C00AB2315 /* StatusEditCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6D79F2A2A0D5D5C00AB2315 /* StatusEditCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6D79F2C2A0D61B400AB2315 /* StatusEditContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditContentTextView.swift; sourceTree = "<group>"; };
|
D6D79F2C2A0D61B400AB2315 /* StatusEditContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditContentTextView.swift; sourceTree = "<group>"; };
|
||||||
D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditPollView.swift; sourceTree = "<group>"; };
|
D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditPollView.swift; sourceTree = "<group>"; };
|
||||||
|
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleableButton.swift; sourceTree = "<group>"; };
|
||||||
D6D94954298963A900C59229 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
D6D94954298963A900C59229 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
||||||
D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = "<group>"; };
|
D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1371,6 +1373,7 @@
|
||||||
D65B4B69297777D900DABDFB /* StatusNotFoundView.swift */,
|
D65B4B69297777D900DABDFB /* StatusNotFoundView.swift */,
|
||||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
|
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
|
||||||
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */,
|
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */,
|
||||||
|
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */,
|
||||||
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
|
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
|
||||||
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */,
|
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */,
|
||||||
D6A3BC872321F78000FD64D5 /* Account Cell */,
|
D6A3BC872321F78000FD64D5 /* Account Cell */,
|
||||||
|
@ -1945,6 +1948,7 @@
|
||||||
D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */,
|
D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */,
|
||||||
D6ADB6EC28EA73CB009924AB /* StatusContentContainer.swift in Sources */,
|
D6ADB6EC28EA73CB009924AB /* StatusContentContainer.swift in Sources */,
|
||||||
D6969E9E240C81B9002843CE /* NSTextAttachment+Emoji.swift in Sources */,
|
D6969E9E240C81B9002843CE /* NSTextAttachment+Emoji.swift in Sources */,
|
||||||
|
D6D79F532A0FFE3200AB2315 /* ToggleableButton.swift in Sources */,
|
||||||
D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */,
|
D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */,
|
||||||
D6ADB6F028ED1F25009924AB /* CachedImageView.swift in Sources */,
|
D6ADB6F028ED1F25009924AB /* CachedImageView.swift in Sources */,
|
||||||
D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */,
|
D693DE5923FE24310061E07D /* InteractivePushTransition.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1430"
|
||||||
|
wasCreatedForAppExtension = "YES"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D6A4531229EF64BA00032932"
|
||||||
|
BuildableName = "ShareExtension.appex"
|
||||||
|
BlueprintName = "ShareExtension"
|
||||||
|
ReferencedContainer = "container:Tusker.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D6D4DDCB212518A000E1C4BB"
|
||||||
|
BuildableName = "Tusker.app"
|
||||||
|
BlueprintName = "Tusker"
|
||||||
|
ReferencedContainer = "container:Tusker.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "1"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D6A4531229EF64BA00032932"
|
||||||
|
BuildableName = "ShareExtension.appex"
|
||||||
|
BlueprintName = "ShareExtension"
|
||||||
|
ReferencedContainer = "container:Tusker.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "D6D4DDCB212518A000E1C4BB"
|
||||||
|
BuildableName = "Tusker.app"
|
||||||
|
BlueprintName = "Tusker"
|
||||||
|
ReferencedContainer = "container:Tusker.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
<AdditionalOption
|
||||||
|
key = "MallocStackLogging"
|
||||||
|
value = ""
|
||||||
|
isEnabled = "YES">
|
||||||
|
</AdditionalOption>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<RemoteRunnable
|
||||||
|
runnableDebuggingMode = "1"
|
||||||
|
BundleIdentifier = "com.apple.mobileslideshow"
|
||||||
|
RemotePath = "/Applications/MobileSlideShow.app">
|
||||||
|
</RemoteRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
|
@ -88,6 +88,10 @@
|
||||||
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
|
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
|
||||||
isEnabled = "YES">
|
isEnabled = "YES">
|
||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
|
<CommandLineArgument
|
||||||
|
argument = "-com.apple.CoreData.CloudKitDebug 0"
|
||||||
|
isEnabled = "YES">
|
||||||
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = "-UIFocusLoggingEnabled YES"
|
argument = "-UIFocusLoggingEnabled YES"
|
||||||
isEnabled = "NO">
|
isEnabled = "NO">
|
||||||
|
|
|
@ -19,7 +19,7 @@ extension StatusEdit: CollapseStateResolving {}
|
||||||
extension CollapseState {
|
extension CollapseState {
|
||||||
|
|
||||||
func resolveFor(status: CollapseStateResolving, height: () -> CGFloat, textLength: Int? = nil) -> Bool {
|
func resolveFor(status: CollapseStateResolving, height: () -> CGFloat, textLength: Int? = nil) -> Bool {
|
||||||
lazy var newHash = hashStatusProperties(status: status)
|
let newHash = hashStatusProperties(status: status)
|
||||||
guard unknown || statusPropertiesHash != newHash else {
|
guard unknown || statusPropertiesHash != newHash else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ protocol Configurable {
|
||||||
func configure(_ closure: (T) -> Void) -> T
|
func configure(_ closure: (T) -> Void) -> T
|
||||||
}
|
}
|
||||||
extension Configurable where Self: UIView {
|
extension Configurable where Self: UIView {
|
||||||
|
@inline(__always)
|
||||||
func configure(_ closure: (Self) -> Void) -> Self {
|
func configure(_ closure: (Self) -> Void) -> Self {
|
||||||
closure(self)
|
closure(self)
|
||||||
return self
|
return self
|
||||||
|
|
|
@ -117,8 +117,8 @@ class StatusEditCollectionViewCell: UICollectionViewListCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = state.resolveFor(status: edit, height: {
|
_ = state.resolveFor(status: edit, height: {
|
||||||
layoutIfNeeded()
|
let width = self.bounds.width - 2*16
|
||||||
return contentContainer.visibleSubviewHeight
|
return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: width)
|
||||||
})
|
})
|
||||||
collapseButton.isHidden = !state.collapsible!
|
collapseButton.isHidden = !state.collapsible!
|
||||||
contentContainer.setCollapsed(state.collapsed!)
|
contentContainer.setCollapsed(state.collapsed!)
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
class StatusEditPollView: UIStackView {
|
class StatusEditPollView: UIStackView, StatusContentPollView {
|
||||||
|
|
||||||
|
private var titleLabels: [EmojiLabel] = []
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
@ -25,6 +27,7 @@ class StatusEditPollView: UIStackView {
|
||||||
|
|
||||||
func updateUI(poll: StatusEdit.Poll?, emojis: [Emoji]) {
|
func updateUI(poll: StatusEdit.Poll?, emojis: [Emoji]) {
|
||||||
arrangedSubviews.forEach { $0.removeFromSuperview() }
|
arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||||
|
titleLabels = []
|
||||||
|
|
||||||
for option in poll?.options ?? [] {
|
for option in poll?.options ?? [] {
|
||||||
// the edit poll doesn't actually include the multiple value
|
// the edit poll doesn't actually include the multiple value
|
||||||
|
@ -33,6 +36,7 @@ class StatusEditPollView: UIStackView {
|
||||||
let label = EmojiLabel()
|
let label = EmojiLabel()
|
||||||
label.text = option.title
|
label.text = option.title
|
||||||
label.setEmojis(emojis, identifier: Optional<String>.none)
|
label.setEmojis(emojis, identifier: Optional<String>.none)
|
||||||
|
titleLabels.append(label)
|
||||||
let stack = UIStackView(arrangedSubviews: [
|
let stack = UIStackView(arrangedSubviews: [
|
||||||
icon,
|
icon,
|
||||||
label,
|
label,
|
||||||
|
@ -43,5 +47,15 @@ class StatusEditPollView: UIStackView {
|
||||||
addArrangedSubview(stack)
|
addArrangedSubview(stack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
|
||||||
|
var height: CGFloat = 0
|
||||||
|
height += CGFloat(arrangedSubviews.count - 1) * 4
|
||||||
|
let labelWidth = effectiveWidth /* checkbox size: */ - 20 /* spacing: */ - 8
|
||||||
|
for titleLabel in titleLabels {
|
||||||
|
height += titleLabel.sizeThatFits(CGSize(width: labelWidth, height: UIView.layoutFittingCompressedSize.height)).height
|
||||||
|
}
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,15 @@ class AttachmentsContainerView: UIView {
|
||||||
let attachmentStacks: NSHashTable<UIStackView> = .weakObjects()
|
let attachmentStacks: NSHashTable<UIStackView> = .weakObjects()
|
||||||
var moreView: UIView?
|
var moreView: UIView?
|
||||||
private var aspectRatioConstraint: NSLayoutConstraint?
|
private var aspectRatioConstraint: NSLayoutConstraint?
|
||||||
|
private(set) var aspectRatio: CGFloat = 16/9 {
|
||||||
|
didSet {
|
||||||
|
if aspectRatio != aspectRatioConstraint?.multiplier {
|
||||||
|
aspectRatioConstraint?.isActive = false
|
||||||
|
aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: aspectRatio)
|
||||||
|
aspectRatioConstraint!.isActive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var blurView: UIVisualEffectView?
|
var blurView: UIVisualEffectView?
|
||||||
var hideButtonView: UIVisualEffectView?
|
var hideButtonView: UIVisualEffectView?
|
||||||
|
@ -93,7 +102,8 @@ class AttachmentsContainerView: UIView {
|
||||||
fillView(attachmentView)
|
fillView(attachmentView)
|
||||||
sendSubviewToBack(attachmentView)
|
sendSubviewToBack(attachmentView)
|
||||||
accessibilityElements.append(attachmentView)
|
accessibilityElements.append(attachmentView)
|
||||||
if let attachmentAspectRatio = attachmentView.attachmentAspectRatio {
|
if Preferences.shared.showUncroppedMediaInline,
|
||||||
|
let attachmentAspectRatio = attachmentView.attachmentAspectRatio {
|
||||||
aspectRatio = attachmentAspectRatio
|
aspectRatio = attachmentAspectRatio
|
||||||
}
|
}
|
||||||
case 2:
|
case 2:
|
||||||
|
@ -266,18 +276,7 @@ class AttachmentsContainerView: UIView {
|
||||||
accessibilityElements.append(moreView)
|
accessibilityElements.append(moreView)
|
||||||
}
|
}
|
||||||
|
|
||||||
if Preferences.shared.showUncroppedMediaInline {
|
self.aspectRatio = aspectRatio
|
||||||
if aspectRatioConstraint?.multiplier != aspectRatio {
|
|
||||||
aspectRatioConstraint?.isActive = false
|
|
||||||
aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: aspectRatio)
|
|
||||||
aspectRatioConstraint!.isActive = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if aspectRatioConstraint == nil {
|
|
||||||
aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: 16/9)
|
|
||||||
aspectRatioConstraint!.isActive = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.isHidden = true
|
self.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,24 +11,27 @@ import Pachyderm
|
||||||
|
|
||||||
class PollOptionView: UIView {
|
class PollOptionView: UIView {
|
||||||
|
|
||||||
private let unselectedBackgroundColor = UIColor(white: 0.5, alpha: 0.25)
|
private static let unselectedBackgroundColor = UIColor(white: 0.5, alpha: 0.25)
|
||||||
|
|
||||||
let checkbox: PollOptionCheckboxView
|
private(set) var label: EmojiLabel!
|
||||||
|
private(set) var checkbox: PollOptionCheckboxView?
|
||||||
|
|
||||||
init(poll: Poll, option: Poll.Option, mastodonController: MastodonController) {
|
init(poll: Poll, option: Poll.Option, mastodonController: MastodonController) {
|
||||||
checkbox = PollOptionCheckboxView(multiple: poll.multiple)
|
|
||||||
|
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
let minHeight: CGFloat = 35
|
let minHeight: CGFloat = 35
|
||||||
layer.cornerRadius = 0.1 * minHeight
|
layer.cornerRadius = 0.1 * minHeight
|
||||||
layer.cornerCurve = .continuous
|
layer.cornerCurve = .continuous
|
||||||
backgroundColor = unselectedBackgroundColor
|
backgroundColor = PollOptionView.unselectedBackgroundColor
|
||||||
|
|
||||||
checkbox.translatesAutoresizingMaskIntoConstraints = false
|
let showCheckbox = poll.ownVotes?.isEmpty == false || !poll.effectiveExpired
|
||||||
addSubview(checkbox)
|
if showCheckbox {
|
||||||
|
checkbox = PollOptionCheckboxView(multiple: poll.multiple)
|
||||||
|
checkbox!.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
addSubview(checkbox!)
|
||||||
|
}
|
||||||
|
|
||||||
let label = EmojiLabel()
|
label = EmojiLabel()
|
||||||
label.translatesAutoresizingMaskIntoConstraints = false
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||||||
label.numberOfLines = 0
|
label.numberOfLines = 0
|
||||||
label.font = .preferredFont(forTextStyle: .callout)
|
label.font = .preferredFont(forTextStyle: .callout)
|
||||||
|
@ -88,12 +91,8 @@ class PollOptionView: UIView {
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
minHeightConstraint,
|
minHeightConstraint,
|
||||||
|
|
||||||
checkbox.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
||||||
checkbox.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
|
|
||||||
|
|
||||||
label.topAnchor.constraint(equalTo: topAnchor, constant: 4),
|
label.topAnchor.constraint(equalTo: topAnchor, constant: 4),
|
||||||
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
|
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4),
|
||||||
label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 8),
|
|
||||||
label.trailingAnchor.constraint(equalTo: percentLabel.leadingAnchor, constant: -4),
|
label.trailingAnchor.constraint(equalTo: percentLabel.leadingAnchor, constant: -4),
|
||||||
|
|
||||||
percentLabel.topAnchor.constraint(equalTo: topAnchor),
|
percentLabel.topAnchor.constraint(equalTo: topAnchor),
|
||||||
|
@ -101,6 +100,16 @@ class PollOptionView: UIView {
|
||||||
percentLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
|
percentLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
if let checkbox {
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
checkbox.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||||
|
checkbox.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
|
||||||
|
label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 8),
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8).isActive = true
|
||||||
|
}
|
||||||
|
|
||||||
isAccessibilityElement = true
|
isAccessibilityElement = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ class PollOptionsView: UIControl {
|
||||||
var mastodonController: MastodonController!
|
var mastodonController: MastodonController!
|
||||||
|
|
||||||
var checkedOptionIndices: [Int] {
|
var checkedOptionIndices: [Int] {
|
||||||
options.enumerated().filter { $0.element.checkbox.isChecked }.map(\.offset)
|
options.enumerated().filter { $0.element.checkbox?.isChecked == true }.map(\.offset)
|
||||||
}
|
}
|
||||||
var checkedOptionsChanged: (() -> Void)?
|
var checkedOptionsChanged: (() -> Void)?
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ class PollOptionsView: UIControl {
|
||||||
|
|
||||||
override var isEnabled: Bool {
|
override var isEnabled: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
options.forEach { $0.checkbox.readOnly = !isEnabled }
|
options.forEach { $0.checkbox?.readOnly = !isEnabled }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,9 +65,11 @@ class PollOptionsView: UIControl {
|
||||||
|
|
||||||
options = poll.options.enumerated().map { (index, opt) in
|
options = poll.options.enumerated().map { (index, opt) in
|
||||||
let optionView = PollOptionView(poll: poll, option: opt, mastodonController: mastodonController)
|
let optionView = PollOptionView(poll: poll, option: opt, mastodonController: mastodonController)
|
||||||
optionView.checkbox.readOnly = !isEnabled
|
if let checkbox = optionView.checkbox {
|
||||||
optionView.checkbox.isChecked = poll.ownVotes?.contains(index) ?? false
|
checkbox.readOnly = !isEnabled
|
||||||
optionView.checkbox.voted = poll.voted ?? false
|
checkbox.isChecked = poll.ownVotes?.contains(index) ?? false
|
||||||
|
checkbox.voted = poll.voted ?? false
|
||||||
|
}
|
||||||
stack.addArrangedSubview(optionView)
|
stack.addArrangedSubview(optionView)
|
||||||
return optionView
|
return optionView
|
||||||
}
|
}
|
||||||
|
@ -75,15 +77,25 @@ class PollOptionsView: UIControl {
|
||||||
accessibilityElements = options
|
accessibilityElements = options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
|
||||||
|
var height: CGFloat = 0
|
||||||
|
height += CGFloat(options.count - 1) * stack.spacing
|
||||||
|
for option in options {
|
||||||
|
// this isn't the actual width, but it's close enough for the estimate
|
||||||
|
height += option.label.sizeThatFits(CGSize(width: effectiveWidth, height: UIView.layoutFittingCompressedSize.height)).height
|
||||||
|
}
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
private func selectOption(_ option: PollOptionView) {
|
private func selectOption(_ option: PollOptionView) {
|
||||||
if poll.multiple {
|
if poll.multiple {
|
||||||
option.checkbox.isChecked.toggle()
|
option.checkbox?.isChecked.toggle()
|
||||||
} else {
|
} else {
|
||||||
for opt in options {
|
for opt in options {
|
||||||
if opt === option {
|
if opt === option {
|
||||||
opt.checkbox.isChecked = true
|
opt.checkbox?.isChecked = true
|
||||||
} else {
|
} else {
|
||||||
opt.checkbox.isChecked = false
|
opt.checkbox?.isChecked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
|
|
||||||
class StatusPollView: UIView {
|
class StatusPollView: UIView, StatusContentPollView {
|
||||||
|
|
||||||
private static let formatter: DateComponentsFormatter = {
|
private static let formatter: DateComponentsFormatter = {
|
||||||
let f = DateComponentsFormatter()
|
let f = DateComponentsFormatter()
|
||||||
|
@ -140,6 +140,11 @@ class StatusPollView: UIView {
|
||||||
voteButton.isEnabled = false
|
voteButton.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat {
|
||||||
|
guard let poll else { return 0 }
|
||||||
|
return optionsView.estimateHeight(effectiveWidth: effectiveWidth) + infoLabel.sizeThatFits(UIView.layoutFittingExpandedSize).height
|
||||||
|
}
|
||||||
|
|
||||||
private func checkedOptionsChanged() {
|
private func checkedOptionsChanged() {
|
||||||
voteButton.isEnabled = optionsView.checkedOptionIndices.count > 0
|
voteButton.isEnabled = optionsView.checkedOptionIndices.count > 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,13 +191,13 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var favoriteButton = UIButton().configure {
|
private(set) lazy var favoriteButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
||||||
$0.setImage(UIImage(systemName: "star.fill"), for: .normal)
|
$0.setImage(UIImage(systemName: "star.fill"), for: .normal)
|
||||||
$0.addTarget(self, action: #selector(favoritePressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(favoritePressed), for: .touchUpInside)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var reblogButton = UIButton().configure {
|
private(set) lazy var reblogButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
||||||
$0.setImage(UIImage(systemName: "repeat"), for: .normal)
|
$0.setImage(UIImage(systemName: "repeat"), for: .normal)
|
||||||
$0.addTarget(self, action: #selector(reblogPressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(reblogPressed), for: .touchUpInside)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
|
@ -348,24 +348,6 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
||||||
accountDetailAccessibilityElement.navigationDelegate = delegate
|
accountDetailAccessibilityElement.navigationDelegate = delegate
|
||||||
accountDetailAccessibilityElement.accountID = accountID
|
accountDetailAccessibilityElement.accountID = accountID
|
||||||
|
|
||||||
let metaButtonAttributes = AttributeContainer([
|
|
||||||
.font: ConversationMainStatusCollectionViewCell.metaFont
|
|
||||||
])
|
|
||||||
|
|
||||||
let favoritesFormat = NSLocalizedString("favorites count", comment: "conv main status favorites button label")
|
|
||||||
var favoritesConfig = UIButton.Configuration.plain()
|
|
||||||
favoritesConfig.baseForegroundColor = .secondaryLabel
|
|
||||||
favoritesConfig.attributedTitle = AttributedString(String.localizedStringWithFormat(favoritesFormat, status.favouritesCount), attributes: metaButtonAttributes)
|
|
||||||
favoritesConfig.contentInsets = .zero
|
|
||||||
favoritesCountButton.configuration = favoritesConfig
|
|
||||||
|
|
||||||
let reblogsFormat = NSLocalizedString("reblogs count", comment: "conv main status reblogs button label")
|
|
||||||
var reblogsConfig = UIButton.Configuration.plain()
|
|
||||||
reblogsConfig.baseForegroundColor = .secondaryLabel
|
|
||||||
reblogsConfig.attributedTitle = AttributedString(String.localizedStringWithFormat(reblogsFormat, status.reblogsCount), attributes: metaButtonAttributes)
|
|
||||||
reblogsConfig.contentInsets = .zero
|
|
||||||
reblogsCountButton.configuration = reblogsConfig
|
|
||||||
|
|
||||||
var timestampAndClientText = ConversationMainStatusCollectionViewCell.dateFormatter.string(from: status.createdAt)
|
var timestampAndClientText = ConversationMainStatusCollectionViewCell.dateFormatter.string(from: status.createdAt)
|
||||||
if let application = status.applicationName {
|
if let application = status.applicationName {
|
||||||
timestampAndClientText += " • \(application)"
|
timestampAndClientText += " • \(application)"
|
||||||
|
@ -376,7 +358,9 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
||||||
editTimestampButton.isHidden = false
|
editTimestampButton.isHidden = false
|
||||||
var config = UIButton.Configuration.plain()
|
var config = UIButton.Configuration.plain()
|
||||||
config.baseForegroundColor = .secondaryLabel
|
config.baseForegroundColor = .secondaryLabel
|
||||||
config.attributedTitle = AttributedString("Edited on \(ConversationMainStatusCollectionViewCell.dateFormatter.string(from: editedAt))", attributes: metaButtonAttributes)
|
config.attributedTitle = AttributedString("Edited on \(ConversationMainStatusCollectionViewCell.dateFormatter.string(from: editedAt))", attributes: AttributeContainer([
|
||||||
|
.font: ConversationMainStatusCollectionViewCell.metaFont
|
||||||
|
]))
|
||||||
config.contentInsets = .zero
|
config.contentInsets = .zero
|
||||||
editTimestampButton.configuration = config
|
editTimestampButton.configuration = config
|
||||||
} else {
|
} else {
|
||||||
|
@ -392,6 +376,33 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
||||||
baseCreateObservers()
|
baseCreateObservers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateStatusState(status: StatusMO) {
|
||||||
|
baseUpdateStatusState(status: status)
|
||||||
|
|
||||||
|
let attributes = AttributeContainer([
|
||||||
|
.font: ConversationMainStatusCollectionViewCell.metaFont
|
||||||
|
])
|
||||||
|
|
||||||
|
let favoritesFormat = NSLocalizedString("favorites count", comment: "conv main status favorites button label")
|
||||||
|
var favoritesConfig = UIButton.Configuration.plain()
|
||||||
|
favoritesConfig.baseForegroundColor = .secondaryLabel
|
||||||
|
favoritesConfig.attributedTitle = AttributedString(String.localizedStringWithFormat(favoritesFormat, status.favouritesCount), attributes: attributes)
|
||||||
|
favoritesConfig.contentInsets = .zero
|
||||||
|
favoritesCountButton.configuration = favoritesConfig
|
||||||
|
|
||||||
|
let reblogsFormat = NSLocalizedString("reblogs count", comment: "conv main status reblogs button label")
|
||||||
|
var reblogsConfig = UIButton.Configuration.plain()
|
||||||
|
reblogsConfig.baseForegroundColor = .secondaryLabel
|
||||||
|
reblogsConfig.attributedTitle = AttributedString(String.localizedStringWithFormat(reblogsFormat, status.reblogsCount), attributes: attributes)
|
||||||
|
reblogsConfig.contentInsets = .zero
|
||||||
|
reblogsCountButton.configuration = reblogsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func estimateContentHeight() -> CGFloat {
|
||||||
|
let width = bounds.width - 2*16
|
||||||
|
return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: width)
|
||||||
|
}
|
||||||
|
|
||||||
func updateUIForPreferences(status: StatusMO) {
|
func updateUIForPreferences(status: StatusMO) {
|
||||||
baseUpdateUIForPreferences(status: status)
|
baseUpdateUIForPreferences(status: status)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,8 @@ protocol StatusCollectionViewCell: UICollectionViewCell, AttachmentViewDelegate
|
||||||
var collapseButton: StatusCollapseButton { get }
|
var collapseButton: StatusCollapseButton { get }
|
||||||
var contentContainer: StatusContentContainer<StatusContentTextView, StatusPollView> { get }
|
var contentContainer: StatusContentContainer<StatusContentTextView, StatusPollView> { get }
|
||||||
var replyButton: UIButton { get }
|
var replyButton: UIButton { get }
|
||||||
var favoriteButton: UIButton { get }
|
var favoriteButton: ToggleableButton { get }
|
||||||
var reblogButton: UIButton { get }
|
var reblogButton: ToggleableButton { get }
|
||||||
var moreButton: UIButton { get }
|
var moreButton: UIButton { get }
|
||||||
var prevThreadLinkView: UIView? { get set }
|
var prevThreadLinkView: UIView? { get set }
|
||||||
var nextThreadLinkView: UIView? { get set }
|
var nextThreadLinkView: UIView? { get set }
|
||||||
|
@ -45,6 +45,8 @@ protocol StatusCollectionViewCell: UICollectionViewCell, AttachmentViewDelegate
|
||||||
var cancellables: Set<AnyCancellable> { get set }
|
var cancellables: Set<AnyCancellable> { get set }
|
||||||
|
|
||||||
func updateUIForPreferences(status: StatusMO)
|
func updateUIForPreferences(status: StatusMO)
|
||||||
|
func updateStatusState(status: StatusMO)
|
||||||
|
func estimateContentHeight() -> CGFloat
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: UI Configuration
|
// MARK: UI Configuration
|
||||||
|
@ -58,7 +60,13 @@ extension StatusCollectionViewCell {
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.filter { [unowned self] in $0 == self.statusID }
|
.filter { [unowned self] in $0 == self.statusID }
|
||||||
.sink { [unowned self] _ in
|
.sink { [unowned self] _ in
|
||||||
self.delegate?.statusCellNeedsReconfigure(self, animated: true, completion: nil)
|
if let status = self.mastodonController.persistentContainer.status(for: self.statusID) {
|
||||||
|
// update immediately w/o animation
|
||||||
|
self.favoriteButton.active = status.favourited
|
||||||
|
self.reblogButton.active = status.reblogged
|
||||||
|
|
||||||
|
self.delegate?.statusCellNeedsReconfigure(self, animated: true, completion: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
@ -98,6 +106,7 @@ extension StatusCollectionViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUIForPreferences(status: status)
|
updateUIForPreferences(status: status)
|
||||||
|
updateStatusState(status: status)
|
||||||
|
|
||||||
contentWarningLabel.text = status.spoilerText
|
contentWarningLabel.text = status.spoilerText
|
||||||
contentWarningLabel.isHidden = status.spoilerText.isEmpty
|
contentWarningLabel.isHidden = status.spoilerText.isEmpty
|
||||||
|
@ -105,35 +114,17 @@ extension StatusCollectionViewCell {
|
||||||
contentWarningLabel.setEmojis(status.emojis, identifier: statusID)
|
contentWarningLabel.setEmojis(status.emojis, identifier: statusID)
|
||||||
}
|
}
|
||||||
|
|
||||||
replyButton.isEnabled = mastodonController.loggedIn
|
|
||||||
|
|
||||||
favoriteButton.isEnabled = mastodonController.loggedIn
|
|
||||||
if status.favourited {
|
|
||||||
favoriteButton.tintColor = UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)
|
|
||||||
favoriteButton.accessibilityLabel = NSLocalizedString("Undo Favorite", comment: "undo favorite button accessibility label")
|
|
||||||
} else {
|
|
||||||
favoriteButton.tintColor = nil
|
|
||||||
favoriteButton.accessibilityLabel = NSLocalizedString("Favorite", comment: "favorite button accessibility label")
|
|
||||||
}
|
|
||||||
|
|
||||||
reblogButton.isEnabled = reblogEnabled(status: status)
|
reblogButton.isEnabled = reblogEnabled(status: status)
|
||||||
if status.reblogged {
|
replyButton.isEnabled = mastodonController.loggedIn
|
||||||
reblogButton.tintColor = UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)
|
favoriteButton.isEnabled = mastodonController.loggedIn
|
||||||
reblogButton.accessibilityLabel = NSLocalizedString("Undo Reblog", comment: "undo reblog button accessibility label")
|
|
||||||
} else {
|
|
||||||
reblogButton.tintColor = nil
|
|
||||||
reblogButton.accessibilityLabel = NSLocalizedString("Reblog", comment: "reblog button accessibility label")
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep menu in sync with changed states e.g. bookmarked, muted
|
let didResolve = statusState.resolveFor(status: status, height: self.estimateContentHeight)
|
||||||
// do not include reply action here, because the cell already contains a button for it
|
// let didResolve = statusState.resolveFor(status: status) {
|
||||||
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: delegate?.actionsForStatus(status, source: .view(moreButton), includeStatusButtonActions: false) ?? [])
|
//// // layout so that we can take the content height into consideration when deciding whether to collapse
|
||||||
|
//// layoutIfNeeded()
|
||||||
let didResolve = statusState.resolveFor(status: status) {
|
//// return contentContainer.visibleSubviewHeight
|
||||||
// layout so that we can take the content height into consideration when deciding whether to collapse
|
// return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: )
|
||||||
layoutIfNeeded()
|
// }
|
||||||
return contentContainer.visibleSubviewHeight
|
|
||||||
}
|
|
||||||
if didResolve {
|
if didResolve {
|
||||||
if statusState.collapsible! && showStatusAutomatically {
|
if statusState.collapsible! && showStatusAutomatically {
|
||||||
statusState.collapsed = false
|
statusState.collapsed = false
|
||||||
|
@ -157,7 +148,9 @@ extension StatusCollectionViewCell {
|
||||||
guard mastodonController.loggedIn else {
|
guard mastodonController.loggedIn else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if status.visibility == .direct || status.visibility == .private {
|
if status.visibility == .direct {
|
||||||
|
return false
|
||||||
|
} else if status.visibility == .private {
|
||||||
if mastodonController.instanceFeatures.boostToOriginalAudience,
|
if mastodonController.instanceFeatures.boostToOriginalAudience,
|
||||||
status.account.id == mastodonController.account?.id {
|
status.account.id == mastodonController.account?.id {
|
||||||
return true
|
return true
|
||||||
|
@ -212,6 +205,30 @@ extension StatusCollectionViewCell {
|
||||||
displayNameLabel.updateForAccountDisplayName(account: status.account)
|
displayNameLabel.updateForAccountDisplayName(account: status.account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func baseUpdateStatusState(status: StatusMO) {
|
||||||
|
favoriteButton.active = status.favourited
|
||||||
|
if status.favourited {
|
||||||
|
favoriteButton.accessibilityLabel = NSLocalizedString("Undo Favorite", comment: "undo favorite button accessibility label")
|
||||||
|
} else {
|
||||||
|
favoriteButton.accessibilityLabel = NSLocalizedString("Favorite", comment: "favorite button accessibility label")
|
||||||
|
}
|
||||||
|
reblogButton.active = status.reblogged
|
||||||
|
if status.reblogged {
|
||||||
|
reblogButton.accessibilityLabel = NSLocalizedString("Undo Reblog", comment: "undo reblog button accessibility label")
|
||||||
|
} else {
|
||||||
|
reblogButton.accessibilityLabel = NSLocalizedString("Reblog", comment: "reblog button accessibility label")
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep menu in sync with changed states e.g. bookmarked, muted
|
||||||
|
// do not include reply action here, because the cell already contains a button for it
|
||||||
|
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: delegate?.actionsForStatus(status, source: .view(moreButton), includeStatusButtonActions: false) ?? [])
|
||||||
|
|
||||||
|
contentContainer.pollView.isHidden = status.poll == nil
|
||||||
|
contentContainer.pollView.mastodonController = mastodonController
|
||||||
|
contentContainer.pollView.delegate = delegate
|
||||||
|
contentContainer.pollView.updateUI(status: status, poll: status.poll)
|
||||||
|
}
|
||||||
|
|
||||||
func setShowThreadLinks(prev: Bool, next: Bool) {
|
func setShowThreadLinks(prev: Bool, next: Bool) {
|
||||||
if prev {
|
if prev {
|
||||||
if let prevThreadLinkView {
|
if let prevThreadLinkView {
|
||||||
|
|
|
@ -8,7 +8,11 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class StatusContentContainer<ContentView: ContentTextView, PollView: UIView>: UIView {
|
protocol StatusContentPollView: UIView {
|
||||||
|
func estimateHeight(effectiveWidth: CGFloat) -> CGFloat
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatusContentContainer<ContentView: ContentTextView, PollView: StatusContentPollView>: UIView {
|
||||||
|
|
||||||
private var useTopSpacer = false
|
private var useTopSpacer = false
|
||||||
private let topSpacer = UIView().configure {
|
private let topSpacer = UIView().configure {
|
||||||
|
@ -25,9 +29,10 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: UIView>: UI
|
||||||
$0.isSelectable = false
|
$0.isSelectable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static var cardViewHeight: CGFloat { 90 }
|
||||||
let cardView = StatusCardView().configure {
|
let cardView = StatusCardView().configure {
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
$0.heightAnchor.constraint(equalToConstant: 90),
|
$0.heightAnchor.constraint(equalToConstant: StatusContentContainer.cardViewHeight),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,4 +137,22 @@ class StatusContentContainer<ContentView: ContentTextView, PollView: UIView>: UI
|
||||||
zeroHeightConstraint.isActive = collapsed
|
zeroHeightConstraint.isActive = collapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used only for collapsing automatically based on height, doesn't need to be accurate
|
||||||
|
// just roughly inline with the content height
|
||||||
|
func estimateVisibleSubviewHeight(effectiveWidth: CGFloat) -> CGFloat {
|
||||||
|
var height: CGFloat = 0
|
||||||
|
height += contentTextView.sizeThatFits(CGSize(width: effectiveWidth, height: UIView.layoutFittingCompressedSize.height)).height
|
||||||
|
if !cardView.isHidden {
|
||||||
|
height += StatusContentContainer.cardViewHeight
|
||||||
|
}
|
||||||
|
if !attachmentsView.isHidden {
|
||||||
|
height += effectiveWidth / attachmentsView.aspectRatio
|
||||||
|
}
|
||||||
|
if !pollView.isHidden {
|
||||||
|
let pollHeight = pollView.estimateHeight(effectiveWidth: effectiveWidth)
|
||||||
|
height += pollHeight
|
||||||
|
}
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,13 +238,13 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var favoriteButton = UIButton().configure {
|
private(set) lazy var favoriteButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
||||||
$0.setImage(UIImage(systemName: "star.fill"), for: .normal)
|
$0.setImage(UIImage(systemName: "star.fill"), for: .normal)
|
||||||
$0.addTarget(self, action: #selector(favoritePressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(favoritePressed), for: .touchUpInside)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var reblogButton = UIButton().configure {
|
private(set) lazy var reblogButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
||||||
$0.setImage(UIImage(systemName: "repeat"), for: .normal)
|
$0.setImage(UIImage(systemName: "repeat"), for: .normal)
|
||||||
$0.addTarget(self, action: #selector(reblogPressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(reblogPressed), for: .touchUpInside)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
|
@ -625,6 +625,15 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateStatusState(status: StatusMO) {
|
||||||
|
baseUpdateStatusState(status: status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func estimateContentHeight() -> CGFloat {
|
||||||
|
let width = bounds.width /* leading spacing: */ - 16 /* avatar: */ - 50 /* spacing: 8 */ - 8 /* trailing spacing: */ - 16
|
||||||
|
return contentContainer.estimateVisibleSubviewHeight(effectiveWidth: width)
|
||||||
|
}
|
||||||
|
|
||||||
private func updateTimestamp() {
|
private func updateTimestamp() {
|
||||||
guard let mastodonController,
|
guard let mastodonController,
|
||||||
let status = mastodonController.persistentContainer.status(for: statusID) else {
|
let status = mastodonController.persistentContainer.status(for: statusID) else {
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// ToggleableButton.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 5/13/23.
|
||||||
|
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class ToggleableButton: UIButton {
|
||||||
|
|
||||||
|
let activeColor: UIColor
|
||||||
|
|
||||||
|
var active: Bool {
|
||||||
|
didSet {
|
||||||
|
tintColor = active ? activeColor : nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(activeColor: UIColor) {
|
||||||
|
self.activeColor = activeColor
|
||||||
|
self.active = false
|
||||||
|
super.init(frame: .zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue