mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-25 20:50:47 +00:00
Various fixes
This commit is contained in:
parent
9edf52ed36
commit
d7634beb50
@ -24,6 +24,7 @@ public enum MessageContentKindKey {
|
|||||||
case expiredVoiceMessage
|
case expiredVoiceMessage
|
||||||
case expiredVideoMessage
|
case expiredVideoMessage
|
||||||
case poll
|
case poll
|
||||||
|
case todo
|
||||||
case restricted
|
case restricted
|
||||||
case dice
|
case dice
|
||||||
case invoice
|
case invoice
|
||||||
@ -50,6 +51,7 @@ public enum MessageContentKind: Equatable {
|
|||||||
case expiredVoiceMessage
|
case expiredVoiceMessage
|
||||||
case expiredVideoMessage
|
case expiredVideoMessage
|
||||||
case poll(String)
|
case poll(String)
|
||||||
|
case todo(String)
|
||||||
case restricted(String)
|
case restricted(String)
|
||||||
case dice(String)
|
case dice(String)
|
||||||
case invoice(String)
|
case invoice(String)
|
||||||
@ -160,6 +162,12 @@ public enum MessageContentKind: Equatable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case .todo:
|
||||||
|
if case .todo = other {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case .restricted:
|
case .restricted:
|
||||||
if case .restricted = other {
|
if case .restricted = other {
|
||||||
return true
|
return true
|
||||||
@ -229,6 +237,8 @@ public enum MessageContentKind: Equatable {
|
|||||||
return .expiredVideoMessage
|
return .expiredVideoMessage
|
||||||
case .poll:
|
case .poll:
|
||||||
return .poll
|
return .poll
|
||||||
|
case .todo:
|
||||||
|
return .todo
|
||||||
case .restricted:
|
case .restricted:
|
||||||
return .restricted
|
return .restricted
|
||||||
case .dice:
|
case .dice:
|
||||||
@ -369,6 +379,8 @@ public func mediaContentKind(_ media: EngineMedia, message: EngineMessage? = nil
|
|||||||
}
|
}
|
||||||
case let .poll(poll):
|
case let .poll(poll):
|
||||||
return .poll(poll.text)
|
return .poll(poll.text)
|
||||||
|
case let .todo(todo):
|
||||||
|
return .todo(todo.text)
|
||||||
case let .dice(dice):
|
case let .dice(dice):
|
||||||
return .dice(dice.emoji)
|
return .dice(dice.emoji)
|
||||||
case let .invoice(invoice):
|
case let .invoice(invoice):
|
||||||
@ -455,6 +467,8 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
|
|||||||
return (NSAttributedString(string: strings.Message_VideoMessageExpired), true)
|
return (NSAttributedString(string: strings.Message_VideoMessageExpired), true)
|
||||||
case let .poll(text):
|
case let .poll(text):
|
||||||
return (NSAttributedString(string: "📊 \(text)"), false)
|
return (NSAttributedString(string: "📊 \(text)"), false)
|
||||||
|
case let .todo(text):
|
||||||
|
return (NSAttributedString(string: "☑️ \(text)"), false)
|
||||||
case let .restricted(text):
|
case let .restricted(text):
|
||||||
return (NSAttributedString(string: text), false)
|
return (NSAttributedString(string: text), false)
|
||||||
case let .dice(emoji):
|
case let .dice(emoji):
|
||||||
|
@ -1297,38 +1297,47 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var taskTitle = "DELETED"
|
||||||
if let todo {
|
if let todo {
|
||||||
if message.author?.id == accountPeerId {
|
if let completedTaskId = completed.first, let completedTask = todo.items.first(where: { $0.id == completedTaskId }) {
|
||||||
let resultString: PresentationStrings.FormattedString
|
taskTitle = completedTask.text
|
||||||
if let completedTaskId = completed.first, let completedTask = todo.items.first(where: { $0.id == completedTaskId }) {
|
} else if let incompletedTaskId = incompleted.first, let incompletedTask = todo.items.first(where: { $0.id == incompletedTaskId }) {
|
||||||
resultString = strings.Notification_TodoCompletedYou(completedTask.text)
|
taskTitle = incompletedTask.text
|
||||||
} else if let incompletedTaskId = incompleted.first, let incompletedTask = todo.items.first(where: { $0.id == incompletedTaskId }) {
|
|
||||||
resultString = strings.Notification_TodoIncompletedYou(incompletedTask.text)
|
|
||||||
} else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
|
||||||
} else {
|
|
||||||
let peerName = message.author?.compactDisplayTitle ?? ""
|
|
||||||
|
|
||||||
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
|
|
||||||
attributes[1] = boldAttributes
|
|
||||||
|
|
||||||
let resultString: PresentationStrings.FormattedString
|
|
||||||
if let completedTaskId = completed.first, let completedTask = todo.items.first(where: { $0.id == completedTaskId }) {
|
|
||||||
resultString = strings.Notification_TodoCompleted(peerName, completedTask.text)
|
|
||||||
} else if let incompletedTaskId = incompleted.first, let incompletedTask = todo.items.first(where: { $0.id == incompletedTaskId }) {
|
|
||||||
resultString = strings.Notification_TodoIncompleted(peerName, incompletedTask.text)
|
|
||||||
} else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if taskTitle.count > 20 {
|
||||||
|
taskTitle = taskTitle.prefix(20) + "…"
|
||||||
|
}
|
||||||
|
|
||||||
|
if message.author?.id == accountPeerId {
|
||||||
|
let resultString: PresentationStrings.FormattedString
|
||||||
|
if let _ = completed.first {
|
||||||
|
resultString = strings.Notification_TodoCompletedYou(taskTitle)
|
||||||
|
} else if let _ = incompleted.first {
|
||||||
|
resultString = strings.Notification_TodoIncompletedYou(taskTitle)
|
||||||
|
} else {
|
||||||
|
resultString = strings.Notification_TodoCompletedYou("")
|
||||||
|
}
|
||||||
|
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||||
} else {
|
} else {
|
||||||
attributedString = NSAttributedString(string: ".")
|
let peerName = message.author?.compactDisplayTitle ?? ""
|
||||||
|
|
||||||
|
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
|
||||||
|
attributes[1] = boldAttributes
|
||||||
|
|
||||||
|
let resultString: PresentationStrings.FormattedString
|
||||||
|
if let _ = completed.first {
|
||||||
|
resultString = strings.Notification_TodoCompleted(peerName, taskTitle)
|
||||||
|
} else if let _ = incompleted.first {
|
||||||
|
resultString = strings.Notification_TodoIncompleted(peerName, taskTitle)
|
||||||
|
} else {
|
||||||
|
resultString = strings.Notification_TodoCompleted(peerName, "")
|
||||||
|
}
|
||||||
|
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||||
}
|
}
|
||||||
case let .todoAppendTasks(tasks):
|
case let .todoAppendTasks(tasks):
|
||||||
var todoTitle = ""
|
var todoTitle = "DELETED"
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
|
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
|
||||||
for media in message.media {
|
for media in message.media {
|
||||||
@ -1338,10 +1347,17 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if todoTitle.count > 20 {
|
||||||
|
todoTitle = todoTitle.prefix(20) + "…"
|
||||||
|
}
|
||||||
if message.author?.id == accountPeerId {
|
if message.author?.id == accountPeerId {
|
||||||
let resultString: PresentationStrings.FormattedString
|
let resultString: PresentationStrings.FormattedString
|
||||||
if tasks.count == 1, let task = tasks.first {
|
if tasks.count == 1, let task = tasks.first {
|
||||||
resultString = strings.Notification_TodoAddedTaskYou(task.text, todoTitle)
|
var taskTitle = task.text
|
||||||
|
if taskTitle.count > 20 {
|
||||||
|
taskTitle = taskTitle.prefix(20) + "…"
|
||||||
|
}
|
||||||
|
resultString = strings.Notification_TodoAddedTaskYou(taskTitle, todoTitle)
|
||||||
} else {
|
} else {
|
||||||
resultString = strings.Notification_TodoAddedMultipleTasksYou(strings.Notification_TodoTasks(Int32(tasks.count)), todoTitle)
|
resultString = strings.Notification_TodoAddedMultipleTasksYou(strings.Notification_TodoTasks(Int32(tasks.count)), todoTitle)
|
||||||
}
|
}
|
||||||
@ -1354,7 +1370,11 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
|
|
||||||
let resultString: PresentationStrings.FormattedString
|
let resultString: PresentationStrings.FormattedString
|
||||||
if tasks.count == 1, let task = tasks.first {
|
if tasks.count == 1, let task = tasks.first {
|
||||||
resultString = strings.Notification_TodoAddedTask(peerName, task.text, todoTitle)
|
var taskTitle = task.text
|
||||||
|
if taskTitle.count > 20 {
|
||||||
|
taskTitle = taskTitle.prefix(20) + "…"
|
||||||
|
}
|
||||||
|
resultString = strings.Notification_TodoAddedTask(peerName, taskTitle, todoTitle)
|
||||||
} else {
|
} else {
|
||||||
resultString = strings.Notification_TodoAddedMultipleTasks(peerName, strings.Notification_TodoTasks(Int32(tasks.count)), todoTitle)
|
resultString = strings.Notification_TodoAddedMultipleTasks(peerName, strings.Notification_TodoTasks(Int32(tasks.count)), todoTitle)
|
||||||
}
|
}
|
||||||
|
@ -414,6 +414,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||||||
if result.last?.1 == ChatMessageTextBubbleContentNode.self {
|
if result.last?.1 == ChatMessageTextBubbleContentNode.self {
|
||||||
} else {
|
} else {
|
||||||
if result.last?.1 == ChatMessagePollBubbleContentNode.self ||
|
if result.last?.1 == ChatMessagePollBubbleContentNode.self ||
|
||||||
|
result.last?.1 == ChatMessageTodoBubbleContentNode.self ||
|
||||||
result.last?.1 == ChatMessageContactBubbleContentNode.self ||
|
result.last?.1 == ChatMessageContactBubbleContentNode.self ||
|
||||||
result.last?.1 == ChatMessageGameBubbleContentNode.self ||
|
result.last?.1 == ChatMessageGameBubbleContentNode.self ||
|
||||||
result.last?.1 == ChatMessageInvoiceBubbleContentNode.self ||
|
result.last?.1 == ChatMessageInvoiceBubbleContentNode.self ||
|
||||||
@ -423,8 +424,9 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||||||
} else if result.last?.1 == ChatMessageCommentFooterContentNode.self {
|
} else if result.last?.1 == ChatMessageCommentFooterContentNode.self {
|
||||||
if result.count >= 2 {
|
if result.count >= 2 {
|
||||||
if result[result.count - 2].1 == ChatMessagePollBubbleContentNode.self ||
|
if result[result.count - 2].1 == ChatMessagePollBubbleContentNode.self ||
|
||||||
result[result.count - 2].1 == ChatMessageContactBubbleContentNode.self ||
|
result[result.count - 2].1 == ChatMessageTodoBubbleContentNode.self ||
|
||||||
result[result.count - 2].1 == ChatMessageGiveawayBubbleContentNode.self {
|
result[result.count - 2].1 == ChatMessageContactBubbleContentNode.self ||
|
||||||
|
result[result.count - 2].1 == ChatMessageGiveawayBubbleContentNode.self {
|
||||||
result.insert((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .reactions, neighborSpacing: .default)), at: result.count - 1)
|
result.insert((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .reactions, neighborSpacing: .default)), at: result.count - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1092,7 +1092,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
backgroundContent.view.mask = strongSelf.mediaBackgroundMaskNode.view
|
backgroundContent.view.mask = strongSelf.mediaBackgroundMaskNode.view
|
||||||
strongSelf.mediaBackgroundMaskNode.frame = CGRect(origin: .zero, size: backgroundMaskFrame.size)
|
animation.animator.updateFrame(layer: strongSelf.mediaBackgroundMaskNode.layer, frame: CGRect(origin: .zero, size: backgroundMaskFrame.size), completion: nil)
|
||||||
} else {
|
} else {
|
||||||
animation.animator.updateFrame(layer: backgroundContent.layer, frame: mediaBackgroundFrame, completion: nil)
|
animation.animator.updateFrame(layer: backgroundContent.layer, frame: mediaBackgroundFrame, completion: nil)
|
||||||
backgroundContent.clipsToBounds = true
|
backgroundContent.clipsToBounds = true
|
||||||
@ -1153,7 +1153,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let backgroundNode = strongSelf.backgroundNode {
|
if let backgroundNode = strongSelf.backgroundNode {
|
||||||
backgroundNode.frame = CGRect(origin: CGPoint(x: baseBackgroundFrame.minX + offset.x, y: baseBackgroundFrame.minY + offset.y), size: image.size)
|
animation.animator.updateFrame(layer: backgroundNode.layer, frame: CGRect(origin: CGPoint(x: baseBackgroundFrame.minX + offset.x, y: baseBackgroundFrame.minY + offset.y), size: image.size), completion: nil)
|
||||||
}
|
}
|
||||||
strongSelf.backgroundMaskNode.image = image
|
strongSelf.backgroundMaskNode.image = image
|
||||||
strongSelf.backgroundMaskNode.frame = CGRect(origin: CGPoint(), size: image.size)
|
strongSelf.backgroundMaskNode.frame = CGRect(origin: CGPoint(), size: image.size)
|
||||||
|
@ -625,6 +625,7 @@ final class ComposeTodoScreenComponent: Component {
|
|||||||
},
|
},
|
||||||
assumeIsEditing: self.inputMediaNodeTargetTag === self.todoTextFieldTag,
|
assumeIsEditing: self.inputMediaNodeTargetTag === self.todoTextFieldTag,
|
||||||
characterLimit: component.initialData.maxTodoTextLength,
|
characterLimit: component.initialData.maxTodoTextLength,
|
||||||
|
canReorder: canEdit,
|
||||||
emptyLineHandling: .allowed,
|
emptyLineHandling: .allowed,
|
||||||
returnKeyAction: { [weak self] in
|
returnKeyAction: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
|
@ -257,18 +257,17 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
|
|||||||
currentNodes = nil
|
currentNodes = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let transition = ControlledTransition(duration: 0.2, curve: .spring, interactive: false)
|
||||||
if let messageNodes = currentNodes {
|
if let messageNodes = currentNodes {
|
||||||
nodes = messageNodes
|
nodes = messageNodes
|
||||||
for i in 0 ..< items.count {
|
for i in 0 ..< items.count {
|
||||||
let itemNode = messageNodes[i]
|
let itemNode = messageNodes[i]
|
||||||
items[i].updateNode(async: { $0() }, node: {
|
items[i].updateNode(async: { $0() }, node: {
|
||||||
return itemNode
|
return itemNode
|
||||||
}, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .System(duration: 0.2, transition: ControlledTransition(duration: 0.2, curve: .spring, interactive: false)), completion: { (layout, apply) in
|
}, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .System(duration: 0.2, transition: transition), completion: { (layout, apply) in
|
||||||
let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height))
|
itemNode.bounds = CGRect(origin: .zero, size: layout.size)
|
||||||
|
|
||||||
itemNode.contentSize = layout.contentSize
|
itemNode.contentSize = layout.contentSize
|
||||||
itemNode.insets = layout.insets
|
itemNode.insets = layout.insets
|
||||||
itemNode.frame = nodeFrame
|
|
||||||
itemNode.isUserInteractionEnabled = false
|
itemNode.isUserInteractionEnabled = false
|
||||||
itemNode.visibility = .visible(1.0, .infinite)
|
itemNode.visibility = .visible(1.0, .infinite)
|
||||||
|
|
||||||
@ -333,12 +332,14 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
|
|||||||
strongSelf.containerNode.addSubnode(node)
|
strongSelf.containerNode.addSubnode(node)
|
||||||
}
|
}
|
||||||
let bubbleHeight: CGFloat
|
let bubbleHeight: CGFloat
|
||||||
if node.frame.height > 44.0, let initialBubbleHeight = strongSelf.initialBubbleHeight {
|
// if node.frame.height > 44.0, let initialBubbleHeight = strongSelf.initialBubbleHeight {
|
||||||
bubbleHeight = max(node.frame.height, initialBubbleHeight)
|
// bubbleHeight = max(node.frame.height, initialBubbleHeight)
|
||||||
} else {
|
// } else {
|
||||||
bubbleHeight = node.frame.height
|
bubbleHeight = node.frame.height
|
||||||
}
|
// }
|
||||||
node.updateFrame(CGRect(origin: CGPoint(x: 0.0, y: originY), size: node.frame.size), within: layoutSize)
|
transition.legacyAnimator.transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: 0.0, y: originY), size: node.frame.size))
|
||||||
|
|
||||||
|
//node.updateFrame(CGRect(origin: CGPoint(x: 0.0, y: originY), size: node.frame.size), within: layoutSize, transition: transition)
|
||||||
originY += bubbleHeight
|
originY += bubbleHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
public let assumeIsEditing: Bool
|
public let assumeIsEditing: Bool
|
||||||
public let characterLimit: Int?
|
public let characterLimit: Int?
|
||||||
public let enableInlineAnimations: Bool
|
public let enableInlineAnimations: Bool
|
||||||
|
public let canReorder: Bool
|
||||||
public let emptyLineHandling: TextFieldComponent.EmptyLineHandling
|
public let emptyLineHandling: TextFieldComponent.EmptyLineHandling
|
||||||
public let returnKeyAction: (() -> Void)?
|
public let returnKeyAction: (() -> Void)?
|
||||||
public let backspaceKeyAction: (() -> Void)?
|
public let backspaceKeyAction: (() -> Void)?
|
||||||
@ -98,6 +99,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
assumeIsEditing: Bool = false,
|
assumeIsEditing: Bool = false,
|
||||||
characterLimit: Int,
|
characterLimit: Int,
|
||||||
enableInlineAnimations: Bool = true,
|
enableInlineAnimations: Bool = true,
|
||||||
|
canReorder: Bool = false,
|
||||||
emptyLineHandling: TextFieldComponent.EmptyLineHandling,
|
emptyLineHandling: TextFieldComponent.EmptyLineHandling,
|
||||||
returnKeyAction: (() -> Void)?,
|
returnKeyAction: (() -> Void)?,
|
||||||
backspaceKeyAction: (() -> Void)?,
|
backspaceKeyAction: (() -> Void)?,
|
||||||
@ -117,6 +119,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
self.assumeIsEditing = assumeIsEditing
|
self.assumeIsEditing = assumeIsEditing
|
||||||
self.characterLimit = characterLimit
|
self.characterLimit = characterLimit
|
||||||
self.enableInlineAnimations = enableInlineAnimations
|
self.enableInlineAnimations = enableInlineAnimations
|
||||||
|
self.canReorder = canReorder
|
||||||
self.emptyLineHandling = emptyLineHandling
|
self.emptyLineHandling = emptyLineHandling
|
||||||
self.returnKeyAction = returnKeyAction
|
self.returnKeyAction = returnKeyAction
|
||||||
self.backspaceKeyAction = backspaceKeyAction
|
self.backspaceKeyAction = backspaceKeyAction
|
||||||
@ -158,6 +161,9 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
if lhs.enableInlineAnimations != rhs.enableInlineAnimations {
|
if lhs.enableInlineAnimations != rhs.enableInlineAnimations {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.canReorder != rhs.canReorder {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.emptyLineHandling != rhs.emptyLineHandling {
|
if lhs.emptyLineHandling != rhs.emptyLineHandling {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -967,7 +967,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
switch action.action {
|
switch action.action {
|
||||||
case .pinnedMessageUpdated, .gameScore, .setSameChatWallpaper, .giveawayResults, .customText:
|
case .pinnedMessageUpdated, .gameScore, .setSameChatWallpaper, .giveawayResults, .customText, .todoCompletions, .todoAppendTasks:
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? ReplyMessageAttribute {
|
if let attribute = attribute as? ReplyMessageAttribute {
|
||||||
self.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil)))
|
self.navigateToMessage(from: message.id, to: .id(attribute.messageId, NavigateToMessageParams(timestamp: nil, quote: attribute.isQuote ? attribute.quote.flatMap { quote in NavigateToMessageParams.Quote(string: quote.text, offset: quote.offset) } : nil)))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user