mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit 'e81e7c6897'
This commit is contained in:
commit
25952cfef6
20 changed files with 313 additions and 44 deletions
|
|
@ -15769,3 +15769,17 @@ Error: %8$@";
|
|||
"Gift.View.Context.Craft" = "Craft";
|
||||
|
||||
"Group.OwnershipTransfer.DescriptionShortInfo" = "This will transfer the full **owner rights** for **%1$@** to **%2$@**.";
|
||||
|
||||
"FormattedDate.InMinutes_1" = "in %@ minute";
|
||||
"FormattedDate.InMinutes_any" = "in %@ minutes";
|
||||
"FormattedDate.InHours_1" = "in %@ hour";
|
||||
"FormattedDate.InHours_any" = "in %@ hours";
|
||||
"FormattedDate.InDays_1" = "in %@ day";
|
||||
"FormattedDate.InDays_any" = "in %@ days";
|
||||
|
||||
"FormattedDate.MinutesAgo_1" = "%@ minute ago";
|
||||
"FormattedDate.MinutesAgo_any" = "%@ minutes ago";
|
||||
"FormattedDate.HoursAgo_1" = "%@ hour ago";
|
||||
"FormattedDate.HoursAgo_any" = "%@ hours ago";
|
||||
"FormattedDate.DaysAgo_1" = "%@ day ago";
|
||||
"FormattedDate.DaysAgo_any" = "%@ days ago";
|
||||
|
|
|
|||
|
|
@ -255,6 +255,7 @@ public enum ChatControllerInteractionLongTapAction {
|
|||
case hashtag(String)
|
||||
case timecode(Double, String)
|
||||
case bankCard(String)
|
||||
case date(Int32)
|
||||
}
|
||||
|
||||
public enum ChatHistoryMessageSelection: Equatable {
|
||||
|
|
|
|||
|
|
@ -791,21 +791,21 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
|
|||
if (flags & (1 << 0)) != 0 {
|
||||
format = .relative
|
||||
} else {
|
||||
let timeFormat: MessageTextEntityType.DateTimeFormat.TimeFormat
|
||||
let timeFormat: MessageTextEntityType.DateTimeFormat.TimeFormat?
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
timeFormat = .short
|
||||
} else if (flags & (1 << 2)) != 0 {
|
||||
timeFormat = .long
|
||||
} else {
|
||||
timeFormat = .short
|
||||
timeFormat = nil
|
||||
}
|
||||
let dateFormat: MessageTextEntityType.DateTimeFormat.DateFormat
|
||||
let dateFormat: MessageTextEntityType.DateTimeFormat.DateFormat?
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
dateFormat = .short
|
||||
} else if (flags & (1 << 4)) != 0 {
|
||||
dateFormat = .long
|
||||
} else {
|
||||
dateFormat = .short
|
||||
dateFormat = nil
|
||||
}
|
||||
format = .full(timeFormat: timeFormat, dateFormat: dateFormat)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,12 +65,16 @@ func apiEntitiesFromMessageTextEntities(_ entities: [MessageTextEntity], associa
|
|||
flags |= 1 << 1
|
||||
case .long:
|
||||
flags |= 1 << 2
|
||||
default:
|
||||
break
|
||||
}
|
||||
switch dateFormat {
|
||||
case .short:
|
||||
flags |= 1 << 3
|
||||
case .long:
|
||||
flags |= 1 << 4
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
apiEntities.append(.messageEntityFormattedDate(.init(flags: flags, offset: offset, length: length, date: date)))
|
||||
|
|
|
|||
|
|
@ -16,27 +16,27 @@ public enum MessageTextEntityType: Equatable {
|
|||
}
|
||||
|
||||
case relative
|
||||
case full(timeFormat: TimeFormat, dateFormat: DateFormat)
|
||||
case full(timeFormat: TimeFormat?, dateFormat: DateFormat?)
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
if (rawValue & (1 << 0)) != 0 {
|
||||
self = .relative
|
||||
} else {
|
||||
let timeFormat: MessageTextEntityType.DateTimeFormat.TimeFormat
|
||||
let timeFormat: MessageTextEntityType.DateTimeFormat.TimeFormat?
|
||||
if (rawValue & (1 << 1)) != 0 {
|
||||
timeFormat = .short
|
||||
} else if (rawValue & (1 << 2)) != 0 {
|
||||
timeFormat = .long
|
||||
} else {
|
||||
timeFormat = .short
|
||||
timeFormat = nil
|
||||
}
|
||||
let dateFormat: MessageTextEntityType.DateTimeFormat.DateFormat
|
||||
let dateFormat: MessageTextEntityType.DateTimeFormat.DateFormat?
|
||||
if (rawValue & (1 << 3)) != 0 {
|
||||
dateFormat = .short
|
||||
} else if (rawValue & (1 << 4)) != 0 {
|
||||
dateFormat = .long
|
||||
} else {
|
||||
dateFormat = .short
|
||||
dateFormat = nil
|
||||
}
|
||||
self = .full(timeFormat: timeFormat, dateFormat: dateFormat)
|
||||
}
|
||||
|
|
@ -53,12 +53,16 @@ public enum MessageTextEntityType: Equatable {
|
|||
rawValue |= 1 << 1
|
||||
case .long:
|
||||
rawValue |= 1 << 2
|
||||
default:
|
||||
break
|
||||
}
|
||||
switch dateFormat {
|
||||
case .short:
|
||||
rawValue |= 1 << 3
|
||||
case .long:
|
||||
rawValue |= 1 << 4
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return rawValue
|
||||
|
|
|
|||
|
|
@ -1,8 +1,60 @@
|
|||
import Foundation
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
|
||||
public func stringForShortTimestamp(hours: Int32, minutes: Int32, dateTimeFormat: PresentationDateTimeFormat, formatAsPlainText: Bool = false) -> String {
|
||||
public func stringForEntityFormattedDate(timestamp: Int32, format: MessageTextEntityType.DateTimeFormat, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> String {
|
||||
switch format {
|
||||
case .relative:
|
||||
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
let value = currentTimestamp - timestamp
|
||||
if value > 0 {
|
||||
if value <= 1 * 60 * 60 {
|
||||
return strings.FormattedDate_MinutesAgo(Int32(round(Float(value) / 60)))
|
||||
} else if value <= 24 * 60 * 60 {
|
||||
return strings.FormattedDate_HoursAgo(Int32(round(Float(value) / (60 * 60))))
|
||||
} else {
|
||||
return strings.FormattedDate_DaysAgo(Int32(round(Float(value) / (24 * 60 * 60))))
|
||||
}
|
||||
} else {
|
||||
let value = abs(value)
|
||||
if value <= 1 * 60 * 60 {
|
||||
return strings.FormattedDate_InMinutes(Int32(round(Float(value) / 60)))
|
||||
} else if value <= 24 * 60 * 60 {
|
||||
return strings.FormattedDate_InHours(Int32(round(Float(value) / (60 * 60))))
|
||||
} else {
|
||||
return strings.FormattedDate_InDays(Int32(round(Float(value) / (24 * 60 * 60))))
|
||||
}
|
||||
}
|
||||
case let .full(timeFormat, dateFormat):
|
||||
var string = ""
|
||||
if let dateFormat {
|
||||
switch dateFormat {
|
||||
case .short:
|
||||
string += stringForMediumDate(timestamp: timestamp, strings: strings, dateTimeFormat: dateTimeFormat, withTime: false)
|
||||
case .long:
|
||||
string += stringForFullDate(timestamp: timestamp, strings: strings, dateTimeFormat: dateTimeFormat)
|
||||
}
|
||||
}
|
||||
if let timeFormat {
|
||||
let timeString: String
|
||||
switch timeFormat {
|
||||
case .short:
|
||||
timeString = stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: dateTimeFormat)
|
||||
case .long:
|
||||
timeString = stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: dateTimeFormat, withSeconds: true)
|
||||
}
|
||||
if !string.isEmpty {
|
||||
string = strings.Time_AtPreciseDate(string, timeString).string
|
||||
} else {
|
||||
string = timeString
|
||||
}
|
||||
}
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
public func stringForShortTimestamp(hours: Int32, minutes: Int32, seconds: Int32? = nil, dateTimeFormat: PresentationDateTimeFormat, formatAsPlainText: Bool = false) -> String {
|
||||
switch dateTimeFormat.timeFormat {
|
||||
case .regular:
|
||||
let hourString: String
|
||||
|
|
@ -28,17 +80,23 @@ public func stringForShortTimestamp(hours: Int32, minutes: Int32, dateTimeFormat
|
|||
spaceCharacter = "\u{00a0}"
|
||||
}
|
||||
|
||||
if minutes >= 10 {
|
||||
return "\(hourString):\(minutes)\(spaceCharacter)\(periodString)"
|
||||
let minuteString = String(format: "%02d", arguments: [Int(minutes)])
|
||||
if let seconds {
|
||||
let secondString = String(format: "%02d", arguments: [Int(seconds)])
|
||||
return "\(hourString):\(minuteString):\(secondString)\(spaceCharacter)\(periodString)"
|
||||
} else {
|
||||
return "\(hourString):0\(minutes)\(spaceCharacter)\(periodString)"
|
||||
return "\(hourString):\(minuteString)\(spaceCharacter)\(periodString)"
|
||||
}
|
||||
case .military:
|
||||
return String(format: "%02d:%02d", arguments: [Int(hours), Int(minutes)])
|
||||
if let seconds {
|
||||
return String(format: "%02d:%02d:%02d", arguments: [Int(hours), Int(minutes), Int(seconds)])
|
||||
} else {
|
||||
return String(format: "%02d:%02d", arguments: [Int(hours), Int(minutes)])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func stringForMessageTimestamp(timestamp: Int32, dateTimeFormat: PresentationDateTimeFormat, local: Bool = true) -> String {
|
||||
public func stringForMessageTimestamp(timestamp: Int32, dateTimeFormat: PresentationDateTimeFormat, withSeconds: Bool = false, local: Bool = true) -> String {
|
||||
var t = Int(timestamp)
|
||||
var timeinfo = tm()
|
||||
if local {
|
||||
|
|
@ -47,7 +105,7 @@ public func stringForMessageTimestamp(timestamp: Int32, dateTimeFormat: Presenta
|
|||
gmtime_r(&t, &timeinfo)
|
||||
}
|
||||
|
||||
return stringForShortTimestamp(hours: timeinfo.tm_hour, minutes: timeinfo.tm_min, dateTimeFormat: dateTimeFormat)
|
||||
return stringForShortTimestamp(hours: timeinfo.tm_hour, minutes: timeinfo.tm_min, seconds: withSeconds ? timeinfo.tm_sec : nil, dateTimeFormat: dateTimeFormat)
|
||||
}
|
||||
|
||||
public func getDateTimeComponents(timestamp: Int32) -> (day: Int32, month: Int32, year: Int32, hour: Int32, minutes: Int32) {
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
|
|||
break
|
||||
case .ignore:
|
||||
return .fail
|
||||
case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .conferenceCall, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .custom:
|
||||
case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .conferenceCall, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .date, .custom:
|
||||
return .waitForSingleTap
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1672,7 +1672,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||
TelegramTextAttributes.PeerTextMention,
|
||||
TelegramTextAttributes.BotCommand,
|
||||
TelegramTextAttributes.Hashtag,
|
||||
TelegramTextAttributes.BankCard
|
||||
TelegramTextAttributes.BankCard,
|
||||
TelegramTextAttributes.Date
|
||||
]
|
||||
for name in possibleNames {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ public struct ChatMessageBubbleContentTapAction {
|
|||
case ignore
|
||||
case openPollResults(Data)
|
||||
case copy(String)
|
||||
case date(Int32, String)
|
||||
case largeEmoji(String, String?, TelegramMediaFile)
|
||||
case customEmoji(TelegramMediaFile)
|
||||
case custom(() -> Void)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import PersistentStringHash
|
|||
import GridMessageSelectionNode
|
||||
import AppBundle
|
||||
import Markdown
|
||||
import TelegramStringFormatting
|
||||
import WallpaperBackgroundNode
|
||||
import ChatPresentationInterfaceState
|
||||
import ChatMessageBackground
|
||||
|
|
@ -1296,7 +1297,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
break
|
||||
case .ignore:
|
||||
return .fail
|
||||
case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .conferenceCall, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .custom:
|
||||
case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .conferenceCall, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .date, .custom:
|
||||
return .waitForSingleTap
|
||||
}
|
||||
}
|
||||
|
|
@ -5658,6 +5659,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
item.controllerInteraction.openLargeEmojiInfo(emoji, fitz, file)
|
||||
})
|
||||
}
|
||||
case let .date(date, _):
|
||||
if let item = self.item {
|
||||
return .action(InternalBubbleTapAction.Action { [weak self] in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(stringForFullDate(timestamp: date, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat), rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.date(date), ChatControllerInteraction.LongTapParams(message: item.message, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
|
||||
})
|
||||
}
|
||||
case let .customEmoji(file):
|
||||
if let item = self.item {
|
||||
return .optionalAction({
|
||||
|
|
@ -5824,6 +5834,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
break
|
||||
case .customEmoji:
|
||||
break
|
||||
case let .date(date, _):
|
||||
return .action(InternalBubbleTapAction.Action { [weak self] in
|
||||
guard let self, let contentNode = self.contextContentNodeForLink(stringForFullDate(timestamp: date, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat), rects: rects) else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.longTap(.date(date), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?()))
|
||||
})
|
||||
case .custom:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -233,7 +233,8 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
|
|||
TelegramTextAttributes.PeerTextMention,
|
||||
TelegramTextAttributes.BotCommand,
|
||||
TelegramTextAttributes.Hashtag,
|
||||
TelegramTextAttributes.BankCard
|
||||
TelegramTextAttributes.BankCard,
|
||||
TelegramTextAttributes.Date
|
||||
]
|
||||
for name in possibleNames {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
|
||||
|
|
|
|||
|
|
@ -1149,7 +1149,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
TelegramTextAttributes.BotCommand,
|
||||
TelegramTextAttributes.Hashtag,
|
||||
TelegramTextAttributes.Timecode,
|
||||
TelegramTextAttributes.BankCard
|
||||
TelegramTextAttributes.BankCard,
|
||||
TelegramTextAttributes.Date
|
||||
]
|
||||
for name in possibleNames {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
|
||||
|
|
@ -1202,6 +1203,12 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
return ChatMessageBubbleContentTapAction(content: .copy(pre))
|
||||
} else if let code = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Code)] as? String {
|
||||
return ChatMessageBubbleContentTapAction(content: .copy(code))
|
||||
} else if let date = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Date)] as? Int32 {
|
||||
var string = ""
|
||||
if let (_, text, _) = self.textNode.textNode.attributeSubstringWithRange(name: TelegramTextAttributes.Date, index: index) {
|
||||
string = text
|
||||
}
|
||||
return ChatMessageBubbleContentTapAction(content: .date(date, string), rects: rects)
|
||||
} else if let _ = attributes[NSAttributedString.Key(rawValue: "Attribute__Blockquote")] {
|
||||
if let _ = self.textNode.textNode.collapsibleBlockAtPoint(textLocalPoint) {
|
||||
return ChatMessageBubbleContentTapAction(content: .none)
|
||||
|
|
@ -1296,7 +1303,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
TelegramTextAttributes.BotCommand,
|
||||
TelegramTextAttributes.Hashtag,
|
||||
TelegramTextAttributes.Timecode,
|
||||
TelegramTextAttributes.BankCard
|
||||
TelegramTextAttributes.BankCard,
|
||||
TelegramTextAttributes.Date
|
||||
]
|
||||
for name in possibleNames {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
|
||||
|
|
|
|||
|
|
@ -686,7 +686,8 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
|||
TelegramTextAttributes.PeerTextMention,
|
||||
TelegramTextAttributes.BotCommand,
|
||||
TelegramTextAttributes.Hashtag,
|
||||
TelegramTextAttributes.BankCard
|
||||
TelegramTextAttributes.BankCard,
|
||||
TelegramTextAttributes.Date
|
||||
]
|
||||
for name in possibleNames {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: name)], let textRects = textNode.textNode.attributeRects(name: name, at: index) {
|
||||
|
|
@ -1584,7 +1585,8 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
TelegramTextAttributes.PeerTextMention,
|
||||
TelegramTextAttributes.BotCommand,
|
||||
TelegramTextAttributes.Hashtag,
|
||||
TelegramTextAttributes.BankCard
|
||||
TelegramTextAttributes.BankCard,
|
||||
TelegramTextAttributes.Date
|
||||
]
|
||||
for name in possibleNames {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: name)], let textRects = self.textNode.textNode.attributeRects(name: name, at: index) {
|
||||
|
|
|
|||
|
|
@ -566,6 +566,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||
strongSelf.presentController(actionSheet, .window(.root), nil)
|
||||
case .bankCard:
|
||||
break
|
||||
case .date:
|
||||
break
|
||||
}
|
||||
}
|
||||
}, todoItemLongTap: { _, _ in
|
||||
|
|
|
|||
|
|
@ -564,7 +564,8 @@ final class StoryContentCaptionComponent: Component {
|
|||
TelegramTextAttributes.BotCommand,
|
||||
TelegramTextAttributes.Hashtag,
|
||||
TelegramTextAttributes.Timecode,
|
||||
TelegramTextAttributes.BankCard
|
||||
TelegramTextAttributes.BankCard,
|
||||
TelegramTextAttributes.Date
|
||||
]
|
||||
for name in possibleNames {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ContextUI
|
||||
import UndoUI
|
||||
import AccountContext
|
||||
import ChatMessageItemView
|
||||
import ChatMessageItemCommon
|
||||
import ChatControllerInteraction
|
||||
import EventKit
|
||||
import EventKitUI
|
||||
|
||||
extension ChatControllerImpl: EKEventEditViewDelegate {
|
||||
func openDateContextMenu(date: Int32, params: ChatControllerInteraction.LongTapParams) -> Void {
|
||||
guard let message = params.message, let contentNode = params.contentNode else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) else {
|
||||
return
|
||||
}
|
||||
|
||||
var updatedMessages = messages
|
||||
for i in 0 ..< updatedMessages.count {
|
||||
if updatedMessages[i].id == message.id {
|
||||
let message = updatedMessages.remove(at: i)
|
||||
updatedMessages.insert(message, at: 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer
|
||||
let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture
|
||||
|
||||
let source: ContextContentSource
|
||||
// if let location = location {
|
||||
// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y)))
|
||||
// } else {
|
||||
source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode))
|
||||
// }
|
||||
|
||||
//TODO:localize
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(
|
||||
.action(ContextMenuActionItem(text: "Copy Date", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
UIPasteboard.general.string = "\(date)"
|
||||
|
||||
self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_CardNumberCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
}))
|
||||
)
|
||||
items.append(
|
||||
.action(ContextMenuActionItem(text: "Add to Calendar", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let eventStore = EKEventStore()
|
||||
let event = EKEvent(eventStore: eventStore)
|
||||
event.startDate = Date(timeIntervalSince1970: Double(date))
|
||||
event.endDate = Date(timeIntervalSince1970: Double(date + 3600))
|
||||
|
||||
let editViewController = EKEventEditViewController()
|
||||
editViewController.eventStore = eventStore
|
||||
editViewController.event = event
|
||||
editViewController.editViewDelegate = self
|
||||
|
||||
if let rootController = self.navigationController?.view.window?.rootViewController {
|
||||
rootController.present(editViewController, animated: true)
|
||||
}
|
||||
}))
|
||||
)
|
||||
|
||||
self.canReadHistory.set(false)
|
||||
|
||||
let controller = makeContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture, disableScreenshots: false)
|
||||
controller.dismissed = { [weak self] in
|
||||
self?.canReadHistory.set(true)
|
||||
}
|
||||
|
||||
self.window?.presentInGlobalOverlay(controller)
|
||||
}
|
||||
|
||||
public func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) {
|
||||
controller.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,8 @@ extension ChatControllerImpl {
|
|||
self.openBankCardContextMenu(number: number, params: params)
|
||||
case let .phone(number):
|
||||
self.openPhoneContextMenu(number: number, params: params)
|
||||
case let .date(date):
|
||||
self.openDateContextMenu(date: date, params: params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ private let whitelistedHosts: Set<String> = Set([
|
|||
private let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link]).rawValue)
|
||||
private let dataAndPhoneNumberDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link, .phoneNumber]).rawValue)
|
||||
private let phoneNumberDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.phoneNumber]).rawValue)
|
||||
private let dataAndPhoneNumberAndDateDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link, .phoneNumber, .date]).rawValue)
|
||||
|
||||
private let validHashtagSet: CharacterSet = {
|
||||
var set = CharacterSet.alphanumerics
|
||||
set.insert("_")
|
||||
|
|
@ -100,8 +102,9 @@ public struct EnabledEntityTypes: OptionSet {
|
|||
public static let timecode = EnabledEntityTypes(rawValue: 1 << 5)
|
||||
public static let external = EnabledEntityTypes(rawValue: 1 << 6)
|
||||
public static let internalUrl = EnabledEntityTypes(rawValue: 1 << 7)
|
||||
public static let date = EnabledEntityTypes(rawValue: 1 << 8)
|
||||
|
||||
public static let all: EnabledEntityTypes = [.command, .mention, .hashtag, .allUrl, .phoneNumber]
|
||||
public static let all: EnabledEntityTypes = [.command, .mention, .hashtag, .allUrl, .phoneNumber, .date]
|
||||
}
|
||||
|
||||
private func commitEntity(_ utf16: String.UTF16View, _ type: CurrentEntityType, _ range: Range<String.UTF16View.Index>, _ enabledTypes: EnabledEntityTypes, _ entities: inout [MessageTextEntity], mediaDuration: Double? = nil) {
|
||||
|
|
@ -254,7 +257,9 @@ public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityType
|
|||
let utf16 = text.utf16
|
||||
|
||||
var detector: NSDataDetector?
|
||||
if enabledTypes.contains(.phoneNumber) && (enabledTypes.contains(.allUrl) || enabledTypes.contains(.internalUrl)) {
|
||||
if enabledTypes.contains(.phoneNumber) && enabledTypes.contains(.date) && (enabledTypes.contains(.allUrl) || enabledTypes.contains(.internalUrl)) {
|
||||
detector = dataAndPhoneNumberAndDateDetector
|
||||
} else if enabledTypes.contains(.phoneNumber) && (enabledTypes.contains(.allUrl) || enabledTypes.contains(.internalUrl)) {
|
||||
detector = dataAndPhoneNumberDetector
|
||||
} else if enabledTypes.contains(.phoneNumber) {
|
||||
detector = phoneNumberDetector
|
||||
|
|
@ -267,7 +272,7 @@ public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityType
|
|||
if let detector = detector {
|
||||
detector.enumerateMatches(in: text, options: [], range: NSMakeRange(0, utf16.count), using: { result, _, _ in
|
||||
if let result = result {
|
||||
if result.resultType == NSTextCheckingResult.CheckingType.link || result.resultType == NSTextCheckingResult.CheckingType.phoneNumber {
|
||||
if [NSTextCheckingResult.CheckingType.link, NSTextCheckingResult.CheckingType.phoneNumber, NSTextCheckingResult.CheckingType.date].contains(result.resultType) {
|
||||
let lowerBound = utf16.index(utf16.startIndex, offsetBy: result.range.location).samePosition(in: text)
|
||||
let upperBound = utf16.index(utf16.startIndex, offsetBy: result.range.location + result.range.length).samePosition(in: text)
|
||||
if let lowerBound = lowerBound, let upperBound = upperBound {
|
||||
|
|
@ -293,6 +298,8 @@ public func generateTextEntities(_ text: String, enabledTypes: EnabledEntityType
|
|||
}
|
||||
|
||||
type = .Url
|
||||
} else if result.resultType == NSTextCheckingResult.CheckingType.date, let date = result.date?.timeIntervalSince1970 {
|
||||
type = .FormattedDate(format: .relative, date: Int32(date))
|
||||
} else {
|
||||
type = .PhoneNumber
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,13 +84,47 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
let baseQuoteTintColor = baseQuoteTintColor ?? baseColor
|
||||
|
||||
var nsString: NSString?
|
||||
let string = NSMutableAttributedString(string: text, attributes: [NSAttributedString.Key.font: baseFont, NSAttributedString.Key.foregroundColor: baseColor])
|
||||
let baseAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: baseFont, NSAttributedString.Key.foregroundColor: baseColor]
|
||||
let string = NSMutableAttributedString(string: text, attributes: baseAttributes)
|
||||
var skipEntity = false
|
||||
var underlineAllLinks = false
|
||||
if linkColor.argb == baseColor.argb {
|
||||
underlineAllLinks = true
|
||||
}
|
||||
|
||||
var adjustedRanges: [NSRange?] = []
|
||||
adjustedRanges.reserveCapacity(entities.count)
|
||||
let rangeDelta = 0
|
||||
for entity in entities {
|
||||
let originalRange = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
|
||||
var range = NSRange(location: originalRange.location + rangeDelta, length: originalRange.length)
|
||||
let stringLength = string.length
|
||||
if range.location > stringLength {
|
||||
adjustedRanges.append(nil)
|
||||
continue
|
||||
} else if range.location + range.length > stringLength {
|
||||
range.length = stringLength - range.location
|
||||
}
|
||||
|
||||
// switch entity.type {
|
||||
// case let .FormattedDate(format, date):
|
||||
// let replacement = stringForEntityFormattedDate()
|
||||
// switch format {
|
||||
// case .relative:
|
||||
// replacement = relative
|
||||
// case let .full(timeFormat, dateFormat):
|
||||
// }
|
||||
//
|
||||
// let replacementString = NSAttributedString(string: replacement, attributes: baseAttributes)
|
||||
// string.replaceCharacters(in: range, with: replacementString)
|
||||
// let newRange = NSRange(location: range.location, length: (replacement as NSString).length)
|
||||
// adjustedRanges.append(newRange)
|
||||
// rangeDelta += newRange.length - range.length
|
||||
// default:
|
||||
adjustedRanges.append(range)
|
||||
// }
|
||||
}
|
||||
|
||||
var fontAttributeMask: [ChatTextFontAttributes] = Array(repeating: [], count: string.length)
|
||||
let addFontAttributes: (NSRange, ChatTextFontAttributes) -> Void = { range, attributes in
|
||||
for i in range.lowerBound ..< range.upperBound {
|
||||
|
|
@ -103,12 +137,11 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
skipEntity = false
|
||||
continue
|
||||
}
|
||||
let stringLength = string.length
|
||||
let entity = entities[i]
|
||||
var range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
guard var range = adjustedRanges[i] else {
|
||||
continue
|
||||
}
|
||||
let stringLength = string.length
|
||||
if range.location > stringLength {
|
||||
continue
|
||||
} else if range.location + range.length > stringLength {
|
||||
|
|
@ -121,13 +154,13 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
string.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
|
||||
}
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.URL), value: nsString!.substring(with: range), range: range)
|
||||
case .Email:
|
||||
string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range)
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
if underlineLinks && underlineAllLinks {
|
||||
string.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
|
||||
|
|
@ -136,7 +169,7 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
case .PhoneNumber:
|
||||
string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range)
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
if underlineLinks && underlineAllLinks {
|
||||
string.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
|
||||
|
|
@ -145,7 +178,7 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
case let .TextUrl(url):
|
||||
string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range)
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
if underlineLinks && underlineAllLinks {
|
||||
string.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
|
||||
|
|
@ -168,7 +201,7 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
string.addAttribute(NSAttributedString.Key.font, value: linkFont, range: range)
|
||||
}
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention), value: nsString!.substring(with: range), range: range)
|
||||
case .Strikethrough:
|
||||
|
|
@ -183,16 +216,21 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
if linkFont !== baseFont {
|
||||
string.addAttribute(NSAttributedString.Key.font, value: linkFont, range: range)
|
||||
}
|
||||
if nsString == nil {
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
let mention = nsString!.substring(with: range)
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention), value: TelegramPeerMention(peerId: peerId, mention: mention), range: range)
|
||||
case .Hashtag:
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
let hashtag = nsString!.substring(with: range)
|
||||
if i + 1 != entities.count {
|
||||
if case .Mention = entities[i + 1].type {
|
||||
let nextRange = NSRange(location: entities[i + 1].range.lowerBound, length: entities[i + 1].range.upperBound - entities[i + 1].range.lowerBound)
|
||||
guard let nextRange = adjustedRanges[i + 1] else {
|
||||
break
|
||||
}
|
||||
if nextRange.location == range.location + range.length + 1 && nsString!.character(at: range.location + range.length) == 43 {
|
||||
skipEntity = true
|
||||
if nextRange.length > 0 {
|
||||
|
|
@ -231,17 +269,20 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
string.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
|
||||
}
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand), value: nsString!.substring(with: range), range: range)
|
||||
case .Code:
|
||||
addFontAttributes(range, .monospace)
|
||||
if nsString == nil {
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Code), value: nsString!.substring(with: range), range: range)
|
||||
case let .Pre(language):
|
||||
addFontAttributes(range, .monospace)
|
||||
addFontAttributes(range, .blockQuote)
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
if let codeBlockTitleColor, let codeBlockAccentColor, let codeBlockBackgroundColor {
|
||||
var title: NSAttributedString?
|
||||
|
|
@ -260,7 +301,7 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
string.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
|
||||
}
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.BankCard), value: nsString!.substring(with: range), range: range)
|
||||
case .Spoiler:
|
||||
|
|
@ -269,6 +310,12 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
} else {
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true as NSNumber, range: range)
|
||||
}
|
||||
case let .FormattedDate(_, date):
|
||||
string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range)
|
||||
if underlineLinks && underlineAllLinks {
|
||||
string.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
|
||||
}
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Date), value: date, range: range)
|
||||
case let .Custom(type):
|
||||
if type == ApplicationSpecificEntityType.Timecode {
|
||||
string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range)
|
||||
|
|
@ -276,7 +323,7 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
|||
string.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: range)
|
||||
}
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
nsString = string.string as NSString
|
||||
}
|
||||
let text = nsString!.substring(with: range)
|
||||
if let time = parseTimecodeString(text) {
|
||||
|
|
|
|||
|
|
@ -44,4 +44,5 @@ public struct TelegramTextAttributes {
|
|||
public static let Spoiler = "TelegramSpoiler"
|
||||
public static let Code = "TelegramCode"
|
||||
public static let Button = "TelegramButton"
|
||||
public static let Date = "TelegramDate"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue