Manual date formatting

This commit is contained in:
Ilya Laktyushin 2026-02-24 13:07:08 +04:00
parent c93c70ff84
commit f852dca2c8
14 changed files with 308 additions and 91 deletions

View file

@ -15832,3 +15832,5 @@ Error: %8$@";
"Channel.AdminLog.MessageRankNameNew" = "changed member tag for %1$@:\n%2$@";
"Channel.AdminLog.MessageRankUsernameNew" = "changed member tag for %1$@ (%2$@):\n%3$@";
"Channel.AdminLog.MessageRankNew" = "changed member tag:\n%1$@";
"TextFormat.Date" = "Date";

View file

@ -1220,6 +1220,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
})
strongSelf.present(controller)
}
}, openDateEditing: {
}, displaySlowmodeTooltip: { _, _ in
}, displaySendMessageOptions: { [weak self] node, gesture in
guard let strongSelf = self, let textInputPanelNode = strongSelf.textInputPanelNode else {

View file

@ -148,6 +148,7 @@ public final class ChatPanelInterfaceInteraction {
public let updateInputLanguage: (@escaping (String?) -> String?) -> Void
public let unarchiveChat: () -> Void
public let openLinkEditing: () -> Void
public let openDateEditing: () -> Void
public let displaySlowmodeTooltip: (UIView, CGRect) -> Void
public let displaySendMessageOptions: (ASDisplayNode, ContextGesture) -> Void
public let openScheduledMessages: () -> Void
@ -277,6 +278,7 @@ public final class ChatPanelInterfaceInteraction {
updateInputLanguage: @escaping ((String?) -> String?) -> Void,
unarchiveChat: @escaping () -> Void,
openLinkEditing: @escaping () -> Void,
openDateEditing: @escaping () -> Void,
displaySlowmodeTooltip: @escaping (UIView, CGRect) -> Void,
displaySendMessageOptions: @escaping (ASDisplayNode, ContextGesture) -> Void,
openScheduledMessages: @escaping () -> Void,
@ -405,6 +407,7 @@ public final class ChatPanelInterfaceInteraction {
self.updateInputLanguage = updateInputLanguage
self.unarchiveChat = unarchiveChat
self.openLinkEditing = openLinkEditing
self.openDateEditing = openDateEditing
self.displaySlowmodeTooltip = displaySlowmodeTooltip
self.displaySendMessageOptions = displaySendMessageOptions
self.openScheduledMessages = openScheduledMessages
@ -541,8 +544,8 @@ public final class ChatPanelInterfaceInteraction {
}, requestStopPollInMessage: { _ in
}, updateInputLanguage: { _ in
}, unarchiveChat: {
}, openLinkEditing: openLinkEditing,
displaySlowmodeTooltip: { _, _ in
}, openLinkEditing: openLinkEditing, openDateEditing: {
}, displaySlowmodeTooltip: { _, _ in
}, displaySendMessageOptions: { _, _ in
}, openScheduledMessages: {
}, openPeersNearby: {

View file

@ -169,6 +169,57 @@ public func chatTextInputRemoveLinkAttribute(_ state: ChatTextInputState, select
}
}
public func chatTextInputAddDateAttribute(_ state: ChatTextInputState, selectionRange: Range<Int>, date: Int32) -> ChatTextInputState {
if !selectionRange.isEmpty {
let nsRange = NSRange(location: selectionRange.lowerBound, length: selectionRange.count)
var linkRange = nsRange
var attributesToRemove: [(NSAttributedString.Key, NSRange)] = []
state.inputText.enumerateAttributes(in: nsRange, options: .longestEffectiveRangeNotRequired) { attributes, range, stop in
for (key, _) in attributes {
if key == ChatTextInputAttributes.date {
attributesToRemove.append((key, range))
linkRange = linkRange.union(range)
} else {
attributesToRemove.append((key, nsRange))
}
}
}
let result = NSMutableAttributedString(attributedString: state.inputText)
for (attribute, range) in attributesToRemove {
result.removeAttribute(attribute, range: range)
}
result.addAttribute(ChatTextInputAttributes.date, value: ChatTextInputTextDateAttribute(date: date), range: nsRange)
return ChatTextInputState(inputText: result, selectionRange: selectionRange)
} else {
return state
}
}
public func chatTextInputRemoveDateAttribute(_ state: ChatTextInputState, selectionRange: Range<Int>) -> ChatTextInputState {
if !selectionRange.isEmpty {
let nsRange = NSRange(location: selectionRange.lowerBound, length: selectionRange.count)
var attributesToRemove: [(NSAttributedString.Key, NSRange)] = []
state.inputText.enumerateAttributes(in: nsRange, options: .longestEffectiveRangeNotRequired) { attributes, range, stop in
for (key, _) in attributes {
if key == ChatTextInputAttributes.date {
attributesToRemove.append((key, range))
} else {
attributesToRemove.append((key, nsRange))
}
}
}
let result = NSMutableAttributedString(attributedString: state.inputText)
for (attribute, range) in attributesToRemove {
result.removeAttribute(attribute, range: range)
}
return ChatTextInputState(inputText: result, selectionRange: selectionRange)
} else {
return state
}
}
public func chatTextInputAddMentionAttribute(_ state: ChatTextInputState, peer: EnginePeer) -> ChatTextInputState {
let inputText = NSMutableAttributedString(attributedString: state.inputText)

View file

@ -136,6 +136,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
}, updateInputLanguage: { _ in
}, unarchiveChat: {
}, openLinkEditing: {
}, openDateEditing: {
}, displaySlowmodeTooltip: { _, _ in
}, displaySendMessageOptions: { _, _ in
}, openScheduledMessages: {

View file

@ -652,6 +652,8 @@ public final class ChatTextInputPanelComponent: Component {
},
openLinkEditing: {
},
openDateEditing: {
},
displaySlowmodeTooltip: { _, _ in
},
displaySendMessageOptions: { [weak self] node, gesture in

View file

@ -4838,6 +4838,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
strongSelf.formatAttributesLink(strongSelf)
}
},
UIAction(title: self.strings?.TextFormat_Date ?? "Date", image: nil) { [weak self] (action) in
if let strongSelf = self {
strongSelf.formatAttributesDate(strongSelf)
}
},
UIAction(title: self.strings?.TextFormat_Strikethrough ?? "Strikethrough", image: nil) { [weak self] (action) in
if let strongSelf = self {
strongSelf.formatAttributesStrikethrough(strongSelf)
@ -4924,6 +4929,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.interfaceInteraction?.openLinkEditing()
}
@objc public func formatAttributesDate(_ sender: Any) {
self.inputMenu.back()
self.interfaceInteraction?.openDateEditing()
}
@objc public func formatAttributesStrikethrough(_ sender: Any) {
self.inputMenu.back()
self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in

View file

@ -65,7 +65,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
private let cancel = ComponentView<Empty>()
private let title = ComponentView<Empty>()
private let button = ComponentView<Empty>()
private let onlineButton = ComponentView<Empty>()
private let secondaryButton = ComponentView<Empty>()
private var datePicker: DatePickerNode?
@ -154,7 +154,17 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
self.environment = environment
if self.component == nil {
self.updateMinimumDate(currentTime: component.currentTime, minimalTime: component.minimalTime)
if case .format = component.mode {
self.minDate = Date(timeIntervalSince1970: 0.0)
self.maxDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
if let current = component.currentTime {
self.date = Date(timeIntervalSince1970: Double(current))
} else {
self.date = Date()
}
} else {
self.updateMinimumDate(currentTime: component.currentTime, minimalTime: component.minimalTime)
}
self.repeatPeriod = component.currentRepeatPeriod
}
@ -208,6 +218,9 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
title = strings.Conversation_ScheduleMessage_Title
case .reminders:
title = strings.Conversation_SetReminder_Title
case .format:
//TODO:localize
title = "Date"
}
let titleSize = self.title.update(
transition: transition,
@ -359,89 +372,94 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
contentHeight += 56.0
transition.setFrame(layer: self.bottomSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: UIScreenPixel)))
self.bottomSeparator.backgroundColor = environment.theme.list.itemBlocksSeparatorColor.cgColor
if self.bottomSeparator.superlayer == nil {
self.layer.addSublayer(self.bottomSeparator)
}
let repeatTitleSize = self.repeatTitle.update(
transition: transition,
component: AnyComponent(
Text(text: strings.ScheduleMessage_Repeat, font: Font.regular(17.0), color: environment.theme.actionSheet.primaryTextColor)
),
environment: {},
containerSize: availableSize
)
let repeatTitleFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 16.0), size: repeatTitleSize)
if let timeTitleView = self.repeatTitle.view {
if timeTitleView.superview == nil {
self.addSubview(timeTitleView)
}
transition.setFrame(view: timeTitleView, frame: repeatTitleFrame)
}
let repeatString: String
if let repeatPeriod = self.repeatPeriod {
switch repeatPeriod {
case 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_Daily
case 7 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_Weekly
case 14 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_Biweekly
case 30 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_Monthly
case 91 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_3Months
case 182 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_6Months
case 365 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_Yearly
default:
repeatString = "\(repeatPeriod)s"
}
var repeatValueFrame = CGRect()
if case .format = component.mode {
} else {
repeatString = strings.ScheduleMessage_RepeatPeriod_Never
}
let repeatValueSize = self.repeatValue.update(
transition: transition,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
ButtonContentComponent(
theme: environment.theme,
text: repeatString,
isActive: self.isPickingRepeatPeriod,
isLocked: !component.context.isPremium
)
),
action: { [weak self] in
guard let self else {
return
}
if self.isPickingTime {
self.isPickingTime = false
} else {
self.isPickingRepeatPeriod = !self.isPickingRepeatPeriod
}
self.state?.updated()
}
)
),
environment: {
},
containerSize: availableSize
)
let repeatValueFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - repeatValueSize.width, y: contentHeight + 10.0), size: repeatValueSize)
if let repeatValueView = self.repeatValue.view {
if repeatValueView.superview == nil {
self.addSubview(repeatValueView)
transition.setFrame(layer: self.bottomSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: UIScreenPixel)))
self.bottomSeparator.backgroundColor = environment.theme.list.itemBlocksSeparatorColor.cgColor
if self.bottomSeparator.superlayer == nil {
self.layer.addSublayer(self.bottomSeparator)
}
transition.setFrame(view: repeatValueView, frame: repeatValueFrame)
let repeatTitleSize = self.repeatTitle.update(
transition: transition,
component: AnyComponent(
Text(text: strings.ScheduleMessage_Repeat, font: Font.regular(17.0), color: environment.theme.actionSheet.primaryTextColor)
),
environment: {},
containerSize: availableSize
)
let repeatTitleFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 16.0), size: repeatTitleSize)
if let timeTitleView = self.repeatTitle.view {
if timeTitleView.superview == nil {
self.addSubview(timeTitleView)
}
transition.setFrame(view: timeTitleView, frame: repeatTitleFrame)
}
let repeatString: String
if let repeatPeriod = self.repeatPeriod {
switch repeatPeriod {
case 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_Daily
case 7 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_Weekly
case 14 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_Biweekly
case 30 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_Monthly
case 91 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_3Months
case 182 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_6Months
case 365 * 86400:
repeatString = strings.ScheduleMessage_RepeatPeriod_Yearly
default:
repeatString = "\(repeatPeriod)s"
}
} else {
repeatString = strings.ScheduleMessage_RepeatPeriod_Never
}
let repeatValueSize = self.repeatValue.update(
transition: transition,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
ButtonContentComponent(
theme: environment.theme,
text: repeatString,
isActive: self.isPickingRepeatPeriod,
isLocked: !component.context.isPremium
)
),
action: { [weak self] in
guard let self else {
return
}
if self.isPickingTime {
self.isPickingTime = false
} else {
self.isPickingRepeatPeriod = !self.isPickingRepeatPeriod
}
self.state?.updated()
}
)
),
environment: {
},
containerSize: availableSize
)
repeatValueFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - repeatValueSize.width, y: contentHeight + 10.0), size: repeatValueSize)
if let repeatValueView = self.repeatValue.view {
if repeatValueView.superview == nil {
self.addSubview(repeatValueView)
}
transition.setFrame(view: repeatValueView, frame: repeatValueFrame)
}
contentHeight += 70.0
}
contentHeight += 70.0
let time = stringForMessageTimestamp(timestamp: Int32(date.timeIntervalSince1970), dateTimeFormat: environment.dateTimeFormat)
let buttonTitle: String
@ -462,6 +480,9 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
} else {
buttonTitle = strings.Conversation_SetReminder_RemindOn(self.dateFormatter.string(from: date), time).string
}
case .format:
//TODO:localize
buttonTitle = component.currentTime != nil ? "Edit Date" : "Add Date"
}
let buttonSideInset: CGFloat = 30.0
@ -504,10 +525,51 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
}
contentHeight += buttonSize.height
if case .scheduledMessages(true) = component.mode {
if case .format = component.mode, component.currentTime != nil {
contentHeight += 8.0
let buttonSize = self.onlineButton.update(
let buttonSize = self.secondaryButton.update(
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
style: .glass,
color: environment.theme.list.itemDestructiveColor.withMultipliedAlpha(0.1),
foreground: environment.theme.list.itemDestructiveColor,
pressedColor: environment.theme.list.itemDestructiveColor.withMultipliedAlpha(0.8),
),
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
Text(text: "Remove Date", font: Font.semibold(17.0), color: environment.theme.list.itemDestructiveColor)
)),
isEnabled: true,
displaysProgress: false,
action: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() as? ChatScheduleTimeScreen else {
return
}
controller.completion(
ChatScheduleTimeScreen.Result(
time: 0,
repeatPeriod: nil
)
)
component.dismiss()
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 52.0)
)
let buttonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: contentHeight), size: buttonSize)
if let buttonView = self.secondaryButton.view {
if buttonView.superview == nil {
self.addSubview(buttonView)
}
transition.setFrame(view: buttonView, frame: buttonFrame)
}
contentHeight += buttonSize.height
} else if case .scheduledMessages(true) = component.mode {
contentHeight += 8.0
let buttonSize = self.secondaryButton.update(
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
@ -538,7 +600,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 52.0)
)
let buttonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: contentHeight), size: buttonSize)
if let buttonView = self.onlineButton.view {
if let buttonView = self.secondaryButton.view {
if buttonView.superview == nil {
self.addSubview(buttonView)
}
@ -844,6 +906,7 @@ public class ChatScheduleTimeScreen: ViewControllerComponentContainer {
public enum Mode: Equatable {
case scheduledMessages(sendWhenOnlineAvailable: Bool)
case reminders
case format
}
public struct Result {

View file

@ -128,6 +128,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, updateInputLanguage: { _ in
}, unarchiveChat: {
}, openLinkEditing: {
}, openDateEditing: {
}, displaySlowmodeTooltip: { _, _ in
}, displaySendMessageOptions: { _, _ in
}, openScheduledMessages: {

View file

@ -723,6 +723,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
})
strongSelf.present(controller, nil)
}
}, openDateEditing: {
}, displaySlowmodeTooltip: { _, _ in
}, displaySendMessageOptions: { [weak self] node, gesture in
guard let strongSelf = self else {

View file

@ -3685,6 +3685,62 @@ extension ChatControllerImpl {
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { $0.updatedInputMode({ _ in return .none }) })
}
}, openDateEditing: { [weak self] in
guard let self else {
return
}
var selectionRange: Range<Int>?
var text: NSAttributedString?
var inputMode: ChatInputMode?
self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
selectionRange = state.interfaceState.effectiveInputState.selectionRange
if let selectionRange = selectionRange {
text = state.interfaceState.effectiveInputState.inputText.attributedSubstring(from: NSRange(location: selectionRange.startIndex, length: selectionRange.count))
}
inputMode = state.inputMode
return state
})
var date: Int32?
if let text {
text.enumerateAttributes(in: NSMakeRange(0, text.length)) { attributes, _, _ in
if let dateAttribute = attributes[ChatTextInputAttributes.date] as? ChatTextInputTextDateAttribute {
date = dateAttribute.date
}
}
}
let controller = ChatScheduleTimeScreen(
context: self.context,
mode: .format,
currentTime: date,
currentRepeatPeriod: nil,
minimalTime: nil,
isDark: false,
completion: { [weak self] result in
guard let self, let inputMode = inputMode, let selectionRange = selectionRange else {
return
}
if result.time != 0 {
self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
return (chatTextInputAddDateAttribute(current, selectionRange: selectionRange, date: result.time), inputMode)
}
} else {
self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
return (chatTextInputRemoveDateAttribute(current, selectionRange: selectionRange), inputMode)
}
}
self.updateChatPresentationInterfaceState(animated: false, interactive: true, {
return $0.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({
$0.withUpdatedEffectiveInputState(ChatTextInputState(inputText: $0.effectiveInputState.inputText, selectionRange: selectionRange.endIndex ..< selectionRange.endIndex))
})
})
}
)
self.push(controller)
self.updateChatPresentationInterfaceState(animated: false, interactive: false, { $0.updatedInputMode({ _ in return .none }) })
}, displaySlowmodeTooltip: { [weak self] sourceView, nodeRect in
guard let strongSelf = self, let slowmodeState = strongSelf.presentationInterfaceState.slowmodeState else {
return

View file

@ -17,12 +17,13 @@ public struct ChatTextInputAttributes {
public static let underline = NSAttributedString.Key(rawValue: "Attribute__Underline")
public static let textMention = NSAttributedString.Key(rawValue: "Attribute__TextMention")
public static let textUrl = NSAttributedString.Key(rawValue: "Attribute__TextUrl")
public static let date = NSAttributedString.Key(rawValue: "Attribute__Date")
public static let spoiler = NSAttributedString.Key(rawValue: "Attribute__Spoiler")
public static let customEmoji = NSAttributedString.Key(rawValue: "Attribute__CustomEmoji")
public static let block = NSAttributedString.Key(rawValue: "Attribute__Blockquote")
public static let collapsedBlock = NSAttributedString.Key(rawValue: "Attribute__CollapsedBlockquote")
public static let allAttributes = [ChatTextInputAttributes.bold, ChatTextInputAttributes.italic, ChatTextInputAttributes.monospace, ChatTextInputAttributes.strikethrough, ChatTextInputAttributes.underline, ChatTextInputAttributes.textMention, ChatTextInputAttributes.textUrl, ChatTextInputAttributes.spoiler, ChatTextInputAttributes.customEmoji, ChatTextInputAttributes.block, ChatTextInputAttributes.collapsedBlock]
public static let allAttributes = [ChatTextInputAttributes.bold, ChatTextInputAttributes.italic, ChatTextInputAttributes.monospace, ChatTextInputAttributes.strikethrough, ChatTextInputAttributes.underline, ChatTextInputAttributes.textMention, ChatTextInputAttributes.textUrl, ChatTextInputAttributes.date, ChatTextInputAttributes.spoiler, ChatTextInputAttributes.customEmoji, ChatTextInputAttributes.block, ChatTextInputAttributes.collapsedBlock]
}
public let originalTextAttributeKey = NSAttributedString.Key(rawValue: "Attribute__OriginalText")
@ -237,7 +238,7 @@ public func textAttributedStringForStateText(context: AnyObject, stateText: NSAt
var fontAttributes: ChatTextFontAttributes = []
for (key, value) in attributes {
if key == ChatTextInputAttributes.textMention || key == ChatTextInputAttributes.textUrl {
if key == ChatTextInputAttributes.textMention || key == ChatTextInputAttributes.textUrl || key == ChatTextInputAttributes.date {
result.addAttribute(key, value: value, range: range)
result.addAttribute(NSAttributedString.Key.foregroundColor, value: accentTextColor, range: range)
if accentTextColor.isEqual(textColor) {
@ -363,6 +364,24 @@ public final class ChatTextInputTextUrlAttribute: NSObject {
}
}
public final class ChatTextInputTextDateAttribute: NSObject {
public let date: Int32
public init(date: Int32) {
self.date = date
super.init()
}
override public func isEqual(_ object: Any?) -> Bool {
if let other = object as? ChatTextInputTextDateAttribute {
return self.date == other.date
} else {
return false
}
}
}
public final class ChatTextInputTextQuoteAttribute: NSObject {
public enum Kind: Equatable {
case quote
@ -836,6 +855,7 @@ public func refreshChatTextInputAttributes(context: AnyObject, textView: UITextV
textView.textStorage.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: fullRange)
textView.textStorage.removeAttribute(ChatTextInputAttributes.textMention, range: fullRange)
textView.textStorage.removeAttribute(ChatTextInputAttributes.textUrl, range: fullRange)
textView.textStorage.removeAttribute(ChatTextInputAttributes.date, range: fullRange)
textView.textStorage.removeAttribute(ChatTextInputAttributes.spoiler, range: fullRange)
textView.textStorage.removeAttribute(ChatTextInputAttributes.customEmoji, range: fullRange)
textView.textStorage.removeAttribute(ChatTextInputAttributes.block, range: fullRange)
@ -850,7 +870,7 @@ public func refreshChatTextInputAttributes(context: AnyObject, textView: UITextV
var fontAttributes: ChatTextFontAttributes = []
for (key, value) in attributes {
if key == ChatTextInputAttributes.textMention || key == ChatTextInputAttributes.textUrl {
if key == ChatTextInputAttributes.textMention || key == ChatTextInputAttributes.textUrl || key == ChatTextInputAttributes.date {
textView.textStorage.addAttribute(key, value: value, range: range)
textView.textStorage.addAttribute(NSAttributedString.Key.foregroundColor, value: accentTextColor, range: range)
@ -964,6 +984,7 @@ public func refreshGenericTextInputAttributes(context: AnyObject, textView: UITe
textView.textStorage.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: fullRange)
textView.textStorage.removeAttribute(ChatTextInputAttributes.textMention, range: fullRange)
textView.textStorage.removeAttribute(ChatTextInputAttributes.textUrl, range: fullRange)
textView.textStorage.removeAttribute(ChatTextInputAttributes.date, range: fullRange)
textView.textStorage.removeAttribute(ChatTextInputAttributes.spoiler, range: fullRange)
textView.textStorage.addAttribute(NSAttributedString.Key.font, value: Font.regular(baseFontSize), range: fullRange)
@ -973,7 +994,7 @@ public func refreshGenericTextInputAttributes(context: AnyObject, textView: UITe
var fontAttributes: ChatTextFontAttributes = []
for (key, value) in attributes {
if key == ChatTextInputAttributes.textMention || key == ChatTextInputAttributes.textUrl {
if key == ChatTextInputAttributes.textMention || key == ChatTextInputAttributes.textUrl || key == ChatTextInputAttributes.date {
textView.textStorage.addAttribute(key, value: value, range: range)
textView.textStorage.addAttribute(NSAttributedString.Key.foregroundColor, value: theme.chat.inputPanel.panelControlAccentColor, range: range)

View file

@ -167,6 +167,8 @@ public func generateChatInputTextEntities(_ text: NSAttributedString, maxAnimate
entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .TextMention(peerId: value.peerId)))
} else if key == ChatTextInputAttributes.textUrl, let value = value as? ChatTextInputTextUrlAttribute {
entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .TextUrl(url: value.url)))
} else if key == ChatTextInputAttributes.date, let value = value as? ChatTextInputTextDateAttribute {
entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .FormattedDate(format: nil, date: value.date)))
} else if key == ChatTextInputAttributes.spoiler {
entities.append(MessageTextEntity(range: range.lowerBound ..< range.upperBound, type: .Spoiler))
} else if key == ChatTextInputAttributes.customEmoji, let value = value as? ChatTextInputTextCustomEmojiAttribute {

View file

@ -39,6 +39,8 @@ public func chatInputStateStringWithAppliedEntities(_ text: String, entities: [M
string.addAttribute(ChatTextInputAttributes.textMention, value: ChatTextInputTextMentionAttribute(peerId: peerId), range: range)
case let .TextUrl(url):
string.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: url), range: range)
case let .FormattedDate(_, date):
string.addAttribute(ChatTextInputAttributes.date, value: ChatTextInputTextDateAttribute(date: date), range: range)
case .Code:
string.addAttribute(ChatTextInputAttributes.monospace, value: true as NSNumber, range: range)
case .Strikethrough: