mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
33f05a9a1d
@ -6861,6 +6861,7 @@ Sorry for the inconvenience.";
|
|||||||
"Chat.NavigationNoChannels" = "You have no unread channels";
|
"Chat.NavigationNoChannels" = "You have no unread channels";
|
||||||
|
|
||||||
"Message.SponsoredLabel" = "sponsored";
|
"Message.SponsoredLabel" = "sponsored";
|
||||||
|
"Message.RecommendedLabel" = "recommended";
|
||||||
|
|
||||||
"Stickers.Favorites" = "Favorites";
|
"Stickers.Favorites" = "Favorites";
|
||||||
"Stickers.Recent" = "Recent";
|
"Stickers.Recent" = "Recent";
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
2300
|
2400
|
||||||
|
|||||||
@ -562,23 +562,23 @@ private final class AnimatedStickerDirectFrameSourceCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource {
|
public final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource {
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private let data: Data
|
private let data: Data
|
||||||
private let width: Int
|
private let width: Int
|
||||||
private let height: Int
|
private let height: Int
|
||||||
private let cache: AnimatedStickerDirectFrameSourceCache?
|
private let cache: AnimatedStickerDirectFrameSourceCache?
|
||||||
private let bytesPerRow: Int
|
private let bytesPerRow: Int
|
||||||
let frameCount: Int
|
public let frameCount: Int
|
||||||
let frameRate: Int
|
public let frameRate: Int
|
||||||
fileprivate var currentFrame: Int
|
fileprivate var currentFrame: Int
|
||||||
private let animation: LottieInstance
|
private let animation: LottieInstance
|
||||||
|
|
||||||
var frameIndex: Int {
|
public var frameIndex: Int {
|
||||||
return self.currentFrame % self.frameCount
|
return self.currentFrame % self.frameCount
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(queue: Queue, data: Data, width: Int, height: Int, cachePathPrefix: String?, useMetalCache: Bool = false, fitzModifier: EmojiFitzModifier?) {
|
public init?(queue: Queue, data: Data, width: Int, height: Int, cachePathPrefix: String?, useMetalCache: Bool = false, fitzModifier: EmojiFitzModifier?) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.data = data
|
self.data = data
|
||||||
self.width = width
|
self.width = width
|
||||||
@ -604,7 +604,7 @@ final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource {
|
|||||||
assert(self.queue.isCurrent())
|
assert(self.queue.isCurrent())
|
||||||
}
|
}
|
||||||
|
|
||||||
func takeFrame(draw: Bool) -> AnimatedStickerFrame? {
|
public func takeFrame(draw: Bool) -> AnimatedStickerFrame? {
|
||||||
let frameIndex = self.currentFrame % self.frameCount
|
let frameIndex = self.currentFrame % self.frameCount
|
||||||
self.currentFrame += 1
|
self.currentFrame += 1
|
||||||
if draw {
|
if draw {
|
||||||
@ -630,11 +630,11 @@ final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func skipToEnd() {
|
public func skipToEnd() {
|
||||||
self.currentFrame = self.frameCount - 1
|
self.currentFrame = self.frameCount - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func skipToFrameIndex(_ index: Int) {
|
public func skipToFrameIndex(_ index: Int) {
|
||||||
self.currentFrame = index
|
self.currentFrame = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,6 +84,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case enableDebugDataDisplay(Bool)
|
case enableDebugDataDisplay(Bool)
|
||||||
case acceleratedStickers(Bool)
|
case acceleratedStickers(Bool)
|
||||||
case experimentalBackground(Bool)
|
case experimentalBackground(Bool)
|
||||||
|
case inlineStickers(Bool)
|
||||||
case snow(Bool)
|
case snow(Bool)
|
||||||
case playerEmbedding(Bool)
|
case playerEmbedding(Bool)
|
||||||
case playlistPlayback(Bool)
|
case playlistPlayback(Bool)
|
||||||
@ -106,7 +107,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return DebugControllerSection.logging.rawValue
|
return DebugControllerSection.logging.rawValue
|
||||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .snow:
|
case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineStickers, .snow:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .preferredVideoCodec:
|
case .preferredVideoCodec:
|
||||||
return DebugControllerSection.videoExperiments.rawValue
|
return DebugControllerSection.videoExperiments.rawValue
|
||||||
@ -181,16 +182,18 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 29
|
return 29
|
||||||
case .experimentalBackground:
|
case .experimentalBackground:
|
||||||
return 30
|
return 30
|
||||||
case .snow:
|
case .inlineStickers:
|
||||||
return 31
|
return 31
|
||||||
case .playerEmbedding:
|
case .snow:
|
||||||
return 32
|
return 32
|
||||||
case .playlistPlayback:
|
case .playerEmbedding:
|
||||||
return 33
|
return 33
|
||||||
case .voiceConference:
|
case .playlistPlayback:
|
||||||
return 34
|
return 34
|
||||||
|
case .voiceConference:
|
||||||
|
return 35
|
||||||
case let .preferredVideoCodec(index, _, _, _):
|
case let .preferredVideoCodec(index, _, _, _):
|
||||||
return 35 + index
|
return 36 + index
|
||||||
case .disableVideoAspectScaling:
|
case .disableVideoAspectScaling:
|
||||||
return 100
|
return 100
|
||||||
case .enableVoipTcp:
|
case .enableVoipTcp:
|
||||||
@ -948,6 +951,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
})
|
})
|
||||||
}).start()
|
}).start()
|
||||||
})
|
})
|
||||||
|
case let .inlineStickers(value):
|
||||||
|
return ItemListSwitchItem(presentationData: presentationData, title: "Inline Stickers", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
|
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||||
|
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||||
|
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
|
||||||
|
settings.inlineStickers = value
|
||||||
|
return PreferencesEntry(settings)
|
||||||
|
})
|
||||||
|
}).start()
|
||||||
|
})
|
||||||
case let .snow(value):
|
case let .snow(value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Snow", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: "Snow", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||||
@ -1073,6 +1086,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
|
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
|
||||||
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
|
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
|
||||||
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
|
entries.append(.experimentalBackground(experimentalSettings.experimentalBackground))
|
||||||
|
entries.append(.inlineStickers(experimentalSettings.inlineStickers))
|
||||||
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
||||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,19 @@ private final class TextNodeSpoiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private final class TextNodeEmbeddedItem {
|
||||||
|
let range: NSRange
|
||||||
|
let frame: CGRect
|
||||||
|
let item: AnyHashable
|
||||||
|
|
||||||
|
init(range: NSRange, frame: CGRect, item: AnyHashable) {
|
||||||
|
self.range = range
|
||||||
|
self.frame = frame
|
||||||
|
self.item = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct TextRangeRectEdge: Equatable {
|
public struct TextRangeRectEdge: Equatable {
|
||||||
public var x: CGFloat
|
public var x: CGFloat
|
||||||
public var y: CGFloat
|
public var y: CGFloat
|
||||||
@ -45,8 +58,9 @@ private final class TextNodeLine {
|
|||||||
let strikethroughs: [TextNodeStrikethrough]
|
let strikethroughs: [TextNodeStrikethrough]
|
||||||
let spoilers: [TextNodeSpoiler]
|
let spoilers: [TextNodeSpoiler]
|
||||||
let spoilerWords: [TextNodeSpoiler]
|
let spoilerWords: [TextNodeSpoiler]
|
||||||
|
let embeddedItems: [TextNodeEmbeddedItem]
|
||||||
|
|
||||||
init(line: CTLine, frame: CGRect, range: NSRange, isRTL: Bool, strikethroughs: [TextNodeStrikethrough], spoilers: [TextNodeSpoiler], spoilerWords: [TextNodeSpoiler]) {
|
init(line: CTLine, frame: CGRect, range: NSRange, isRTL: Bool, strikethroughs: [TextNodeStrikethrough], spoilers: [TextNodeSpoiler], spoilerWords: [TextNodeSpoiler], embeddedItems: [TextNodeEmbeddedItem]) {
|
||||||
self.line = line
|
self.line = line
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
self.range = range
|
self.range = range
|
||||||
@ -54,6 +68,7 @@ private final class TextNodeLine {
|
|||||||
self.strikethroughs = strikethroughs
|
self.strikethroughs = strikethroughs
|
||||||
self.spoilers = spoilers
|
self.spoilers = spoilers
|
||||||
self.spoilerWords = spoilerWords
|
self.spoilerWords = spoilerWords
|
||||||
|
self.embeddedItems = embeddedItems
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +168,31 @@ public final class TextNodeLayoutArguments {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class TextNodeLayout: NSObject {
|
public final class TextNodeLayout: NSObject {
|
||||||
|
public final class EmbeddedItem: Equatable {
|
||||||
|
public let range: NSRange
|
||||||
|
public let rect: CGRect
|
||||||
|
public let value: AnyHashable
|
||||||
|
|
||||||
|
public init(range: NSRange, rect: CGRect, value: AnyHashable) {
|
||||||
|
self.range = range
|
||||||
|
self.rect = rect
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: EmbeddedItem, rhs: EmbeddedItem) -> Bool {
|
||||||
|
if lhs.range != rhs.range {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.rect != rhs.rect {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.value != rhs.value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public let attributedString: NSAttributedString?
|
public let attributedString: NSAttributedString?
|
||||||
fileprivate let maximumNumberOfLines: Int
|
fileprivate let maximumNumberOfLines: Int
|
||||||
fileprivate let truncationType: CTLineTruncationType
|
fileprivate let truncationType: CTLineTruncationType
|
||||||
@ -163,7 +203,7 @@ public final class TextNodeLayout: NSObject {
|
|||||||
fileprivate let verticalAlignment: TextVerticalAlignment
|
fileprivate let verticalAlignment: TextVerticalAlignment
|
||||||
fileprivate let lineSpacing: CGFloat
|
fileprivate let lineSpacing: CGFloat
|
||||||
fileprivate let cutout: TextNodeCutout?
|
fileprivate let cutout: TextNodeCutout?
|
||||||
fileprivate let insets: UIEdgeInsets
|
public let insets: UIEdgeInsets
|
||||||
public let size: CGSize
|
public let size: CGSize
|
||||||
public let rawTextSize: CGSize
|
public let rawTextSize: CGSize
|
||||||
public let truncated: Bool
|
public let truncated: Bool
|
||||||
@ -177,6 +217,7 @@ public final class TextNodeLayout: NSObject {
|
|||||||
public let hasRTL: Bool
|
public let hasRTL: Bool
|
||||||
public let spoilers: [(NSRange, CGRect)]
|
public let spoilers: [(NSRange, CGRect)]
|
||||||
public let spoilerWords: [(NSRange, CGRect)]
|
public let spoilerWords: [(NSRange, CGRect)]
|
||||||
|
public let embeddedItems: [TextNodeLayout.EmbeddedItem]
|
||||||
|
|
||||||
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, explicitAlignment: NSTextAlignment, resolvedAlignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool) {
|
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, explicitAlignment: NSTextAlignment, resolvedAlignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, rawTextSize: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?, textStroke: (UIColor, CGFloat)?, displaySpoilers: Bool) {
|
||||||
self.attributedString = attributedString
|
self.attributedString = attributedString
|
||||||
@ -203,6 +244,7 @@ public final class TextNodeLayout: NSObject {
|
|||||||
var hasRTL = false
|
var hasRTL = false
|
||||||
var spoilers: [(NSRange, CGRect)] = []
|
var spoilers: [(NSRange, CGRect)] = []
|
||||||
var spoilerWords: [(NSRange, CGRect)] = []
|
var spoilerWords: [(NSRange, CGRect)] = []
|
||||||
|
var embeddedItems: [TextNodeLayout.EmbeddedItem] = []
|
||||||
for line in lines {
|
for line in lines {
|
||||||
if line.isRTL {
|
if line.isRTL {
|
||||||
hasRTL = true
|
hasRTL = true
|
||||||
@ -218,10 +260,14 @@ public final class TextNodeLayout: NSObject {
|
|||||||
|
|
||||||
spoilers.append(contentsOf: line.spoilers.map { ( $0.range, $0.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) })
|
spoilers.append(contentsOf: line.spoilers.map { ( $0.range, $0.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) })
|
||||||
spoilerWords.append(contentsOf: line.spoilerWords.map { ( $0.range, $0.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) })
|
spoilerWords.append(contentsOf: line.spoilerWords.map { ( $0.range, $0.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)) })
|
||||||
|
for embeddedItem in line.embeddedItems {
|
||||||
|
embeddedItems.append(TextNodeLayout.EmbeddedItem(range: embeddedItem.range, rect: embeddedItem.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY), value: embeddedItem.item))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.hasRTL = hasRTL
|
self.hasRTL = hasRTL
|
||||||
self.spoilers = spoilers
|
self.spoilers = spoilers
|
||||||
self.spoilerWords = spoilerWords
|
self.spoilerWords = spoilerWords
|
||||||
|
self.embeddedItems = embeddedItems
|
||||||
}
|
}
|
||||||
|
|
||||||
public func areLinesEqual(to other: TextNodeLayout) -> Bool {
|
public func areLinesEqual(to other: TextNodeLayout) -> Bool {
|
||||||
@ -971,6 +1017,7 @@ public class TextNode: ASDisplayNode {
|
|||||||
var strikethroughs: [TextNodeStrikethrough] = []
|
var strikethroughs: [TextNodeStrikethrough] = []
|
||||||
var spoilers: [TextNodeSpoiler] = []
|
var spoilers: [TextNodeSpoiler] = []
|
||||||
var spoilerWords: [TextNodeSpoiler] = []
|
var spoilerWords: [TextNodeSpoiler] = []
|
||||||
|
var embeddedItems: [TextNodeEmbeddedItem] = []
|
||||||
|
|
||||||
var lineConstrainedWidth = constrainedSize.width
|
var lineConstrainedWidth = constrainedSize.width
|
||||||
var lineConstrainedWidthDelta: CGFloat = 0.0
|
var lineConstrainedWidthDelta: CGFloat = 0.0
|
||||||
@ -1028,6 +1075,24 @@ public class TextNode: ASDisplayNode {
|
|||||||
spoilerWords.append(TextNodeSpoiler(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent)))
|
spoilerWords.append(TextNodeSpoiler(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addEmbeddedItem(item: AnyHashable, line: CTLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int, rightInset: CGFloat = 0.0) {
|
||||||
|
var secondaryLeftOffset: CGFloat = 0.0
|
||||||
|
let rawLeftOffset = CTLineGetOffsetForStringIndex(line, startIndex, &secondaryLeftOffset)
|
||||||
|
var leftOffset = floor(rawLeftOffset)
|
||||||
|
if !rawLeftOffset.isEqual(to: secondaryLeftOffset) {
|
||||||
|
leftOffset = floor(secondaryLeftOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
var secondaryRightOffset: CGFloat = 0.0
|
||||||
|
let rawRightOffset = CTLineGetOffsetForStringIndex(line, endIndex, &secondaryRightOffset)
|
||||||
|
var rightOffset = ceil(rawRightOffset)
|
||||||
|
if !rawRightOffset.isEqual(to: secondaryRightOffset) {
|
||||||
|
rightOffset = ceil(secondaryRightOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
embeddedItems.append(TextNodeEmbeddedItem(range: NSMakeRange(startIndex, endIndex - startIndex + 1), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent), item: item))
|
||||||
|
}
|
||||||
|
|
||||||
var isLastLine = false
|
var isLastLine = false
|
||||||
if maximumNumberOfLines != 0 && lines.count == maximumNumberOfLines - 1 && lineCharacterCount > 0 {
|
if maximumNumberOfLines != 0 && lines.count == maximumNumberOfLines - 1 && lineCharacterCount > 0 {
|
||||||
isLastLine = true
|
isLastLine = true
|
||||||
@ -1116,6 +1181,12 @@ public class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
|
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
|
||||||
|
} else if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) {
|
||||||
|
var ascent: CGFloat = 0.0
|
||||||
|
var descent: CGFloat = 0.0
|
||||||
|
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
|
||||||
|
|
||||||
|
addEmbeddedItem(item: embeddedItem, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
|
||||||
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
|
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
|
||||||
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
|
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
|
||||||
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
|
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
|
||||||
@ -1123,7 +1194,6 @@ public class TextNode: ASDisplayNode {
|
|||||||
strikethroughs.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight)))
|
strikethroughs.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight)))
|
||||||
} else if let paragraphStyle = attributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle {
|
} else if let paragraphStyle = attributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle {
|
||||||
headIndent = paragraphStyle.headIndent
|
headIndent = paragraphStyle.headIndent
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1146,7 +1216,7 @@ public class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords))
|
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: embeddedItems))
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
if lineCharacterCount > 0 {
|
if lineCharacterCount > 0 {
|
||||||
@ -1198,6 +1268,12 @@ public class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
|
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
|
||||||
|
} else if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) {
|
||||||
|
var ascent: CGFloat = 0.0
|
||||||
|
var descent: CGFloat = 0.0
|
||||||
|
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
|
||||||
|
|
||||||
|
addEmbeddedItem(item: embeddedItem, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
|
||||||
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
|
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
|
||||||
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
|
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
|
||||||
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
|
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
|
||||||
@ -1226,7 +1302,7 @@ public class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords))
|
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: embeddedItems))
|
||||||
} else {
|
} else {
|
||||||
if !lines.isEmpty {
|
if !lines.isEmpty {
|
||||||
layoutSize.height += fontLineSpacing
|
layoutSize.height += fontLineSpacing
|
||||||
@ -1289,9 +1365,6 @@ public class TextNode: ASDisplayNode {
|
|||||||
|
|
||||||
var clearRects: [CGRect] = []
|
var clearRects: [CGRect] = []
|
||||||
if let layout = parameters as? TextNodeLayout {
|
if let layout = parameters as? TextNodeLayout {
|
||||||
if (layout.attributedString?.string ?? "").hasPrefix("Д") {
|
|
||||||
print()
|
|
||||||
}
|
|
||||||
if !isRasterizing || layout.backgroundColor != nil {
|
if !isRasterizing || layout.backgroundColor != nil {
|
||||||
context.setBlendMode(.copy)
|
context.setBlendMode(.copy)
|
||||||
context.setFillColor((layout.backgroundColor ?? UIColor.clear).cgColor)
|
context.setFillColor((layout.backgroundColor ?? UIColor.clear).cgColor)
|
||||||
@ -1803,7 +1876,7 @@ open class TextView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords))
|
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: []))
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
if lineCharacterCount > 0 {
|
if lineCharacterCount > 0 {
|
||||||
@ -1883,7 +1956,7 @@ open class TextView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords))
|
lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs, spoilers: spoilers, spoilerWords: spoilerWords, embeddedItems: []))
|
||||||
} else {
|
} else {
|
||||||
if !lines.isEmpty {
|
if !lines.isEmpty {
|
||||||
layoutSize.height += fontLineSpacing
|
layoutSize.height += fontLineSpacing
|
||||||
|
|||||||
@ -2,16 +2,23 @@ import Foundation
|
|||||||
import Postbox
|
import Postbox
|
||||||
|
|
||||||
public final class AdMessageAttribute: MessageAttribute {
|
public final class AdMessageAttribute: MessageAttribute {
|
||||||
|
public enum MessageType {
|
||||||
|
case sponsored
|
||||||
|
case recommended
|
||||||
|
}
|
||||||
|
|
||||||
public enum MessageTarget {
|
public enum MessageTarget {
|
||||||
case peer(id: EnginePeer.Id, message: EngineMessage.Id?, startParam: String?)
|
case peer(id: EnginePeer.Id, message: EngineMessage.Id?, startParam: String?)
|
||||||
case join(title: String, joinHash: String)
|
case join(title: String, joinHash: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
public let opaqueId: Data
|
public let opaqueId: Data
|
||||||
|
public let messageType: MessageType
|
||||||
public let target: MessageTarget
|
public let target: MessageTarget
|
||||||
|
|
||||||
public init(opaqueId: Data, target: MessageTarget) {
|
public init(opaqueId: Data, messageType: MessageType, target: MessageTarget) {
|
||||||
self.opaqueId = opaqueId
|
self.opaqueId = opaqueId
|
||||||
|
self.messageType = messageType
|
||||||
self.target = target
|
self.target = target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
final class CachedMessage: Equatable, Codable {
|
final class CachedMessage: Equatable, Codable {
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case opaqueId
|
case opaqueId
|
||||||
|
case messageType
|
||||||
case text
|
case text
|
||||||
case textEntities
|
case textEntities
|
||||||
case media
|
case media
|
||||||
@ -15,6 +16,11 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
case startParam
|
case startParam
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MessageType: Int32, Codable {
|
||||||
|
case sponsored = 0
|
||||||
|
case recommended = 1
|
||||||
|
}
|
||||||
|
|
||||||
enum Target: Equatable, Codable {
|
enum Target: Equatable, Codable {
|
||||||
enum DecodingError: Error {
|
enum DecodingError: Error {
|
||||||
case generic
|
case generic
|
||||||
@ -58,6 +64,7 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public let opaqueId: Data
|
public let opaqueId: Data
|
||||||
|
public let messageType: MessageType
|
||||||
public let text: String
|
public let text: String
|
||||||
public let textEntities: [MessageTextEntity]
|
public let textEntities: [MessageTextEntity]
|
||||||
public let media: [Media]
|
public let media: [Media]
|
||||||
@ -67,6 +74,7 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
|
|
||||||
public init(
|
public init(
|
||||||
opaqueId: Data,
|
opaqueId: Data,
|
||||||
|
messageType: MessageType,
|
||||||
text: String,
|
text: String,
|
||||||
textEntities: [MessageTextEntity],
|
textEntities: [MessageTextEntity],
|
||||||
media: [Media],
|
media: [Media],
|
||||||
@ -75,6 +83,7 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
startParam: String?
|
startParam: String?
|
||||||
) {
|
) {
|
||||||
self.opaqueId = opaqueId
|
self.opaqueId = opaqueId
|
||||||
|
self.messageType = messageType
|
||||||
self.text = text
|
self.text = text
|
||||||
self.textEntities = textEntities
|
self.textEntities = textEntities
|
||||||
self.media = media
|
self.media = media
|
||||||
@ -88,6 +97,8 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
|
|
||||||
self.opaqueId = try container.decode(Data.self, forKey: .opaqueId)
|
self.opaqueId = try container.decode(Data.self, forKey: .opaqueId)
|
||||||
|
|
||||||
|
self.messageType = (try container.decodeIfPresent(MessageType.self, forKey: .messageType)) ?? .sponsored
|
||||||
|
|
||||||
self.text = try container.decode(String.self, forKey: .text)
|
self.text = try container.decode(String.self, forKey: .text)
|
||||||
self.textEntities = try container.decode([MessageTextEntity].self, forKey: .textEntities)
|
self.textEntities = try container.decode([MessageTextEntity].self, forKey: .textEntities)
|
||||||
|
|
||||||
@ -105,6 +116,7 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
try container.encode(self.opaqueId, forKey: .opaqueId)
|
try container.encode(self.opaqueId, forKey: .opaqueId)
|
||||||
|
try container.encode(self.messageType, forKey: .messageType)
|
||||||
try container.encode(self.text, forKey: .text)
|
try container.encode(self.text, forKey: .text)
|
||||||
try container.encode(self.textEntities, forKey: .textEntities)
|
try container.encode(self.textEntities, forKey: .textEntities)
|
||||||
|
|
||||||
@ -124,6 +136,9 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
if lhs.opaqueId != rhs.opaqueId {
|
if lhs.opaqueId != rhs.opaqueId {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.messageType != rhs.messageType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.text != rhs.text {
|
if lhs.text != rhs.text {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -160,7 +175,14 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
case let .invite(invite):
|
case let .invite(invite):
|
||||||
target = .join(title: invite.title, joinHash: invite.joinHash)
|
target = .join(title: invite.title, joinHash: invite.joinHash)
|
||||||
}
|
}
|
||||||
attributes.append(AdMessageAttribute(opaqueId: self.opaqueId, target: target))
|
let mappedMessageType: AdMessageAttribute.MessageType
|
||||||
|
switch self.messageType {
|
||||||
|
case .sponsored:
|
||||||
|
mappedMessageType = .sponsored
|
||||||
|
case .recommended:
|
||||||
|
mappedMessageType = .recommended
|
||||||
|
}
|
||||||
|
attributes.append(AdMessageAttribute(opaqueId: self.opaqueId, messageType: mappedMessageType, target: target))
|
||||||
if !self.textEntities.isEmpty {
|
if !self.textEntities.isEmpty {
|
||||||
let attribute = TextEntitiesMessageAttribute(entities: self.textEntities)
|
let attribute = TextEntitiesMessageAttribute(entities: self.textEntities)
|
||||||
attributes.append(attribute)
|
attributes.append(attribute)
|
||||||
@ -400,12 +422,14 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
|
|
||||||
for message in messages {
|
for message in messages {
|
||||||
switch message {
|
switch message {
|
||||||
case let .sponsoredMessage(_, randomId, fromId, chatInvite, chatInviteHash, channelPost, startParam, message, entities):
|
case let .sponsoredMessage(flags, randomId, fromId, chatInvite, chatInviteHash, channelPost, startParam, message, entities):
|
||||||
var parsedEntities: [MessageTextEntity] = []
|
var parsedEntities: [MessageTextEntity] = []
|
||||||
if let entities = entities {
|
if let entities = entities {
|
||||||
parsedEntities = messageTextEntitiesFromApiEntities(entities)
|
parsedEntities = messageTextEntitiesFromApiEntities(entities)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isRecommended = (flags & (1 << 5)) != 0
|
||||||
|
|
||||||
let _ = chatInvite
|
let _ = chatInvite
|
||||||
let _ = chatInviteHash
|
let _ = chatInviteHash
|
||||||
|
|
||||||
@ -452,6 +476,7 @@ private class AdMessagesHistoryContextImpl {
|
|||||||
if let target = target {
|
if let target = target {
|
||||||
parsedMessages.append(CachedMessage(
|
parsedMessages.append(CachedMessage(
|
||||||
opaqueId: randomId.makeData(),
|
opaqueId: randomId.makeData(),
|
||||||
|
messageType: isRecommended ? .recommended : .sponsored,
|
||||||
text: message,
|
text: message,
|
||||||
textEntities: parsedEntities,
|
textEntities: parsedEntities,
|
||||||
media: [],
|
media: [],
|
||||||
|
|||||||
@ -9,6 +9,12 @@ import UrlEscaping
|
|||||||
import TelegramUniversalVideoContent
|
import TelegramUniversalVideoContent
|
||||||
import TextSelectionNode
|
import TextSelectionNode
|
||||||
import InvisibleInkDustNode
|
import InvisibleInkDustNode
|
||||||
|
import Emoji
|
||||||
|
import AnimatedStickerNode
|
||||||
|
import TelegramAnimatedStickerNode
|
||||||
|
import SwiftSignalKit
|
||||||
|
import AccountContext
|
||||||
|
import YuvConversion
|
||||||
|
|
||||||
private final class CachedChatMessageText {
|
private final class CachedChatMessageText {
|
||||||
let text: String
|
let text: String
|
||||||
@ -36,6 +42,163 @@ private final class CachedChatMessageText {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class InlineStickerItem: Hashable {
|
||||||
|
let file: TelegramMediaFile
|
||||||
|
|
||||||
|
init(file: TelegramMediaFile) {
|
||||||
|
self.file = file
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(self.file.fileId)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: InlineStickerItem, rhs: InlineStickerItem) -> Bool {
|
||||||
|
if lhs.file.fileId != rhs.file.fileId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class InlineStickerItemLayer: SimpleLayer {
|
||||||
|
static let queue = Queue()
|
||||||
|
|
||||||
|
struct Key: Hashable {
|
||||||
|
var id: MediaId
|
||||||
|
var index: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
private let file: TelegramMediaFile
|
||||||
|
private let source: AnimatedStickerNodeSource
|
||||||
|
private var frameSource: QueueLocalObject<AnimatedStickerDirectFrameSource>?
|
||||||
|
private var disposable: Disposable?
|
||||||
|
private var fetchDisposable: Disposable?
|
||||||
|
|
||||||
|
private var isInHierarchy: Bool = false
|
||||||
|
var isVisibleForAnimations: Bool = false {
|
||||||
|
didSet {
|
||||||
|
self.updatePlayback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var displayLink: ConstantDisplayLinkAnimator?
|
||||||
|
|
||||||
|
init(context: AccountContext, file: TelegramMediaFile) {
|
||||||
|
self.source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
|
||||||
|
self.file = file
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
||||||
|
|
||||||
|
self.disposable = (self.source.directDataPath()
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOn(InlineStickerItemLayer.queue)).start(next: { [weak self] path in
|
||||||
|
guard let directData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.frameSource = QueueLocalObject(queue: InlineStickerItemLayer.queue, generate: {
|
||||||
|
return AnimatedStickerDirectFrameSource(queue: InlineStickerItemLayer.queue, data: directData, width: Int(24 * UIScreenScale), height: Int(24 * UIScreenScale), cachePathPrefix: pathPrefix, useMetalCache: false, fitzModifier: nil)!
|
||||||
|
})
|
||||||
|
strongSelf.updatePlayback()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file)).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(layer: Any) {
|
||||||
|
guard let layer = layer as? InlineStickerItemLayer else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
self.source = layer.source
|
||||||
|
self.file = layer.file
|
||||||
|
|
||||||
|
super.init(layer: layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable?.dispose()
|
||||||
|
self.fetchDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func action(forKey event: String) -> CAAction? {
|
||||||
|
if event == kCAOnOrderIn {
|
||||||
|
self.isInHierarchy = true
|
||||||
|
} else if event == kCAOnOrderOut {
|
||||||
|
self.isInHierarchy = false
|
||||||
|
}
|
||||||
|
self.updatePlayback()
|
||||||
|
return nullAction
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updatePlayback() {
|
||||||
|
let shouldBePlaying = self.isInHierarchy && self.isVisibleForAnimations && self.frameSource != nil
|
||||||
|
if shouldBePlaying != (self.displayLink != nil) {
|
||||||
|
if shouldBePlaying {
|
||||||
|
self.displayLink = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||||
|
self?.loadNextFrame()
|
||||||
|
})
|
||||||
|
self.displayLink?.isPaused = false
|
||||||
|
} else {
|
||||||
|
self.displayLink?.invalidate()
|
||||||
|
self.displayLink = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadNextFrame() {
|
||||||
|
guard let frameSource = self.frameSource else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
frameSource.with { [weak self] impl in
|
||||||
|
if let animationFrame = impl.takeFrame(draw: true) {
|
||||||
|
var image: UIImage?
|
||||||
|
|
||||||
|
autoreleasepool {
|
||||||
|
image = generateImagePixel(CGSize(width: CGFloat(animationFrame.width), height: CGFloat(animationFrame.height)), scale: 1.0, pixelGenerator: { _, pixelData, contextBytesPerRow in
|
||||||
|
var data = animationFrame.data
|
||||||
|
data.withUnsafeMutableBytes { bytes -> Void in
|
||||||
|
guard let baseAddress = bytes.baseAddress else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch animationFrame.type {
|
||||||
|
case .argb:
|
||||||
|
memcpy(pixelData, baseAddress.assumingMemoryBound(to: UInt8.self), bytes.count)
|
||||||
|
case .yuva:
|
||||||
|
if animationFrame.bytesPerRow <= 0 || animationFrame.height <= 0 || animationFrame.width <= 0 || animationFrame.bytesPerRow * animationFrame.height > bytes.count {
|
||||||
|
assert(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
decodeYUVAToRGBA(baseAddress.assumingMemoryBound(to: UInt8.self), pixelData, Int32(animationFrame.width), Int32(animationFrame.height), Int32(contextBytesPerRow))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if let image = image {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.contents = image.cgImage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||||
private let textNode: TextNode
|
private let textNode: TextNode
|
||||||
private var spoilerTextNode: TextNode?
|
private var spoilerTextNode: TextNode?
|
||||||
@ -47,9 +210,26 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
private var textSelectionNode: TextSelectionNode?
|
private var textSelectionNode: TextSelectionNode?
|
||||||
|
|
||||||
private var textHighlightingNodes: [LinkHighlightingNode] = []
|
private var textHighlightingNodes: [LinkHighlightingNode] = []
|
||||||
|
private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayer] = [:]
|
||||||
|
|
||||||
private var cachedChatMessageText: CachedChatMessageText?
|
private var cachedChatMessageText: CachedChatMessageText?
|
||||||
|
|
||||||
|
private var isVisibleForAnimations: Bool {
|
||||||
|
return self.visibility != .none
|
||||||
|
}
|
||||||
|
|
||||||
|
override var visibility: ListViewItemNodeVisibility {
|
||||||
|
didSet {
|
||||||
|
let wasVisible = oldValue != .none
|
||||||
|
let isVisible = self.visibility != .none
|
||||||
|
if wasVisible != isVisible {
|
||||||
|
for (_, itemLayer) in self.inlineStickerItemLayers {
|
||||||
|
itemLayer.isVisibleForAnimations = isVisible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
self.textNode = TextNode()
|
self.textNode = TextNode()
|
||||||
|
|
||||||
@ -179,7 +359,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let rawText: String
|
let rawText: String
|
||||||
let attributedText: NSAttributedString
|
var attributedText: NSAttributedString
|
||||||
var messageEntities: [MessageTextEntity]?
|
var messageEntities: [MessageTextEntity]?
|
||||||
|
|
||||||
var mediaDuration: Double? = nil
|
var mediaDuration: Double? = nil
|
||||||
@ -281,6 +461,36 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
attributedText = NSAttributedString(string: " ", font: textFont, textColor: messageTheme.primaryTextColor)
|
attributedText = NSAttributedString(string: " ", font: textFont, textColor: messageTheme.primaryTextColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*if item.context.sharedContext.immediateExperimentalUISettings.inlineStickers*/ do {
|
||||||
|
var currentCount = 0
|
||||||
|
let updatedString = NSMutableAttributedString(attributedString: attributedText)
|
||||||
|
while true {
|
||||||
|
var hadUpdates = false
|
||||||
|
updatedString.string.enumerateSubstrings(in: updatedString.string.startIndex ..< updatedString.string.endIndex, options: [.byComposedCharacterSequences]) { substring, substringRange, _, stop in
|
||||||
|
if let substring = substring {
|
||||||
|
let emoji = substring.basicEmoji.0
|
||||||
|
|
||||||
|
var emojiFile: TelegramMediaFile?
|
||||||
|
emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.first?.file
|
||||||
|
if emojiFile == nil {
|
||||||
|
emojiFile = item.associatedData.animatedEmojiStickers[emoji.strippedEmoji]?.first?.file
|
||||||
|
}
|
||||||
|
|
||||||
|
if let emojiFile = emojiFile {
|
||||||
|
updatedString.replaceCharacters(in: NSRange(substringRange, in: updatedString.string), with: NSAttributedString(string: "[\u{00a0}\u{00a0}\u{00a0}\u{00a0}\u{00a0}]", attributes: [NSAttributedString.Key("Attribute__EmbeddedItem"): InlineStickerItem(file: emojiFile), NSAttributedString.Key.foregroundColor: UIColor.clear.cgColor]))
|
||||||
|
currentCount += 1
|
||||||
|
hadUpdates = true
|
||||||
|
stop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hadUpdates || currentCount >= 10 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attributedText = updatedString
|
||||||
|
}
|
||||||
|
|
||||||
let cutout: TextNodeCutout? = nil
|
let cutout: TextNodeCutout? = nil
|
||||||
|
|
||||||
let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0)
|
let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0)
|
||||||
@ -386,7 +596,6 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let _ = textApply()
|
let _ = textApply()
|
||||||
animation.animator.updateFrame(layer: strongSelf.textNode.layer, frame: textFrame, completion: nil)
|
animation.animator.updateFrame(layer: strongSelf.textNode.layer, frame: textFrame, completion: nil)
|
||||||
//strongSelf.textNode.frame = textFrame
|
|
||||||
|
|
||||||
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
|
if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
|
||||||
let spoilerTextNode = spoilerTextApply()
|
let spoilerTextNode = spoilerTextApply()
|
||||||
@ -434,6 +643,8 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.textAccessibilityOverlayNode.frame = textFrame
|
strongSelf.textAccessibilityOverlayNode.frame = textFrame
|
||||||
strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout
|
strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout
|
||||||
|
|
||||||
|
strongSelf.updateInlineStickers(context: item.context, textLayout: textLayout)
|
||||||
|
|
||||||
if let statusSizeAndApply = statusSizeAndApply {
|
if let statusSizeAndApply = statusSizeAndApply {
|
||||||
animation.animator.updateFrame(layer: strongSelf.statusNode.layer, frame: CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0), completion: nil)
|
animation.animator.updateFrame(layer: strongSelf.statusNode.layer, frame: CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0), completion: nil)
|
||||||
if strongSelf.statusNode.supernode == nil {
|
if strongSelf.statusNode.supernode == nil {
|
||||||
@ -463,6 +674,50 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateInlineStickers(context: AccountContext, textLayout: TextNodeLayout?) {
|
||||||
|
var nextIndexById: [MediaId: Int] = [:]
|
||||||
|
var validIds: [InlineStickerItemLayer.Key] = []
|
||||||
|
|
||||||
|
if let textLayout = textLayout {
|
||||||
|
for item in textLayout.embeddedItems {
|
||||||
|
if let stickerItem = item.value as? InlineStickerItem {
|
||||||
|
let index: Int
|
||||||
|
if let currentNext = nextIndexById[stickerItem.file.fileId] {
|
||||||
|
index = currentNext
|
||||||
|
} else {
|
||||||
|
index = 0
|
||||||
|
}
|
||||||
|
nextIndexById[stickerItem.file.fileId] = index + 1
|
||||||
|
let id = InlineStickerItemLayer.Key(id: stickerItem.file.fileId, index: index)
|
||||||
|
validIds.append(id)
|
||||||
|
|
||||||
|
let itemLayer: InlineStickerItemLayer
|
||||||
|
if let current = self.inlineStickerItemLayers[id] {
|
||||||
|
itemLayer = current
|
||||||
|
} else {
|
||||||
|
itemLayer = InlineStickerItemLayer(context: context, file: stickerItem.file)
|
||||||
|
self.inlineStickerItemLayers[id] = itemLayer
|
||||||
|
self.textNode.layer.addSublayer(itemLayer)
|
||||||
|
itemLayer.isVisibleForAnimations = self.isVisibleForAnimations
|
||||||
|
}
|
||||||
|
|
||||||
|
itemLayer.frame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left, dy: textLayout.insets.top + 1.0).center, size: CGSize()).insetBy(dx: -11.0, dy: -11.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeKeys: [InlineStickerItemLayer.Key] = []
|
||||||
|
for (key, itemLayer) in self.inlineStickerItemLayers {
|
||||||
|
if !validIds.contains(key) {
|
||||||
|
removeKeys.append(key)
|
||||||
|
itemLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for key in removeKeys {
|
||||||
|
self.inlineStickerItemLayers.removeValue(forKey: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
||||||
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
|||||||
@ -29,8 +29,13 @@ private func dateStringForDay(strings: PresentationStrings, dateTimeFormat: Pres
|
|||||||
}
|
}
|
||||||
|
|
||||||
func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular) -> String {
|
func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular) -> String {
|
||||||
if message.adAttribute != nil {
|
if let adAttribute = message.adAttribute {
|
||||||
return strings.Message_SponsoredLabel
|
switch adAttribute.messageType {
|
||||||
|
case .sponsored:
|
||||||
|
return strings.Message_SponsoredLabel
|
||||||
|
case .recommended:
|
||||||
|
return strings.Message_RecommendedLabel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let timestamp: Int32
|
let timestamp: Int32
|
||||||
|
|||||||
@ -20,6 +20,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
public var acceleratedStickers: Bool
|
public var acceleratedStickers: Bool
|
||||||
public var experimentalBackground: Bool
|
public var experimentalBackground: Bool
|
||||||
public var snow: Bool
|
public var snow: Bool
|
||||||
|
public var inlineStickers: Bool
|
||||||
|
|
||||||
public static var defaultSettings: ExperimentalUISettings {
|
public static var defaultSettings: ExperimentalUISettings {
|
||||||
return ExperimentalUISettings(
|
return ExperimentalUISettings(
|
||||||
@ -38,7 +39,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
enableDebugDataDisplay: false,
|
enableDebugDataDisplay: false,
|
||||||
acceleratedStickers: false,
|
acceleratedStickers: false,
|
||||||
experimentalBackground: false,
|
experimentalBackground: false,
|
||||||
snow: false
|
snow: false,
|
||||||
|
inlineStickers: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +60,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
enableDebugDataDisplay: Bool,
|
enableDebugDataDisplay: Bool,
|
||||||
acceleratedStickers: Bool,
|
acceleratedStickers: Bool,
|
||||||
experimentalBackground: Bool,
|
experimentalBackground: Bool,
|
||||||
snow: Bool
|
snow: Bool,
|
||||||
|
inlineStickers: Bool
|
||||||
) {
|
) {
|
||||||
self.keepChatNavigationStack = keepChatNavigationStack
|
self.keepChatNavigationStack = keepChatNavigationStack
|
||||||
self.skipReadHistory = skipReadHistory
|
self.skipReadHistory = skipReadHistory
|
||||||
@ -76,6 +79,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.acceleratedStickers = acceleratedStickers
|
self.acceleratedStickers = acceleratedStickers
|
||||||
self.experimentalBackground = experimentalBackground
|
self.experimentalBackground = experimentalBackground
|
||||||
self.snow = snow
|
self.snow = snow
|
||||||
|
self.inlineStickers = inlineStickers
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -97,6 +101,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0
|
self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0
|
||||||
self.experimentalBackground = (try container.decodeIfPresent(Int32.self, forKey: "experimentalBackground") ?? 0) != 0
|
self.experimentalBackground = (try container.decodeIfPresent(Int32.self, forKey: "experimentalBackground") ?? 0) != 0
|
||||||
self.snow = (try container.decodeIfPresent(Int32.self, forKey: "snow") ?? 0) != 0
|
self.snow = (try container.decodeIfPresent(Int32.self, forKey: "snow") ?? 0) != 0
|
||||||
|
self.inlineStickers = (try container.decodeIfPresent(Int32.self, forKey: "inlineStickers") ?? 0) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -118,6 +123,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers")
|
try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers")
|
||||||
try container.encode((self.experimentalBackground ? 1 : 0) as Int32, forKey: "experimentalBackground")
|
try container.encode((self.experimentalBackground ? 1 : 0) as Int32, forKey: "experimentalBackground")
|
||||||
try container.encode((self.snow ? 1 : 0) as Int32, forKey: "snow")
|
try container.encode((self.snow ? 1 : 0) as Int32, forKey: "snow")
|
||||||
|
try container.encode((self.inlineStickers ? 1 : 0) as Int32, forKey: "inlineStickers")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -162,11 +162,6 @@ private let setupLogs: Bool = {
|
|||||||
Logger.shared.log("TGVOIP", value)
|
Logger.shared.log("TGVOIP", value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
/*OngoingCallThreadLocalContextWebrtcCustom.setupLoggingFunction({ value in
|
|
||||||
if let value = value {
|
|
||||||
Logger.shared.log("TGVOIP", value)
|
|
||||||
}
|
|
||||||
})*/
|
|
||||||
return true
|
return true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -1241,7 +1236,7 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection {
|
|||||||
private func stateUpdated(state: NWConnection.State) {
|
private func stateUpdated(state: NWConnection.State) {
|
||||||
switch state {
|
switch state {
|
||||||
case .ready:
|
case .ready:
|
||||||
Logger.shared.log("CallSignaling", "Connection state is ready")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Connection state is ready")
|
||||||
|
|
||||||
var headerData = Data(count: 4)
|
var headerData = Data(count: 4)
|
||||||
headerData.withUnsafeMutableBytes { bytes in
|
headerData.withUnsafeMutableBytes { bytes in
|
||||||
@ -1249,7 +1244,7 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection {
|
|||||||
}
|
}
|
||||||
self.connection.send(content: headerData, completion: .contentProcessed({ error in
|
self.connection.send(content: headerData, completion: .contentProcessed({ error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
Logger.shared.log("CallSignaling", "Connection send header error: \(error)")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Connection send header error: \(error)")
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -1257,7 +1252,7 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection {
|
|||||||
|
|
||||||
self.sendPacket(payload: Data())
|
self.sendPacket(payload: Data())
|
||||||
case let .failed(error):
|
case let .failed(error):
|
||||||
Logger.shared.log("CallSignaling", "Connection error: \(error)")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Connection error: \(error)")
|
||||||
self.onIsClosed()
|
self.onIsClosed()
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -1293,10 +1288,10 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection {
|
|||||||
if payloadSize < 2 * 1024 * 1024 {
|
if payloadSize < 2 * 1024 * 1024 {
|
||||||
strongSelf.receivePacketPayload(size: Int(payloadSize))
|
strongSelf.receivePacketPayload(size: Int(payloadSize))
|
||||||
} else {
|
} else {
|
||||||
Logger.shared.log("CallSignaling", "Connection received invalid packet size: \(payloadSize)")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Connection received invalid packet size: \(payloadSize)")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.shared.log("CallSignaling", "Connection receive packet header error: \(String(describing: error))")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Connection receive packet header error: \(String(describing: error))")
|
||||||
strongSelf.onIsClosed()
|
strongSelf.onIsClosed()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1308,15 +1303,15 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let data = data, data.count == size {
|
if let data = data, data.count == size {
|
||||||
Logger.shared.log("CallSignaling", "Connection receive packet payload: \(data.count) bytes")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Connection receive packet payload: \(data.count) bytes")
|
||||||
|
|
||||||
if data.count < 16 + 4 {
|
if data.count < 16 + 4 {
|
||||||
Logger.shared.log("CallSignaling", "Connection invalid payload size: \(data.count)")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Connection invalid payload size: \(data.count)")
|
||||||
strongSelf.onIsClosed()
|
strongSelf.onIsClosed()
|
||||||
} else {
|
} else {
|
||||||
let readPeerTag = data.subdata(in: 0 ..< 16)
|
let readPeerTag = data.subdata(in: 0 ..< 16)
|
||||||
if readPeerTag != strongSelf.peerTag {
|
if readPeerTag != strongSelf.peerTag {
|
||||||
Logger.shared.log("CallSignaling", "Peer tag mismatch: \(hexString(readPeerTag))")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Peer tag mismatch: \(hexString(readPeerTag))")
|
||||||
strongSelf.onIsClosed()
|
strongSelf.onIsClosed()
|
||||||
} else {
|
} else {
|
||||||
let actualPayloadSize = data.withUnsafeBytes { bytes -> UInt32 in
|
let actualPayloadSize = data.withUnsafeBytes { bytes -> UInt32 in
|
||||||
@ -1326,7 +1321,7 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if Int(actualPayloadSize) > data.count - 16 - 4 {
|
if Int(actualPayloadSize) > data.count - 16 - 4 {
|
||||||
Logger.shared.log("CallSignaling", "Connection invalid actual payload size: \(actualPayloadSize)")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Connection invalid actual payload size: \(actualPayloadSize)")
|
||||||
strongSelf.onIsClosed()
|
strongSelf.onIsClosed()
|
||||||
} else {
|
} else {
|
||||||
if !strongSelf.isConnected {
|
if !strongSelf.isConnected {
|
||||||
@ -1348,7 +1343,7 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.shared.log("CallSignaling", "Connection receive packet payload error: \(String(describing: error))")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Connection receive packet payload error: \(String(describing: error))")
|
||||||
strongSelf.onIsClosed()
|
strongSelf.onIsClosed()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1397,11 +1392,11 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.shared.log("CallSignaling", "Send packet payload: \(totalSize) bytes")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Send packet payload: \(totalSize) bytes")
|
||||||
|
|
||||||
self.connection.send(content: sendBuffer, isComplete: true, completion: .contentProcessed({ error in
|
self.connection.send(content: sendBuffer, isComplete: true, completion: .contentProcessed({ error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
Logger.shared.log("CallSignaling", "Connection send payload error: \(error)")
|
OngoingCallThreadLocalContextWebrtc.logMessage("CallSignaling: Connection send payload error: \(error)")
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -195,6 +195,8 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
|||||||
|
|
||||||
@interface OngoingCallThreadLocalContextWebrtc : NSObject
|
@interface OngoingCallThreadLocalContextWebrtc : NSObject
|
||||||
|
|
||||||
|
+ (void)logMessage:(NSString * _Nonnull)string;
|
||||||
|
|
||||||
+ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction;
|
+ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction;
|
||||||
+ (void)applyServerConfig:(NSString * _Nullable)data;
|
+ (void)applyServerConfig:(NSString * _Nullable)data;
|
||||||
+ (int32_t)maxLayer;
|
+ (int32_t)maxLayer;
|
||||||
|
|||||||
@ -38,6 +38,7 @@
|
|||||||
#include "sdk/objc/native/src/objc_frame_buffer.h"
|
#include "sdk/objc/native/src/objc_frame_buffer.h"
|
||||||
#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
|
#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
|
||||||
#import "platform/darwin/TGRTCCVPixelBuffer.h"
|
#import "platform/darwin/TGRTCCVPixelBuffer.h"
|
||||||
|
#include "rtc_base/logging.h"
|
||||||
|
|
||||||
@implementation OngoingCallConnectionDescriptionWebrtc
|
@implementation OngoingCallConnectionDescriptionWebrtc
|
||||||
|
|
||||||
@ -783,6 +784,10 @@ static tgcalls::DataSaving callControllerDataSavingForType(OngoingCallDataSaving
|
|||||||
|
|
||||||
static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||||
|
|
||||||
|
+ (void)logMessage:(NSString * _Nonnull)string {
|
||||||
|
RTC_LOG(LS_INFO) << std::string(string.UTF8String);
|
||||||
|
}
|
||||||
|
|
||||||
+ (void)setupLoggingFunction:(void (*)(NSString *))loggingFunction {
|
+ (void)setupLoggingFunction:(void (*)(NSString *))loggingFunction {
|
||||||
InternalVoipLoggingFunction = loggingFunction;
|
InternalVoipLoggingFunction = loggingFunction;
|
||||||
tgcalls::SetLoggingFunction([](std::string const &string) {
|
tgcalls::SetLoggingFunction([](std::string const &string) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user