mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add support for poll translation
This commit is contained in:
parent
18a6a3c2a9
commit
63e45b4bbb
@ -1,11 +1,10 @@
|
|||||||
import Postbox
|
import Postbox
|
||||||
|
|
||||||
public class TranslationMessageAttribute: MessageAttribute, Equatable {
|
public class TranslationMessageAttribute: MessageAttribute, Equatable {
|
||||||
|
|
||||||
public struct Additional : PostboxCoding, Equatable {
|
public struct Additional : PostboxCoding, Equatable {
|
||||||
|
|
||||||
public let text: String
|
public let text: String
|
||||||
public let entities: [MessageTextEntity]
|
public let entities: [MessageTextEntity]
|
||||||
|
|
||||||
public init(text: String, entities: [MessageTextEntity]) {
|
public init(text: String, entities: [MessageTextEntity]) {
|
||||||
self.text = text
|
self.text = text
|
||||||
self.entities = entities
|
self.entities = entities
|
||||||
@ -20,15 +19,13 @@ public class TranslationMessageAttribute: MessageAttribute, Equatable {
|
|||||||
encoder.encodeString(self.text, forKey: "text")
|
encoder.encodeString(self.text, forKey: "text")
|
||||||
encoder.encodeObjectArray(self.entities, forKey: "entities")
|
encoder.encodeObjectArray(self.entities, forKey: "entities")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public let text: String
|
public let text: String
|
||||||
public let entities: [MessageTextEntity]
|
public let entities: [MessageTextEntity]
|
||||||
public let toLang: String
|
public let toLang: String
|
||||||
|
|
||||||
public let additional:[Additional]
|
public let additional: [Additional]
|
||||||
|
|
||||||
public var associatedPeerIds: [PeerId] {
|
public var associatedPeerIds: [PeerId] {
|
||||||
return []
|
return []
|
||||||
|
@ -27,6 +27,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Chat/PollBubbleTimerNode",
|
"//submodules/TelegramUI/Components/Chat/PollBubbleTimerNode",
|
||||||
"//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
|
"//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
|
||||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ShimmeringLinkNode",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -17,6 +17,7 @@ import ChatMessageItemCommon
|
|||||||
import PollBubbleTimerNode
|
import PollBubbleTimerNode
|
||||||
import MergedAvatarsNode
|
import MergedAvatarsNode
|
||||||
import TextNodeWithEntities
|
import TextNodeWithEntities
|
||||||
|
import ShimmeringLinkNode
|
||||||
|
|
||||||
private final class ChatMessagePollOptionRadioNodeParameters: NSObject {
|
private final class ChatMessagePollOptionRadioNodeParameters: NSObject {
|
||||||
let timestamp: Double
|
let timestamp: Double
|
||||||
@ -387,7 +388,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
|||||||
private(set) var radioNode: ChatMessagePollOptionRadioNode?
|
private(set) var radioNode: ChatMessagePollOptionRadioNode?
|
||||||
private let percentageNode: ASDisplayNode
|
private let percentageNode: ASDisplayNode
|
||||||
private var percentageImage: UIImage?
|
private var percentageImage: UIImage?
|
||||||
private var titleNode: TextNodeWithEntities?
|
fileprivate var titleNode: TextNodeWithEntities?
|
||||||
private let buttonNode: HighlightTrackingButtonNode
|
private let buttonNode: HighlightTrackingButtonNode
|
||||||
let separatorNode: ASDisplayNode
|
let separatorNode: ASDisplayNode
|
||||||
private let resultBarNode: ASImageNode
|
private let resultBarNode: ASImageNode
|
||||||
@ -491,22 +492,29 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func asyncLayout(_ maybeNode: ChatMessagePollOptionNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode))) {
|
static func asyncLayout(_ maybeNode: ChatMessagePollOptionNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ translation: TranslationMessageAttribute.Additional?, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode))) {
|
||||||
let makeTitleLayout = TextNodeWithEntities.asyncLayout(maybeNode?.titleNode)
|
let makeTitleLayout = TextNodeWithEntities.asyncLayout(maybeNode?.titleNode)
|
||||||
let currentResult = maybeNode?.currentResult
|
let currentResult = maybeNode?.currentResult
|
||||||
let currentSelection = maybeNode?.currentSelection
|
let currentSelection = maybeNode?.currentSelection
|
||||||
let currentTheme = maybeNode?.theme
|
let currentTheme = maybeNode?.theme
|
||||||
|
|
||||||
return { context, presentationData, message, poll, option, optionResult, constrainedWidth in
|
return { context, presentationData, message, poll, option, translation, optionResult, constrainedWidth in
|
||||||
let leftInset: CGFloat = 50.0
|
let leftInset: CGFloat = 50.0
|
||||||
let rightInset: CGFloat = 12.0
|
let rightInset: CGFloat = 12.0
|
||||||
|
|
||||||
let incoming = message.effectivelyIncoming(context.account.peerId)
|
let incoming = message.effectivelyIncoming(context.account.peerId)
|
||||||
|
|
||||||
|
var optionText = option.text
|
||||||
|
var optionEntities = option.entities
|
||||||
|
if let translation {
|
||||||
|
optionText = translation.text
|
||||||
|
optionEntities = translation.entities
|
||||||
|
}
|
||||||
|
|
||||||
let optionTextColor: UIColor = incoming ? presentationData.theme.theme.chat.message.incoming.primaryTextColor : presentationData.theme.theme.chat.message.outgoing.primaryTextColor
|
let optionTextColor: UIColor = incoming ? presentationData.theme.theme.chat.message.incoming.primaryTextColor : presentationData.theme.theme.chat.message.outgoing.primaryTextColor
|
||||||
let optionAttributedText = stringWithAppliedEntities(
|
let optionAttributedText = stringWithAppliedEntities(
|
||||||
option.text,
|
optionText,
|
||||||
entities: option.entities,
|
entities: optionEntities,
|
||||||
baseColor: optionTextColor,
|
baseColor: optionTextColor,
|
||||||
linkColor: optionTextColor,
|
linkColor: optionTextColor,
|
||||||
baseFont: presentationData.messageFont,
|
baseFont: presentationData.messageFont,
|
||||||
@ -627,6 +635,25 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
|
|||||||
|
|
||||||
node.buttonNode.accessibilityLabel = option.text
|
node.buttonNode.accessibilityLabel = option.text
|
||||||
|
|
||||||
|
if animated {
|
||||||
|
if let titleNode = node.titleNode, let cachedLayout = titleNode.textNode.cachedLayout {
|
||||||
|
if !cachedLayout.areLinesEqual(to: titleLayout) {
|
||||||
|
if let textContents = titleNode.textNode.contents {
|
||||||
|
let fadeNode = ASDisplayNode()
|
||||||
|
fadeNode.displaysAsynchronously = false
|
||||||
|
fadeNode.contents = textContents
|
||||||
|
fadeNode.frame = titleNode.textNode.frame
|
||||||
|
fadeNode.isLayerBacked = true
|
||||||
|
node.addSubnode(fadeNode)
|
||||||
|
fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in
|
||||||
|
fadeNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
titleNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let titleNode = titleApply(TextNodeWithEntities.Arguments(
|
let titleNode = titleApply(TextNodeWithEntities.Arguments(
|
||||||
context: context,
|
context: context,
|
||||||
cache: context.animationCache,
|
cache: context.animationCache,
|
||||||
@ -828,6 +855,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
private let buttonNode: HighlightableButtonNode
|
private let buttonNode: HighlightableButtonNode
|
||||||
private let statusNode: ChatMessageDateAndStatusNode
|
private let statusNode: ChatMessageDateAndStatusNode
|
||||||
private var optionNodes: [ChatMessagePollOptionNode] = []
|
private var optionNodes: [ChatMessagePollOptionNode] = []
|
||||||
|
private var shimmeringNodes: [ShimmeringLinkNode] = []
|
||||||
|
|
||||||
private var poll: TelegramMediaPoll?
|
private var poll: TelegramMediaPoll?
|
||||||
|
|
||||||
@ -996,7 +1024,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var previousOptionNodeLayouts: [Data: (_ contet: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode)))] = [:]
|
var previousOptionNodeLayouts: [Data: (_ contet: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ translation: TranslationMessageAttribute.Additional?, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode)))] = [:]
|
||||||
for optionNode in self.optionNodes {
|
for optionNode in self.optionNodes {
|
||||||
if let option = optionNode.option {
|
if let option = optionNode.option {
|
||||||
previousOptionNodeLayouts[option.opaqueIdentifier] = ChatMessagePollOptionNode.asyncLayout(optionNode)
|
previousOptionNodeLayouts[option.opaqueIdentifier] = ChatMessagePollOptionNode.asyncLayout(optionNode)
|
||||||
@ -1114,11 +1142,28 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing
|
let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing
|
||||||
|
|
||||||
let attributedText: NSAttributedString
|
|
||||||
if let poll {
|
var pollTitleText = poll?.text ?? ""
|
||||||
attributedText = stringWithAppliedEntities(
|
var pollTitleEntities = poll?.textEntities ?? []
|
||||||
poll.text,
|
var pollOptions: [TranslationMessageAttribute.Additional] = []
|
||||||
entities: poll.textEntities,
|
|
||||||
|
var isTranslating = false
|
||||||
|
if let poll, let translateToLanguage = item.associatedData.translateToLanguage, !poll.text.isEmpty && incoming {
|
||||||
|
isTranslating = true
|
||||||
|
for attribute in item.message.attributes {
|
||||||
|
if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage {
|
||||||
|
pollTitleText = attribute.text
|
||||||
|
pollTitleEntities = attribute.entities
|
||||||
|
pollOptions = attribute.additional
|
||||||
|
isTranslating = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let attributedText = stringWithAppliedEntities(
|
||||||
|
pollTitleText,
|
||||||
|
entities: pollTitleEntities,
|
||||||
baseColor: messageTheme.primaryTextColor,
|
baseColor: messageTheme.primaryTextColor,
|
||||||
linkColor: messageTheme.linkTextColor,
|
linkColor: messageTheme.linkTextColor,
|
||||||
baseFont: item.presentationData.messageBoldFont,
|
baseFont: item.presentationData.messageBoldFont,
|
||||||
@ -1130,9 +1175,6 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
blockQuoteFont: item.presentationData.messageBoldFont,
|
blockQuoteFont: item.presentationData.messageBoldFont,
|
||||||
message: message
|
message: message
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
attributedText = NSAttributedString(string: "", font: item.presentationData.messageBoldFont, textColor: messageTheme.primaryTextColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
let textInsets = UIEdgeInsets(top: 2.0, left: 0.0, bottom: 5.0, right: 0.0)
|
let textInsets = UIEdgeInsets(top: 2.0, left: 0.0, bottom: 5.0, right: 0.0)
|
||||||
|
|
||||||
@ -1269,7 +1311,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
for i in 0 ..< poll.options.count {
|
for i in 0 ..< poll.options.count {
|
||||||
let option = poll.options[i]
|
let option = poll.options[i]
|
||||||
|
|
||||||
let makeLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode)))
|
let makeLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ translation: TranslationMessageAttribute.Additional?, _ optionResult: ChatMessagePollOptionResult?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode)))
|
||||||
if let previous = previousOptionNodeLayouts[option.opaqueIdentifier] {
|
if let previous = previousOptionNodeLayouts[option.opaqueIdentifier] {
|
||||||
makeLayout = previous
|
makeLayout = previous
|
||||||
} else {
|
} else {
|
||||||
@ -1285,7 +1327,13 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
} else if isClosed {
|
} else if isClosed {
|
||||||
optionResult = ChatMessagePollOptionResult(normalized: 0, percent: 0, count: 0)
|
optionResult = ChatMessagePollOptionResult(normalized: 0, percent: 0, count: 0)
|
||||||
}
|
}
|
||||||
let result = makeLayout(item.context, item.presentationData, item.message, poll, option, optionResult, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0)
|
|
||||||
|
var translation: TranslationMessageAttribute.Additional?
|
||||||
|
if !pollOptions.isEmpty && i < pollOptions.count {
|
||||||
|
translation = pollOptions[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = makeLayout(item.context, item.presentationData, item.message, poll, option, translation, optionResult, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0)
|
||||||
boundingSize.width = max(boundingSize.width, result.minimumWidth + layoutConstants.bubble.borderInset * 2.0)
|
boundingSize.width = max(boundingSize.width, result.minimumWidth + layoutConstants.bubble.borderInset * 2.0)
|
||||||
pollOptionsFinalizeLayouts.append(result.1)
|
pollOptionsFinalizeLayouts.append(result.1)
|
||||||
}
|
}
|
||||||
@ -1351,6 +1399,26 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.item = item
|
strongSelf.item = item
|
||||||
strongSelf.poll = poll
|
strongSelf.poll = poll
|
||||||
|
|
||||||
|
let cachedLayout = strongSelf.textNode.textNode.cachedLayout
|
||||||
|
if case .System = animation {
|
||||||
|
if let cachedLayout = cachedLayout {
|
||||||
|
if !cachedLayout.areLinesEqual(to: textLayout) {
|
||||||
|
if let textContents = strongSelf.textNode.textNode.contents {
|
||||||
|
let fadeNode = ASDisplayNode()
|
||||||
|
fadeNode.displaysAsynchronously = false
|
||||||
|
fadeNode.contents = textContents
|
||||||
|
fadeNode.frame = strongSelf.textNode.textNode.frame
|
||||||
|
fadeNode.isLayerBacked = true
|
||||||
|
strongSelf.addSubnode(fadeNode)
|
||||||
|
fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak fadeNode] _ in
|
||||||
|
fadeNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
strongSelf.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let _ = textApply(TextNodeWithEntities.Arguments(
|
let _ = textApply(TextNodeWithEntities.Arguments(
|
||||||
context: item.context,
|
context: item.context,
|
||||||
cache: item.context.animationCache,
|
cache: item.context.animationCache,
|
||||||
@ -1371,6 +1439,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let optionNode = apply(animation.isAnimated, isRequesting, synchronousLoad)
|
let optionNode = apply(animation.isAnimated, isRequesting, synchronousLoad)
|
||||||
|
let optionNodeFrame = CGRect(origin: CGPoint(x: layoutConstants.bubble.borderInset, y: verticalOffset), size: size)
|
||||||
if optionNode.supernode !== strongSelf {
|
if optionNode.supernode !== strongSelf {
|
||||||
strongSelf.addSubnode(optionNode)
|
strongSelf.addSubnode(optionNode)
|
||||||
let option = optionNode.option
|
let option = optionNode.option
|
||||||
@ -1387,8 +1456,11 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
strongSelf.updateSelection()
|
strongSelf.updateSelection()
|
||||||
}
|
}
|
||||||
|
optionNode.frame = optionNodeFrame
|
||||||
|
} else {
|
||||||
|
animation.animator.updateFrame(layer: optionNode.layer, frame: optionNodeFrame, completion: nil)
|
||||||
}
|
}
|
||||||
optionNode.frame = CGRect(origin: CGPoint(x: layoutConstants.bubble.borderInset, y: verticalOffset), size: size)
|
|
||||||
verticalOffset += size.height
|
verticalOffset += size.height
|
||||||
updatedOptionNodes.append(optionNode)
|
updatedOptionNodes.append(optionNode)
|
||||||
optionNode.isUserInteractionEnabled = canVote && item.controllerInteraction.pollActionState.pollMessageIdsInProgress[item.message.id] == nil
|
optionNode.isUserInteractionEnabled = canVote && item.controllerInteraction.pollActionState.pollMessageIdsInProgress[item.message.id] == nil
|
||||||
@ -1410,7 +1482,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.textNode.textNode.frame = textFrame
|
strongSelf.textNode.textNode.frame = textFrame
|
||||||
}
|
}
|
||||||
let typeFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: textFrame.maxY + titleTypeSpacing), size: typeLayout.size)
|
let typeFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: textFrame.maxY + titleTypeSpacing), size: typeLayout.size)
|
||||||
strongSelf.typeNode.frame = typeFrame
|
animation.animator.updateFrame(layer: strongSelf.typeNode.layer, frame: typeFrame, completion: nil)
|
||||||
|
|
||||||
let deadlineTimeout = poll?.deadlineTimeout
|
let deadlineTimeout = poll?.deadlineTimeout
|
||||||
var displayDeadline = true
|
var displayDeadline = true
|
||||||
@ -1536,7 +1608,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let _ = votersApply()
|
let _ = votersApply()
|
||||||
let votersFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - votersLayout.size.width) / 2.0), y: verticalOffset + optionsVotersSpacing), size: votersLayout.size)
|
let votersFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - votersLayout.size.width) / 2.0), y: verticalOffset + optionsVotersSpacing), size: votersLayout.size)
|
||||||
strongSelf.votersNode.frame = votersFrame
|
animation.animator.updateFrame(layer: strongSelf.votersNode.layer, frame: votersFrame, completion: nil)
|
||||||
|
|
||||||
if animation.isAnimated, let previousPoll = previousPoll, let poll = poll {
|
if animation.isAnimated, let previousPoll = previousPoll, let poll = poll {
|
||||||
if previousPoll.results.totalVoters == nil && poll.results.totalVoters != nil {
|
if previousPoll.results.totalVoters == nil && poll.results.totalVoters != nil {
|
||||||
strongSelf.votersNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
strongSelf.votersNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
@ -1571,6 +1644,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
strongSelf.updateSelection()
|
strongSelf.updateSelection()
|
||||||
strongSelf.updatePollTooltipMessageState(animated: false)
|
strongSelf.updatePollTooltipMessageState(animated: false)
|
||||||
|
|
||||||
|
strongSelf.updateIsTranslating(isTranslating)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1578,6 +1653,46 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateIsTranslating(_ isTranslating: Bool) {
|
||||||
|
guard let item = self.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var rects: [[CGRect]] = []
|
||||||
|
let titleRects = (self.textNode.textNode.rangeRects(in: NSRange(location: 0, length: self.textNode.textNode.cachedLayout?.attributedString?.length ?? 0))?.rects ?? []).map { self.textNode.textNode.view.convert($0, to: self.view) }
|
||||||
|
rects.append(titleRects)
|
||||||
|
|
||||||
|
for optionNode in self.optionNodes {
|
||||||
|
if let titleNode = optionNode.titleNode {
|
||||||
|
let optionRects = (titleNode.textNode.rangeRects(in: NSRange(location: 0, length: titleNode.textNode.cachedLayout?.attributedString?.length ?? 0))?.rects ?? []).map { titleNode.textNode.view.convert($0, to: self.view) }
|
||||||
|
rects.append(optionRects)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTranslating, !rects.isEmpty {
|
||||||
|
if self.shimmeringNodes.isEmpty {
|
||||||
|
for rects in rects {
|
||||||
|
let shimmeringNode = ShimmeringLinkNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.secondaryTextColor.withAlphaComponent(0.1) : item.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor.withAlphaComponent(0.1))
|
||||||
|
shimmeringNode.updateRects(rects)
|
||||||
|
shimmeringNode.frame = self.bounds
|
||||||
|
shimmeringNode.updateLayout(self.bounds.size)
|
||||||
|
shimmeringNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
self.shimmeringNodes.append(shimmeringNode)
|
||||||
|
self.insertSubnode(shimmeringNode, belowSubnode: self.textNode.textNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !self.shimmeringNodes.isEmpty {
|
||||||
|
let shimmeringNodes = self.shimmeringNodes
|
||||||
|
self.shimmeringNodes = []
|
||||||
|
|
||||||
|
for shimmeringNode in shimmeringNodes {
|
||||||
|
shimmeringNode.alpha = 0.0
|
||||||
|
shimmeringNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak shimmeringNode] _ in
|
||||||
|
shimmeringNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateSelection() {
|
private func updateSelection() {
|
||||||
guard let item = self.item, let poll = self.poll else {
|
guard let item = self.item, let poll = self.poll else {
|
||||||
return
|
return
|
||||||
|
@ -998,6 +998,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func updateTouchesAtPoint(_ point: CGPoint?) {
|
override public func updateTouchesAtPoint(_ point: CGPoint?) {
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
var rects: [CGRect]?
|
var rects: [CGRect]?
|
||||||
|
@ -1934,7 +1934,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let cachedData = data.cachedData as? CachedChannelData, isCreator || cachedData.flags.contains(.canViewStats) {
|
if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) {
|
||||||
items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStats, label: .none, text: presentationData.strings.Channel_Info_Stats, icon: UIImage(bundleImageName: "Chat/Info/StatsIcon"), action: {
|
items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStats, label: .none, text: presentationData.strings.Channel_Info_Stats, icon: UIImage(bundleImageName: "Chat/Info/StatsIcon"), action: {
|
||||||
interaction.openStats(false)
|
interaction.openStats(false)
|
||||||
}))
|
}))
|
||||||
|
@ -2474,22 +2474,30 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
guard message.adAttribute == nil && message.id.namespace == Namespaces.Message.Cloud else {
|
guard message.adAttribute == nil && message.id.namespace == Namespaces.Message.Cloud else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !message.text.isEmpty && message.author?.id != self.context.account.peerId {
|
guard message.author?.id != self.context.account.peerId else {
|
||||||
if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == translateToLanguage {
|
continue
|
||||||
} else {
|
|
||||||
messageIdsToTranslate.append(message.id)
|
|
||||||
}
|
}
|
||||||
|
if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == translateToLanguage {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !message.text.isEmpty {
|
||||||
|
messageIdsToTranslate.append(message.id)
|
||||||
|
} else if let _ = message.media.first(where: { $0 is TelegramMediaPoll }) {
|
||||||
|
messageIdsToTranslate.append(message.id)
|
||||||
}
|
}
|
||||||
case let .MessageGroupEntry(_, messages, _):
|
case let .MessageGroupEntry(_, messages, _):
|
||||||
for (message, _, _, _, _) in messages {
|
for (message, _, _, _, _) in messages {
|
||||||
guard message.adAttribute == nil && message.id.namespace == Namespaces.Message.Cloud else {
|
guard message.adAttribute == nil && message.id.namespace == Namespaces.Message.Cloud else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !message.text.isEmpty && message.author?.id != self.context.account.peerId {
|
guard message.author?.id != self.context.account.peerId else {
|
||||||
if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == translateToLanguage {
|
continue
|
||||||
} else {
|
|
||||||
messageIdsToTranslate.append(message.id)
|
|
||||||
}
|
}
|
||||||
|
if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == translateToLanguage {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !message.text.isEmpty {
|
||||||
|
messageIdsToTranslate.append(message.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -126,13 +126,22 @@ public func translateMessageIds(context: AccountContext, messageIds: [EngineMess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !message.text.isEmpty && message.author?.id != context.account.peerId {
|
guard message.author?.id != context.account.peerId else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == toLang {
|
if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == toLang {
|
||||||
} else {
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !message.text.isEmpty {
|
||||||
if !messageIdsSet.contains(messageId) {
|
if !messageIdsSet.contains(messageId) {
|
||||||
messageIdsToTranslate.append(messageId)
|
messageIdsToTranslate.append(messageId)
|
||||||
messageIdsSet.insert(messageId)
|
messageIdsSet.insert(messageId)
|
||||||
}
|
}
|
||||||
|
} else if let _ = message.media.first(where: { $0 is TelegramMediaPoll }) {
|
||||||
|
if !messageIdsSet.contains(messageId) {
|
||||||
|
messageIdsToTranslate.append(messageId)
|
||||||
|
messageIdsSet.insert(messageId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user