diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 97a0bfa489..c7e9962184 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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"; diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 46f0a8defd..7e9aef8bb9 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -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 { diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index 4929856d01..9aa995b19c 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -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: { diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatTextFormat.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatTextFormat.swift index a0f301fccf..1444788525 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatTextFormat.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatTextFormat.swift @@ -169,6 +169,57 @@ public func chatTextInputRemoveLinkAttribute(_ state: ChatTextInputState, select } } +public func chatTextInputAddDateAttribute(_ state: ChatTextInputState, selectionRange: Range, 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) -> 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) diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index 461d248957..93d7b85687 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -136,6 +136,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, updateInputLanguage: { _ in }, unarchiveChat: { }, openLinkEditing: { + }, openDateEditing: { }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { _, _ in }, openScheduledMessages: { diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift index 692a8e7f2e..d3920e7d92 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift @@ -652,6 +652,8 @@ public final class ChatTextInputPanelComponent: Component { }, openLinkEditing: { }, + openDateEditing: { + }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { [weak self] node, gesture in diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index b5f761b536..ad3ab67515 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift index 59cc9551b8..6f6f7d6137 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift @@ -65,7 +65,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { private let cancel = ComponentView() private let title = ComponentView() private let button = ComponentView() - private let onlineButton = ComponentView() + private let secondaryButton = ComponentView() 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 { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift index 8643281272..5ed1e9a78c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift @@ -128,6 +128,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, updateInputLanguage: { _ in }, unarchiveChat: { }, openLinkEditing: { + }, openDateEditing: { }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { _, _ in }, openScheduledMessages: { diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index e8d788e1f8..076445ecca 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 7f0c23b413..ed70252f28 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -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? + 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 diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index 50ee9631cf..61dde36ecb 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -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) diff --git a/submodules/TextFormat/Sources/GenerateTextEntities.swift b/submodules/TextFormat/Sources/GenerateTextEntities.swift index 7b0969e033..c64a417d28 100644 --- a/submodules/TextFormat/Sources/GenerateTextEntities.swift +++ b/submodules/TextFormat/Sources/GenerateTextEntities.swift @@ -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 { diff --git a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift index a9e2f1275e..35cd855bfb 100644 --- a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift +++ b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift @@ -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: