Update API

This commit is contained in:
Peter 2018-12-18 16:02:33 +03:00
parent e055885c52
commit a4abbc4068
12 changed files with 1862 additions and 1739 deletions

View File

@ -4109,7 +4109,9 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
private func presentPollCreation() {
if case let .peer(peerId) = self.chatLocation {
self.present(createPollController(account: self.account, peerId: peerId), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
self.present(createPollController(account: self.account, peerId: peerId, completion: { [weak self] message in
self?.sendMessages([message])
}), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
}

View File

@ -430,7 +430,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
})))
}
if let activePoll = activePoll {
if let _ = activePoll, messages[0].forwardInfo == nil {
actions.append(.sheet(ChatMessageContextMenuSheetAction(color: .accent, title: chatPresentationInterfaceState.strings.Conversation_StopPoll, action: {
interfaceInteraction.requestStopPollInMessage(messages[0].id)
})))
@ -575,7 +575,9 @@ func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messag
}
}
if id.peerId == accountPeerId {
optionsMap[id]!.insert(.forward)
if !(message.flags.isSending || message.flags.contains(.Failed)) {
optionsMap[id]!.insert(.forward)
}
optionsMap[id]!.insert(.deleteLocally)
} else if let peer = transaction.getPeer(id.peerId) {
var isAction = false
@ -603,7 +605,9 @@ func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messag
}
if !message.containsSecretMedia && !isAction {
if message.id.peerId.namespace != Namespaces.Peer.SecretChat {
optionsMap[id]!.insert(.forward)
if !(message.flags.isSending || message.flags.contains(.Failed)) {
optionsMap[id]!.insert(.forward)
}
}
}
@ -617,7 +621,9 @@ func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messag
} else if let group = peer as? TelegramGroup {
if message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia {
if !isAction {
optionsMap[id]!.insert(.forward)
if !(message.flags.isSending || message.flags.contains(.Failed)) {
optionsMap[id]!.insert(.forward)
}
}
}
optionsMap[id]!.insert(.deleteLocally)
@ -643,7 +649,9 @@ func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messag
}
} else if let user = peer as? TelegramUser {
if message.id.peerId.namespace != Namespaces.Peer.SecretChat && !message.containsSecretMedia && !isAction {
optionsMap[id]!.insert(.forward)
if !(message.flags.isSending || message.flags.contains(.Failed)) {
optionsMap[id]!.insert(.forward)
}
}
optionsMap[id]!.insert(.deleteLocally)
if canPerformEditingActions(limits: limitsConfiguration, accountPeerId: accountPeerId, message: message) {

View File

@ -36,57 +36,57 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
var isVideo = false
inner: for attribute in fileMedia.attributes {
switch attribute {
case .Animated:
isAnimated = true
break inner
case let .Audio(isVoice, _, title, performer, _):
if isVoice {
messageText = strings.Message_Audio
case .Animated:
isAnimated = true
break inner
} else {
let descriptionString: String
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
descriptionString = title + "" + performer
} else if let title = title, !title.isEmpty {
descriptionString = title
} else if let performer = performer, !performer.isEmpty {
descriptionString = performer
} else if let fileName = fileMedia.fileName {
descriptionString = fileName
case let .Audio(isVoice, _, title, performer, _):
if isVoice {
messageText = strings.Message_Audio
break inner
} else {
descriptionString = strings.Message_Audio
}
messageText = descriptionString
break inner
}
case let .Sticker(displayText, _, _):
if displayText.isEmpty {
messageText = strings.Message_Sticker
break inner
} else {
messageText = strings.Message_StickerText(displayText).0
break inner
}
case let .Video(_, _, flags):
if flags.contains(.instantRoundVideo) {
messageText = strings.Message_VideoMessage
break inner
} else {
if message.text.isEmpty {
isVideo = true
} else if #available(iOSApplicationExtension 9.0, *) {
if !fileMedia.isAnimated {
messageText = "📹 \(messageText)"
let descriptionString: String
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
descriptionString = title + "" + performer
} else if let title = title, !title.isEmpty {
descriptionString = title
} else if let performer = performer, !performer.isEmpty {
descriptionString = performer
} else if let fileName = fileMedia.fileName {
descriptionString = fileName
} else {
descriptionString = strings.Message_Audio
}
messageText = descriptionString
break inner
}
}
default:
if !message.text.isEmpty {
messageText = "📎 \(messageText)"
break inner
}
break
case let .Sticker(displayText, _, _):
if displayText.isEmpty {
messageText = strings.Message_Sticker
break inner
} else {
messageText = strings.Message_StickerText(displayText).0
break inner
}
case let .Video(_, _, flags):
if flags.contains(.instantRoundVideo) {
messageText = strings.Message_VideoMessage
break inner
} else {
if message.text.isEmpty {
isVideo = true
} else if #available(iOSApplicationExtension 9.0, *) {
if !fileMedia.isAnimated {
messageText = "📹 \(messageText)"
}
break inner
}
}
default:
if !message.text.isEmpty {
messageText = "📎 \(messageText)"
break inner
}
break
}
}
if isAnimated {
@ -139,7 +139,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
messageText = text
}
case let poll as TelegramMediaPoll:
messageText = poll.text
messageText = "📊 \(strings.Message_Poll)"
default:
break
}

View File

@ -431,6 +431,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
tmpWidth = baseWidth - 32.0
}
}
var deliveryFailedInset: CGFloat = 0.0
if item.content.firstMessage.flags.contains(.Failed) {
deliveryFailedInset += 24.0
}
tmpWidth -= deliveryFailedInset
let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset)
var contentPropertiesAndPrepareLayouts: [(Message, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = []
@ -459,8 +467,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
var authorNameString: String?
let authorIsAdmin: Bool
switch content {
case let .message(_, _, _, isAdmin):
authorIsAdmin = isAdmin
case let .message(message, _, _, isAdmin):
if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
authorIsAdmin = false
} else {
authorIsAdmin = isAdmin
}
case .group:
authorIsAdmin = false
}
@ -1050,11 +1062,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
contentVerticalOffset = floorToScreenPixels((minimalContentSize.height - calculatedBubbleHeight) / 2.0)
}
var deliveryFailedInset: CGFloat = 0.0
if item.content.firstMessage.flags.contains(.Failed) {
deliveryFailedInset += 24.0
}
let backgroundFrame: CGRect
let contentOrigin: CGPoint
let contentUpperRightCorner: CGPoint

View File

@ -549,7 +549,6 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let bubbleTheme = item.presentationData.theme.theme.chat.bubble
let attributedText = NSAttributedString(string: poll?.text ?? "", font: item.presentationData.messageBoldFont, textColor: incoming ? bubbleTheme.incomingPrimaryTextColor : bubbleTheme.outgoingPrimaryTextColor)
@ -566,7 +565,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
let (typeLayout, typeApply) = makeTypeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: typeText, font: labelsFont, textColor: incoming ? bubbleTheme.incomingSecondaryTextColor : bubbleTheme.outgoingSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let votersString: String
if let totalVoters = poll?.results.totalVoters, totalVoters != 0 {
if let totalVoters = poll?.results.totalVoters {
votersString = item.presentationData.strings.MessagePoll_VotedCount(totalVoters)
} else {
votersString = " "
@ -597,14 +596,28 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
var optionVoterCount: [Int: Int32] = [:]
var maxOptionVoterCount: Int32 = 0
var totalVoterCount: Int32 = 0
if let voters = poll.results.voters, let totalVoters = poll.results.totalVoters {
let voters: [TelegramMediaPollOptionVoters]?
if poll.isClosed {
voters = poll.results.voters ?? []
} else {
voters = poll.results.voters
}
if let voters = voters, let totalVoters = poll.results.totalVoters {
var didVote = false
for voter in voters {
if voter.selected {
didVote = true
}
}
totalVoterCount = totalVoters
for i in 0 ..< poll.options.count {
inner: for optionVoters in voters {
if optionVoters.opaqueIdentifier == poll.options[i].opaqueIdentifier {
optionVoterCount[i] = optionVoters.count
maxOptionVoterCount = max(maxOptionVoterCount, optionVoters.count)
break inner
if didVote {
for i in 0 ..< poll.options.count {
inner: for optionVoters in voters {
if optionVoters.opaqueIdentifier == poll.options[i].opaqueIdentifier {
optionVoterCount[i] = optionVoters.count
maxOptionVoterCount = max(maxOptionVoterCount, optionVoters.count)
break inner
}
}
}
}
@ -620,8 +633,14 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
makeLayout = ChatMessagePollOptionNode.asyncLayout(nil)
}
var optionResult: ChatMessagePollOptionResult?
if let count = optionVoterCount[i], maxOptionVoterCount != 0, totalVoterCount != 0 {
optionResult = ChatMessagePollOptionResult(normalized: CGFloat(count) / CGFloat(maxOptionVoterCount), absolute: CGFloat(count) / CGFloat(totalVoterCount))
if let count = optionVoterCount[i] {
if maxOptionVoterCount != 0 && totalVoterCount != 0 {
optionResult = ChatMessagePollOptionResult(normalized: CGFloat(count) / CGFloat(maxOptionVoterCount), absolute: CGFloat(count) / CGFloat(totalVoterCount))
} else if poll.isClosed {
optionResult = ChatMessagePollOptionResult(normalized: 0, absolute: 0)
}
} else if poll.isClosed {
optionResult = ChatMessagePollOptionResult(normalized: 0, absolute: 0)
}
let result = makeLayout(item.account.peerId, item.presentationData, item.message, option, optionResult, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0)
boundingSize.width = max(boundingSize.width, result.minimumWidth + layoutConstants.bubble.borderInset * 2.0)
@ -632,7 +651,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
boundingSize.width = max(boundingSize.width, min(270.0, constrainedSize.width))
var canVote = false
if item.message.id.namespace == Namespaces.Message.Cloud, let poll = poll, poll.pollId.namespace == Namespaces.Media.CloudPoll {
if item.message.id.namespace == Namespaces.Message.Cloud, let poll = poll, poll.pollId.namespace == Namespaces.Media.CloudPoll, !poll.isClosed {
var hasVoted = false
if let voters = poll.results.voters {
for voter in voters {

View File

@ -4,6 +4,9 @@ import SwiftSignalKit
import Postbox
import TelegramCore
private let maxTextLength = 255
private let maxOptionLength = 100
private final class CreatePollControllerArguments {
let updatePollText: (String) -> Void
let updateOptionText: (Int, String) -> Void
@ -44,7 +47,7 @@ private enum CreatePollEntryTag: Equatable, ItemListItemTag {
}
private enum CreatePollEntry: ItemListNodeEntry {
case textHeader(PresentationTheme, String, String)
case textHeader(PresentationTheme, String, ItemListSectionHeaderAccessoryText)
case text(PresentationTheme, String, String, Int)
case optionsHeader(PresentationTheme, String)
case option(PresentationTheme, PresentationStrings, Int, Int, String, String, Bool, Bool)
@ -109,7 +112,7 @@ private enum CreatePollEntry: ItemListNodeEntry {
case let .optionsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .option(theme, strings, id, _, placeholder, text, revealed, hasNext):
return CreatePollOptionItem(theme: theme, strings: strings, id: id, placeholder: placeholder, value: text, maxLength: 256, editing: CreatePollOptionItemEditing(editable: true, hasActiveRevealControls: revealed), sectionId: self.section, setItemIdWithRevealedOptions: { id, fromId in
return CreatePollOptionItem(theme: theme, strings: strings, id: id, placeholder: placeholder, value: text, maxLength: maxOptionLength, editing: CreatePollOptionItemEditing(editable: true, hasActiveRevealControls: revealed), sectionId: self.section, setItemIdWithRevealedOptions: { id, fromId in
arguments.setItemIdWithRevealedOptions(id, fromId)
}, updated: { value in
arguments.updateOptionText(id, value)
@ -146,9 +149,10 @@ private struct CreatePollControllerState: Equatable {
private func createPollControllerEntries(presentationData: PresentationData, state: CreatePollControllerState, limitsConfiguration: LimitsConfiguration) -> [CreatePollEntry] {
var entries: [CreatePollEntry] = []
var textLimitText = ""
if state.text.count >= Int(limitsConfiguration.maxMediaCaptionLength) * 70 / 100 {
textLimitText = "\(Int(limitsConfiguration.maxMediaCaptionLength) - state.text.count)"
var textLimitText = ItemListSectionHeaderAccessoryText(value: "", color: .generic)
if state.text.count >= Int(maxTextLength) * 70 / 100 {
let remainingCount = Int(maxTextLength) - state.text.count
textLimitText = ItemListSectionHeaderAccessoryText(value: "\(remainingCount)", color: remainingCount < 0 ? .destructive : .generic)
}
entries.append(.textHeader(presentationData.theme, presentationData.strings.CreatePoll_TextHeader, textLimitText))
entries.append(.text(presentationData.theme, presentationData.strings.CreatePoll_TextPlaceholder, state.text, Int(limitsConfiguration.maxMediaCaptionLength)))
@ -166,7 +170,7 @@ private func createPollControllerEntries(presentationData: PresentationData, sta
return entries
}
public func createPollController(account: Account, peerId: PeerId) -> ViewController {
public func createPollController(account: Account, peerId: PeerId, completion: @escaping (EnqueueMessage) -> Void) -> ViewController {
let statePromise = ValuePromise(CreatePollControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: CreatePollControllerState())
let updateState: ((CreatePollControllerState) -> CreatePollControllerState) -> Void = { f in
@ -175,6 +179,7 @@ public func createPollController(account: Account, peerId: PeerId) -> ViewContro
var presentControllerImpl: ((ViewController, Any?) -> Void)?
var dismissImpl: (() -> Void)?
var ensureTextVisibleImpl: (() -> Void)?
var ensureOptionVisibleImpl: ((Int) -> Void)?
let actionsDisposable = DisposableSet()
@ -191,6 +196,7 @@ public func createPollController(account: Account, peerId: PeerId) -> ViewContro
state.text = value
return state
}
ensureTextVisibleImpl?()
}, updateOptionText: { id, value in
updateState { state in
var state = state
@ -201,6 +207,7 @@ public func createPollController(account: Account, peerId: PeerId) -> ViewContro
}
return state
}
ensureOptionVisibleImpl?(id)
}, moveToNextOption: { id in
updateState { state in
var state = state
@ -257,11 +264,17 @@ public func createPollController(account: Account, peerId: PeerId) -> ViewContro
if state.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
enabled = false
}
if state.text.count > maxTextLength {
enabled = false
}
var hasNonEmptyOptions = false
for option in state.options {
if !option.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
hasNonEmptyOptions = true
}
if option.text.count > maxOptionLength {
enabled = false
}
}
if !hasNonEmptyOptions {
enabled = false
@ -276,7 +289,7 @@ public func createPollController(account: Account, peerId: PeerId) -> ViewContro
options.append(TelegramMediaPollOption(text: optionText, opaqueIdentifier: "\(i)".data(using: .utf8)!))
}
}
let _ = enqueueMessages(account: account, peerId: peerId, messages: [.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: arc4random64()), text: state.text.trimmingCharacters(in: .whitespacesAndNewlines), options: options, results: TelegramMediaPollResults(voters: nil, totalVoters: nil), isClosed: false)), replyToMessageId: nil, localGroupingKey: nil)]).start()
completion(.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: arc4random64()), text: state.text.trimmingCharacters(in: .whitespacesAndNewlines), options: options, results: TelegramMediaPollResults(voters: nil, totalVoters: nil), isClosed: false)), replyToMessageId: nil, localGroupingKey: nil))
dismissImpl?()
})
@ -333,6 +346,27 @@ public func createPollController(account: Account, peerId: PeerId) -> ViewContro
controller?.view.endEditing(true)
controller?.dismiss()
}
ensureTextVisibleImpl = { [weak controller] in
controller?.afterLayout({
guard let controller = controller else {
return
}
var resultItemNode: ListViewItemNode?
let _ = controller.frameForItemNode({ itemNode in
if let itemNode = itemNode as? ItemListItemNode {
if let tag = itemNode.tag, tag.isEqual(to: CreatePollEntryTag.text) {
resultItemNode = itemNode as? ListViewItemNode
return true
}
}
return false
})
if let resultItemNode = resultItemNode {
controller.ensureItemNodeVisible(resultItemNode)
}
})
}
ensureOptionVisibleImpl = { [weak controller] id in
controller?.afterLayout({
guard let controller = controller else {

View File

@ -151,21 +151,6 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var updatedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
if let item = self.item {
if updatedText.count > item.maxLength {
updatedText = String(updatedText[..<updatedText.index(updatedText.startIndex, offsetBy: item.maxLength)])
if textField.text != updatedText {
textField.text = updatedText
self.textFieldTextChanged(textField)
return false
}
}
}
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let item = self.item {
if let next = item.next {
@ -210,8 +195,9 @@ class CreatePollOptionItemNode: ItemListRevealOptionsItemNode, ItemListItemNode,
let textLength = item.value.count
let displayTextLimit = textLength > item.maxLength * 70 / 100
let remainingCount = item.maxLength - textLength
let (textLimitLayout, textLimitApply) = makeTextLimitLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(item.maxLength - textLength)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: .greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let (textLimitLayout, textLimitApply) = makeTextLimitLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(remainingCount)", font: Font.regular(13.0), textColor: remainingCount < 0 ? item.theme.list.itemDestructiveColor : item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: .greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
return (layout, { [weak self] in
if let strongSelf = self {

View File

@ -93,7 +93,31 @@ final class ItemListNodeVisibleEntries<Entry: ItemListNodeEntry>: Sequence {
}
}
class ItemListControllerNode<Entry: ItemListNodeEntry>: ViewControllerTracingNode, UIScrollViewDelegate {
final class ItemListControllerNodeView: UITracingLayerView {
var onLayout: (() -> Void)?
override func layoutSubviews() {
super.layoutSubviews()
self.onLayout?()
}
private var inHitTest = false
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.inHitTest {
return super.hitTest(point, with: event)
} else {
self.inHitTest = true
let result = self.hitTestImpl?(point, event)
self.inHitTest = false
return result
}
}
}
class ItemListControllerNode<Entry: ItemListNodeEntry>: ASDisplayNode, UIScrollViewDelegate {
private var _ready = ValuePromise<Bool>()
public var ready: Signal<Bool, NoError> {
return self._ready.get()
@ -142,6 +166,10 @@ class ItemListControllerNode<Entry: ItemListNodeEntry>: ViewControllerTracingNod
super.init()
self.setViewBlock({
return ItemListControllerNodeView()
})
self.backgroundColor = nil
self.isOpaque = false
@ -203,6 +231,23 @@ class ItemListControllerNode<Entry: ItemListNodeEntry>: ViewControllerTracingNod
override func didLoad() {
super.didLoad()
(self.view as? ItemListControllerNodeView)?.onLayout = { [weak self] in
guard let strongSelf = self else {
return
}
if !strongSelf.afterLayoutActions.isEmpty {
let afterLayoutActions = strongSelf.afterLayoutActions
strongSelf.afterLayoutActions = []
for f in afterLayoutActions {
f()
}
}
}
(self.view as? ItemListControllerNodeView)?.hitTestImpl = { [weak self] point, event in
return self?.hitTest(point, with: event)
}
}
func animateIn() {

View File

@ -166,7 +166,7 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega
}
let attributedMeasureText = NSAttributedString(string: measureText, font: Font.regular(17.0), textColor: .black)
let attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedMeasureText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 16.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedMeasureText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 16.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
let separatorHeight = UIScreenPixel

View File

@ -3,15 +3,25 @@ import Display
import AsyncDisplayKit
import SwiftSignalKit
enum ItemListSectionHeaderAccessoryTextColor {
case generic
case destructive
}
struct ItemListSectionHeaderAccessoryText: Equatable {
let value: String
let color: ItemListSectionHeaderAccessoryTextColor
}
class ItemListSectionHeaderItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let text: String
let accessoryText: String
let accessoryText: ItemListSectionHeaderAccessoryText?
let sectionId: ItemListSectionId
let isAlwaysPlain: Bool = true
init(theme: PresentationTheme, text: String, accessoryText: String = "", sectionId: ItemListSectionId) {
init(theme: PresentationTheme, text: String, accessoryText: ItemListSectionHeaderAccessoryText? = nil, sectionId: ItemListSectionId) {
self.theme = theme
self.text = text
self.accessoryText = accessoryText
@ -86,7 +96,18 @@ class ItemListSectionHeaderItemNode: ListViewItemNode {
let leftInset: CGFloat = 15.0 + params.leftInset
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.text, font: titleFont, textColor: item.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (accessoryLayout, accessoryApply) = makeAccessoryTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.accessoryText, font: titleFont, textColor: item.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var accessoryTextString: NSAttributedString?
if let accessoryText = item.accessoryText {
let color: UIColor
switch accessoryText.color {
case .generic:
color = item.theme.list.sectionHeaderTextColor
case .destructive:
color = item.theme.list.freeTextErrorColor
}
accessoryTextString = NSAttributedString(string: accessoryText.value, font: titleFont, textColor: color)
}
let (accessoryLayout, accessoryApply) = makeAccessoryTextLayout(TextNodeLayoutArguments(attributedString: accessoryTextString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let contentSize: CGSize
var insets = UIEdgeInsets()

File diff suppressed because it is too large Load Diff