mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Emoji improvements
This commit is contained in:
parent
7b663a3445
commit
61b47ada27
@ -272,8 +272,8 @@ private final class VideoStickerFrameSourceCache {
|
|||||||
|
|
||||||
private let useCache = true
|
private let useCache = true
|
||||||
|
|
||||||
public func makeVideoStickerDirectFrameSource(queue: Queue, path: String, width: Int, height: Int, cachePathPrefix: String?) -> AnimatedStickerFrameSource? {
|
public func makeVideoStickerDirectFrameSource(queue: Queue, path: String, width: Int, height: Int, cachePathPrefix: String?, unpremultiplyAlpha: Bool) -> AnimatedStickerFrameSource? {
|
||||||
return VideoStickerDirectFrameSource(queue: queue, path: path, width: width, height: height, cachePathPrefix: cachePathPrefix)
|
return VideoStickerDirectFrameSource(queue: queue, path: path, width: width, height: height, cachePathPrefix: cachePathPrefix, unpremultiplyAlpha: unpremultiplyAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
||||||
@ -293,7 +293,7 @@ final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
|||||||
return self.currentFrame % self.frameCount
|
return self.currentFrame % self.frameCount
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(queue: Queue, path: String, width: Int, height: Int, cachePathPrefix: String?) {
|
init?(queue: Queue, path: String, width: Int, height: Int, cachePathPrefix: String?, unpremultiplyAlpha: Bool = true) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.path = path
|
self.path = path
|
||||||
self.width = width
|
self.width = width
|
||||||
@ -310,7 +310,7 @@ final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
|||||||
self.frameRate = Int(cache.frameRate)
|
self.frameRate = Int(cache.frameRate)
|
||||||
self.frameCount = Int(cache.frameCount)
|
self.frameCount = Int(cache.frameCount)
|
||||||
} else {
|
} else {
|
||||||
let source = SoftwareVideoSource(path: path, hintVP9: true)
|
let source = SoftwareVideoSource(path: path, hintVP9: true, unpremultiplyAlpha: unpremultiplyAlpha)
|
||||||
self.source = source
|
self.source = source
|
||||||
self.frameRate = min(30, source.getFramerate())
|
self.frameRate = min(30, source.getFramerate())
|
||||||
self.frameCount = 0
|
self.frameCount = 0
|
||||||
|
@ -125,7 +125,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func decode(frame: MediaTrackDecodableFrame, ptsOffset: CMTime?, forceARGB: Bool = false) -> MediaTrackFrame? {
|
public func decode(frame: MediaTrackDecodableFrame, ptsOffset: CMTime?, forceARGB: Bool = false, unpremultiplyAlpha: Bool = true) -> MediaTrackFrame? {
|
||||||
if self.isError {
|
if self.isError {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
|
|||||||
if let ptsOffset = ptsOffset {
|
if let ptsOffset = ptsOffset {
|
||||||
pts = CMTimeAdd(pts, ptsOffset)
|
pts = CMTimeAdd(pts, ptsOffset)
|
||||||
}
|
}
|
||||||
return convertVideoFrame(self.videoFrame, pts: pts, dts: pts, duration: frame.duration, forceARGB: forceARGB)
|
return convertVideoFrame(self.videoFrame, pts: pts, dts: pts, duration: frame.duration, forceARGB: forceARGB, unpremultiplyAlpha: unpremultiplyAlpha)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +268,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
|
|||||||
return UIImage(cgImage: image, scale: 1.0, orientation: .up)
|
return UIImage(cgImage: image, scale: 1.0, orientation: .up)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func convertVideoFrame(_ frame: FFMpegAVFrame, pts: CMTime, dts: CMTime, duration: CMTime, forceARGB: Bool = false) -> MediaTrackFrame? {
|
private func convertVideoFrame(_ frame: FFMpegAVFrame, pts: CMTime, dts: CMTime, duration: CMTime, forceARGB: Bool = false, unpremultiplyAlpha: Bool = true) -> MediaTrackFrame? {
|
||||||
if frame.data[0] == nil {
|
if frame.data[0] == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -335,7 +335,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder {
|
|||||||
var base: UnsafeMutableRawPointer
|
var base: UnsafeMutableRawPointer
|
||||||
if pixelFormat == kCVPixelFormatType_32ARGB {
|
if pixelFormat == kCVPixelFormatType_32ARGB {
|
||||||
let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
|
let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
|
||||||
decodeYUVAPlanesToRGBA(frame.data[0], Int32(frame.lineSize[0]), frame.data[1], Int32(frame.lineSize[1]), frame.data[2], Int32(frame.lineSize[2]), hasAlpha, frame.data[3], CVPixelBufferGetBaseAddress(pixelBuffer)?.assumingMemoryBound(to: UInt8.self), Int32(frame.width), Int32(frame.height), Int32(bytesPerRow))
|
decodeYUVAPlanesToRGBA(frame.data[0], Int32(frame.lineSize[0]), frame.data[1], Int32(frame.lineSize[1]), frame.data[2], Int32(frame.lineSize[2]), hasAlpha, frame.data[3], CVPixelBufferGetBaseAddress(pixelBuffer)?.assumingMemoryBound(to: UInt8.self), Int32(frame.width), Int32(frame.height), Int32(bytesPerRow), unpremultiplyAlpha)
|
||||||
} else {
|
} else {
|
||||||
let srcPlaneSize = Int(frame.lineSize[1]) * Int(frame.height / 2)
|
let srcPlaneSize = Int(frame.lineSize[1]) * Int(frame.height / 2)
|
||||||
let uvPlaneSize = srcPlaneSize * 2
|
let uvPlaneSize = srcPlaneSize * 2
|
||||||
|
@ -59,14 +59,16 @@ public final class SoftwareVideoSource {
|
|||||||
fileprivate let size: Int32
|
fileprivate let size: Int32
|
||||||
|
|
||||||
private let hintVP9: Bool
|
private let hintVP9: Bool
|
||||||
|
private let unpremultiplyAlpha: Bool
|
||||||
|
|
||||||
private var enqueuedFrames: [(MediaTrackFrame, CGFloat, CGFloat, Bool)] = []
|
private var enqueuedFrames: [(MediaTrackFrame, CGFloat, CGFloat, Bool)] = []
|
||||||
private var hasReadToEnd: Bool = false
|
private var hasReadToEnd: Bool = false
|
||||||
|
|
||||||
public init(path: String, hintVP9: Bool) {
|
public init(path: String, hintVP9: Bool, unpremultiplyAlpha: Bool) {
|
||||||
let _ = FFMpegMediaFrameSourceContextHelpers.registerFFMpegGlobals
|
let _ = FFMpegMediaFrameSourceContextHelpers.registerFFMpegGlobals
|
||||||
|
|
||||||
self.hintVP9 = hintVP9
|
self.hintVP9 = hintVP9
|
||||||
|
self.unpremultiplyAlpha = unpremultiplyAlpha
|
||||||
|
|
||||||
var s = stat()
|
var s = stat()
|
||||||
stat(path, &s)
|
stat(path, &s)
|
||||||
@ -228,7 +230,7 @@ public final class SoftwareVideoSource {
|
|||||||
if let maxPts = maxPts, CMTimeCompare(decodableFrame.pts, maxPts) < 0 {
|
if let maxPts = maxPts, CMTimeCompare(decodableFrame.pts, maxPts) < 0 {
|
||||||
ptsOffset = maxPts
|
ptsOffset = maxPts
|
||||||
}
|
}
|
||||||
result = (videoStream.decoder.decode(frame: decodableFrame, ptsOffset: ptsOffset, forceARGB: self.hintVP9), CGFloat(videoStream.rotationAngle), CGFloat(videoStream.aspect), loop)
|
result = (videoStream.decoder.decode(frame: decodableFrame, ptsOffset: ptsOffset, forceARGB: self.hintVP9, unpremultiplyAlpha: self.unpremultiplyAlpha), CGFloat(videoStream.rotationAngle), CGFloat(videoStream.aspect), loop)
|
||||||
} else {
|
} else {
|
||||||
result = (nil, CGFloat(videoStream.rotationAngle), CGFloat(videoStream.aspect), loop)
|
result = (nil, CGFloat(videoStream.rotationAngle), CGFloat(videoStream.aspect), loop)
|
||||||
}
|
}
|
||||||
|
@ -2537,6 +2537,9 @@ final class MessageHistoryTable: Table {
|
|||||||
}
|
}
|
||||||
for mediaId in attribute.associatedMediaIds {
|
for mediaId in attribute.associatedMediaIds {
|
||||||
if associatedMedia[mediaId] == nil {
|
if associatedMedia[mediaId] == nil {
|
||||||
|
if mediaId.id == 5364107552168613887 {
|
||||||
|
assert(true)
|
||||||
|
}
|
||||||
if let media = self.getMedia(mediaId) {
|
if let media = self.getMedia(mediaId) {
|
||||||
associatedMedia[mediaId] = media
|
associatedMedia[mediaId] = media
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,10 @@ final class MessageMediaTable: Table {
|
|||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exists(id: MediaId) -> Bool {
|
||||||
|
return self.valueBox.exists(self.table, key: self.key(id))
|
||||||
|
}
|
||||||
|
|
||||||
func get(_ id: MediaId, embedded: (MessageIndex, MediaId) -> Media?) -> (MessageIndex?, Media)? {
|
func get(_ id: MediaId, embedded: (MessageIndex, MediaId) -> Media?) -> (MessageIndex?, Media)? {
|
||||||
if let value = self.valueBox.get(self.table, key: self.key(id)) {
|
if let value = self.valueBox.get(self.table, key: self.key(id)) {
|
||||||
var type: Int8 = 0
|
var type: Int8 = 0
|
||||||
|
@ -740,6 +740,14 @@ public final class Transaction {
|
|||||||
return Set()
|
return Set()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func filterStoredMediaIds(namespace: MediaId.Namespace, ids: Set<Int64>) -> Set<Int64> {
|
||||||
|
assert(!self.disposed)
|
||||||
|
if let postbox = self.postbox {
|
||||||
|
return postbox.filterStoredMediaIds(namespace: namespace, ids: ids)
|
||||||
|
}
|
||||||
|
return Set()
|
||||||
|
}
|
||||||
|
|
||||||
public func storedMessageId(peerId: PeerId, namespace: MessageId.Namespace, timestamp: Int32) -> MessageId? {
|
public func storedMessageId(peerId: PeerId, namespace: MessageId.Namespace, timestamp: Int32) -> MessageId? {
|
||||||
assert(!self.disposed)
|
assert(!self.disposed)
|
||||||
return self.postbox?.storedMessageId(peerId: peerId, namespace: namespace, timestamp: timestamp)
|
return self.postbox?.storedMessageId(peerId: peerId, namespace: namespace, timestamp: timestamp)
|
||||||
@ -2352,6 +2360,18 @@ final class PostboxImpl {
|
|||||||
return filteredIds
|
return filteredIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileprivate func filterStoredMediaIds(namespace: MediaId.Namespace, ids: Set<Int64>) -> Set<Int64> {
|
||||||
|
var filteredIds = Set<Int64>()
|
||||||
|
|
||||||
|
for id in ids {
|
||||||
|
if !self.mediaTable.exists(id: MediaId(namespace: namespace, id: id)) {
|
||||||
|
filteredIds.insert(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredIds
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate func storedMessageId(peerId: PeerId, namespace: MessageId.Namespace, timestamp: Int32) -> MessageId? {
|
fileprivate func storedMessageId(peerId: PeerId, namespace: MessageId.Namespace, timestamp: Int32) -> MessageId? {
|
||||||
if let id = self.messageHistoryTable.findMessageId(peerId: peerId, namespace: namespace, timestamp: timestamp), id.namespace == namespace {
|
if let id = self.messageHistoryTable.findMessageId(peerId: peerId, namespace: namespace, timestamp: timestamp), id.namespace == namespace {
|
||||||
return id
|
return id
|
||||||
|
@ -116,7 +116,7 @@ public final class SoftwareVideoLayerFrameManager {
|
|||||||
let size = fileSize(path)
|
let size = fileSize(path)
|
||||||
Logger.shared.log("SoftwareVideo", "loaded video from \(stringForResource(resource)) (file size: \(String(describing: size))")
|
Logger.shared.log("SoftwareVideo", "loaded video from \(stringForResource(resource)) (file size: \(String(describing: size))")
|
||||||
|
|
||||||
let _ = strongSelf.source.swap(SoftwareVideoSource(path: path, hintVP9: strongSelf.hintVP9))
|
let _ = strongSelf.source.swap(SoftwareVideoSource(path: path, hintVP9: strongSelf.hintVP9, unpremultiplyAlpha: true))
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -263,7 +263,7 @@ public func cacheVideoStickerFrames(path: String, size: CGSize, cacheKey: String
|
|||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let cancelled = Atomic<Bool>(value: false)
|
let cancelled = Atomic<Bool>(value: false)
|
||||||
|
|
||||||
let source = SoftwareVideoSource(path: path, hintVP9: true)
|
let source = SoftwareVideoSource(path: path, hintVP9: true, unpremultiplyAlpha: true)
|
||||||
let queue = ThreadPoolQueue(threadPool: softwareVideoWorkers)
|
let queue = ThreadPoolQueue(threadPool: softwareVideoWorkers)
|
||||||
|
|
||||||
queue.addTask(ThreadPoolTask({ _ in
|
queue.addTask(ThreadPoolTask({ _ in
|
||||||
|
@ -1556,7 +1556,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !channelPeers.isEmpty {
|
if !channelPeers.isEmpty {
|
||||||
let resetSignal = resetChannels(network: network, peers: channelPeers, state: updatedState)
|
let resetSignal = resetChannels(postbox: postbox, network: network, peers: channelPeers, state: updatedState)
|
||||||
|> map { resultState -> (AccountMutableState, Bool, Int32?) in
|
|> map { resultState -> (AccountMutableState, Bool, Int32?) in
|
||||||
return (resultState, true, nil)
|
return (resultState, true, nil)
|
||||||
}
|
}
|
||||||
@ -1589,7 +1589,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resolveAssociatedMessages(network: network, state: finalState)
|
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|
||||||
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
||||||
return resolveMissingPeerChatInfos(network: network, state: resultingState)
|
return resolveMissingPeerChatInfos(network: network, state: resultingState)
|
||||||
|> map { resultingState, resolveError -> AccountFinalState in
|
|> map { resultingState, resolveError -> AccountFinalState in
|
||||||
@ -1599,11 +1599,40 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractEmojiFileIds(message: StoreMessage, fileIds: inout Set<Int64>) {
|
||||||
|
for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||||
|
for entity in attribute.entities {
|
||||||
|
switch entity.type {
|
||||||
|
case let .CustomEmoji(_, fileId):
|
||||||
|
fileIds.insert(fileId)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func resolveAssociatedMessages(network: Network, state: AccountMutableState) -> Signal<AccountMutableState, NoError> {
|
private func messagesFromOperations(state: AccountMutableState) -> [StoreMessage] {
|
||||||
|
var messages: [StoreMessage] = []
|
||||||
|
for operation in state.operations {
|
||||||
|
switch operation {
|
||||||
|
case let .AddMessages(messagesValue, _):
|
||||||
|
messages.append(contentsOf: messagesValue)
|
||||||
|
case let .EditMessage(_, message):
|
||||||
|
messages.append(message)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resolveAssociatedMessages(postbox: Postbox, network: Network, state: AccountMutableState) -> Signal<AccountMutableState, NoError> {
|
||||||
let missingMessageIds = state.referencedMessageIds.subtracting(state.storedMessages)
|
let missingMessageIds = state.referencedMessageIds.subtracting(state.storedMessages)
|
||||||
if missingMessageIds.isEmpty {
|
if missingMessageIds.isEmpty {
|
||||||
return .single(state)
|
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: state), result: state)
|
||||||
} else {
|
} else {
|
||||||
var missingPeers = false
|
var missingPeers = false
|
||||||
let _ = missingPeers
|
let _ = missingPeers
|
||||||
@ -1642,7 +1671,8 @@ private func resolveAssociatedMessages(network: Network, state: AccountMutableSt
|
|||||||
|
|
||||||
let fetchMessages = combineLatest(signals)
|
let fetchMessages = combineLatest(signals)
|
||||||
|
|
||||||
return fetchMessages |> map { results in
|
return fetchMessages
|
||||||
|
|> map { results in
|
||||||
var updatedState = state
|
var updatedState = state
|
||||||
for (messages, chats, users) in results {
|
for (messages, chats, users) in results {
|
||||||
if !messages.isEmpty {
|
if !messages.isEmpty {
|
||||||
@ -1663,6 +1693,9 @@ private func resolveAssociatedMessages(network: Network, state: AccountMutableSt
|
|||||||
}
|
}
|
||||||
return updatedState
|
return updatedState
|
||||||
}
|
}
|
||||||
|
|> mapToSignal { updatedState -> Signal<AccountMutableState, NoError> in
|
||||||
|
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: updatedState), result: updatedState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1825,7 +1858,7 @@ func pollChannelOnce(postbox: Postbox, network: Network, peerId: PeerId, stateMa
|
|||||||
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
|
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
|
||||||
return pollChannel(network: network, peer: peer, state: initialState)
|
return pollChannel(network: network, peer: peer, state: initialState)
|
||||||
|> mapToSignal { (finalState, _, timeout) -> Signal<Int32, NoError> in
|
|> mapToSignal { (finalState, _, timeout) -> Signal<Int32, NoError> in
|
||||||
return resolveAssociatedMessages(network: network, state: finalState)
|
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|
||||||
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
||||||
return resolveMissingPeerChatInfos(network: network, state: resultingState)
|
return resolveMissingPeerChatInfos(network: network, state: resultingState)
|
||||||
|> map { resultingState, _ -> AccountFinalState in
|
|> map { resultingState, _ -> AccountFinalState in
|
||||||
@ -1879,7 +1912,7 @@ public func standalonePollChannelOnce(postbox: Postbox, network: Network, peerId
|
|||||||
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
|
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
|
||||||
return pollChannel(network: network, peer: peer, state: initialState)
|
return pollChannel(network: network, peer: peer, state: initialState)
|
||||||
|> mapToSignal { (finalState, _, timeout) -> Signal<Never, NoError> in
|
|> mapToSignal { (finalState, _, timeout) -> Signal<Never, NoError> in
|
||||||
return resolveAssociatedMessages(network: network, state: finalState)
|
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|
||||||
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
||||||
return resolveMissingPeerChatInfos(network: network, state: resultingState)
|
return resolveMissingPeerChatInfos(network: network, state: resultingState)
|
||||||
|> map { resultingState, _ -> AccountFinalState in
|
|> map { resultingState, _ -> AccountFinalState in
|
||||||
@ -1900,7 +1933,7 @@ func keepPollingChannel(postbox: Postbox, network: Network, peerId: PeerId, stat
|
|||||||
|> delay(1.0, queue: .concurrentDefaultQueue())
|
|> delay(1.0, queue: .concurrentDefaultQueue())
|
||||||
}
|
}
|
||||||
|
|
||||||
private func resetChannels(network: Network, peers: [Peer], state: AccountMutableState) -> Signal<AccountMutableState, NoError> {
|
private func resetChannels(postbox: Postbox, network: Network, peers: [Peer], state: AccountMutableState) -> Signal<AccountMutableState, NoError> {
|
||||||
var inputPeers: [Api.InputDialogPeer] = []
|
var inputPeers: [Api.InputDialogPeer] = []
|
||||||
for peer in peers {
|
for peer in peers {
|
||||||
if let inputPeer = apiInputPeer(peer) {
|
if let inputPeer = apiInputPeer(peer) {
|
||||||
@ -2052,7 +2085,7 @@ private func resetChannels(network: Network, peers: [Peer], state: AccountMutabl
|
|||||||
|
|
||||||
// TODO: delete messages later than top
|
// TODO: delete messages later than top
|
||||||
|
|
||||||
return resolveAssociatedMessages(network: network, state: updatedState)
|
return resolveAssociatedMessages(postbox: postbox, network: network, state: updatedState)
|
||||||
|> mapToSignal { resultingState -> Signal<AccountMutableState, NoError> in
|
|> mapToSignal { resultingState -> Signal<AccountMutableState, NoError> in
|
||||||
return .single(resultingState)
|
return .single(resultingState)
|
||||||
}
|
}
|
||||||
|
@ -295,7 +295,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
|||||||
}
|
}
|
||||||
|
|
||||||
return combineLatest(folderSignals)
|
return combineLatest(folderSignals)
|
||||||
|> map { folders -> FetchedChatList? in
|
|> mapToSignal { folders -> Signal<FetchedChatList?, NoError> in
|
||||||
var peers: [Peer] = []
|
var peers: [Peer] = []
|
||||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||||
var notificationSettings: [PeerId: PeerNotificationSettings] = [:]
|
var notificationSettings: [PeerId: PeerNotificationSettings] = [:]
|
||||||
@ -372,7 +372,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return FetchedChatList(
|
let result: FetchedChatList? = FetchedChatList(
|
||||||
chatPeerIds: parsedRemoteChats.itemIds + (pinnedItemIds ?? []),
|
chatPeerIds: parsedRemoteChats.itemIds + (pinnedItemIds ?? []),
|
||||||
peers: peers,
|
peers: peers,
|
||||||
peerPresences: peerPresences,
|
peerPresences: peerPresences,
|
||||||
@ -390,6 +390,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
|||||||
folderSummaries: folderSummaries,
|
folderSummaries: folderSummaries,
|
||||||
peerGroupIds: peerGroupIds
|
peerGroupIds: peerGroupIds
|
||||||
)
|
)
|
||||||
|
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: storeMessages, result: result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,58 @@ enum FetchMessageHistoryHoleSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> T) -> Signal<T, NoError> {
|
func resolveUnknownEmojiFiles<T>(postbox: Postbox, source: FetchMessageHistoryHoleSource, messages: [StoreMessage], result: T) -> Signal<T, NoError> {
|
||||||
|
var fileIds = Set<Int64>()
|
||||||
|
|
||||||
|
for message in messages {
|
||||||
|
extractEmojiFileIds(message: message, fileIds: &fileIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileIds.isEmpty {
|
||||||
|
return .single(result)
|
||||||
|
} else {
|
||||||
|
return postbox.transaction { transaction -> Set<Int64> in
|
||||||
|
return transaction.filterStoredMediaIds(namespace: Namespaces.Media.CloudFile, ids: fileIds)
|
||||||
|
}
|
||||||
|
|> mapToSignal { unknownIds -> Signal<T, NoError> in
|
||||||
|
if unknownIds.isEmpty {
|
||||||
|
return .single(result)
|
||||||
|
} else {
|
||||||
|
var signals: [Signal<[Api.Document]?, NoError>] = []
|
||||||
|
var remainingIds = Array(unknownIds)
|
||||||
|
while !remainingIds.isEmpty {
|
||||||
|
let partIdCount = min(100, remainingIds.count)
|
||||||
|
let partIds = remainingIds.prefix(partIdCount)
|
||||||
|
remainingIds.removeFirst(partIdCount)
|
||||||
|
signals.append(source.request(Api.functions.messages.getCustomEmojiDocuments(documentId: Array(partIds)))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<[Api.Document]?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return combineLatest(signals)
|
||||||
|
|> mapToSignal { documentSets -> Signal<T, NoError> in
|
||||||
|
return postbox.transaction { transaction -> T in
|
||||||
|
for documentSet in documentSets {
|
||||||
|
if let documentSet = documentSet {
|
||||||
|
for document in documentSet {
|
||||||
|
if let file = telegramMediaFileFromApiDocument(document) {
|
||||||
|
transaction.storeMediaIfNotPresent(media: file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMessageHistoryHoleSource, peers: [PeerId: Peer], storeMessages: [StoreMessage], _ f: @escaping (Transaction, [Peer], [StoreMessage]) -> T) -> Signal<T, NoError> {
|
||||||
return postbox.transaction { transaction -> Signal<T, NoError> in
|
return postbox.transaction { transaction -> Signal<T, NoError> in
|
||||||
var storedIds = Set<MessageId>()
|
var storedIds = Set<MessageId>()
|
||||||
var referencedIds = Set<MessageId>()
|
var referencedIds = Set<MessageId>()
|
||||||
@ -60,7 +111,12 @@ func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMessageHis
|
|||||||
referencedIds.subtract(transaction.filterStoredMessageIds(referencedIds))
|
referencedIds.subtract(transaction.filterStoredMessageIds(referencedIds))
|
||||||
|
|
||||||
if referencedIds.isEmpty {
|
if referencedIds.isEmpty {
|
||||||
return .single(f(transaction, [], []))
|
return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages, result: Void())
|
||||||
|
|> mapToSignal { _ -> Signal<T, NoError> in
|
||||||
|
return postbox.transaction { transaction -> T in
|
||||||
|
return f(transaction, [], [])
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var signals: [Signal<([Api.Message], [Api.Chat], [Api.User]), NoError>] = []
|
var signals: [Signal<([Api.Message], [Api.Chat], [Api.User]), NoError>] = []
|
||||||
for (peerId, messageIds) in messagesIdsGroupedByPeerId(referencedIds) {
|
for (peerId, messageIds) in messagesIdsGroupedByPeerId(referencedIds) {
|
||||||
@ -117,8 +173,12 @@ func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMessageHis
|
|||||||
additionalPeers.append(TelegramUser(user: user))
|
additionalPeers.append(TelegramUser(user: user))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return postbox.transaction { transaction -> T in
|
|
||||||
return f(transaction, additionalPeers, additionalMessages)
|
return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages + additionalMessages, result: Void())
|
||||||
|
|> mapToSignal { _ -> Signal<T, NoError> in
|
||||||
|
return postbox.transaction { transaction -> T in
|
||||||
|
return f(transaction, additionalPeers, additionalMessages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -528,7 +588,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> FetchMessageHistoryHoleResult in
|
return withResolvedAssociatedMessages(postbox: postbox, source: source, peers: Dictionary(peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: storeMessages, { transaction, additionalPeers, additionalMessages -> FetchMessageHistoryHoleResult? in
|
||||||
let _ = transaction.addMessages(storeMessages, location: .Random)
|
let _ = transaction.addMessages(storeMessages, location: .Random)
|
||||||
let _ = transaction.addMessages(additionalMessages, location: .Random)
|
let _ = transaction.addMessages(additionalMessages, location: .Random)
|
||||||
var filledRange: ClosedRange<MessageId.Id>
|
var filledRange: ClosedRange<MessageId.Id>
|
||||||
@ -623,13 +683,14 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
|
|||||||
|
|
||||||
print("fetchMessageHistoryHole for \(peerInput) space \(space) done")
|
print("fetchMessageHistoryHole for \(peerInput) space \(space) done")
|
||||||
|
|
||||||
return FetchMessageHistoryHoleResult(
|
let result = FetchMessageHistoryHoleResult(
|
||||||
removedIndices: IndexSet(integersIn: Int(filledRange.lowerBound) ... Int(filledRange.upperBound)),
|
removedIndices: IndexSet(integersIn: Int(filledRange.lowerBound) ... Int(filledRange.upperBound)),
|
||||||
strictRemovedIndices: strictFilledIndices,
|
strictRemovedIndices: strictFilledIndices,
|
||||||
actualPeerId: storeMessages.first?.id.peerId,
|
actualPeerId: storeMessages.first?.id.peerId,
|
||||||
actualThreadId: storeMessages.first?.threadId,
|
actualThreadId: storeMessages.first?.threadId,
|
||||||
ids: fullIds
|
ids: fullIds
|
||||||
)
|
)
|
||||||
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -665,7 +726,7 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId
|
|||||||
}
|
}
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
}
|
}
|
||||||
return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), peers: Dictionary(fetchedChats.peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages in
|
return withResolvedAssociatedMessages(postbox: postbox, source: .network(network), peers: Dictionary(fetchedChats.peers.map({ ($0.id, $0) }), uniquingKeysWith: { lhs, _ in lhs }), storeMessages: fetchedChats.storeMessages, { transaction, additionalPeers, additionalMessages -> Void in
|
||||||
updatePeers(transaction: transaction, peers: fetchedChats.peers + additionalPeers, update: { _, updated -> Peer in
|
updatePeers(transaction: transaction, peers: fetchedChats.peers + additionalPeers, update: { _, updated -> Peer in
|
||||||
return updated
|
return updated
|
||||||
})
|
})
|
||||||
@ -716,8 +777,6 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId
|
|||||||
for (groupId, summary) in fetchedChats.folderSummaries {
|
for (groupId, summary) in fetchedChats.folderSummaries {
|
||||||
transaction.resetPeerGroupSummary(groupId: groupId, namespace: Namespaces.Message.Cloud, summary: summary)
|
transaction.resetPeerGroupSummary(groupId: groupId, namespace: Namespaces.Message.Cloud, summary: summary)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
})
|
})
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
}
|
}
|
||||||
|
@ -235,10 +235,12 @@ private final class AnimationCacheItemWriterInternal {
|
|||||||
|
|
||||||
private var frames: [FrameMetadata] = []
|
private var frames: [FrameMetadata] = []
|
||||||
|
|
||||||
private let dctQuality: Int
|
private let dctQualityLuma: Int
|
||||||
|
private let dctQualityChroma: Int
|
||||||
|
|
||||||
init?(allocateTempFile: @escaping () -> String) {
|
init?(allocateTempFile: @escaping () -> String) {
|
||||||
self.dctQuality = 70
|
self.dctQualityLuma = 70
|
||||||
|
self.dctQualityChroma = 75
|
||||||
|
|
||||||
self.compressedPath = allocateTempFile()
|
self.compressedPath = allocateTempFile()
|
||||||
|
|
||||||
@ -297,7 +299,7 @@ private final class AnimationCacheItemWriterInternal {
|
|||||||
if let current = self.currentDctData {
|
if let current = self.currentDctData {
|
||||||
dctData = current
|
dctData = current
|
||||||
} else {
|
} else {
|
||||||
dctData = DctData(generatingTablesAtQuality: self.dctQuality)
|
dctData = DctData(generatingTablesAtQualityLuma: self.dctQualityLuma, chroma: self.dctQualityChroma)
|
||||||
self.currentDctData = dctData
|
self.currentDctData = dctData
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,12 +435,14 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
|
|||||||
|
|
||||||
private var frames: [FrameMetadata] = []
|
private var frames: [FrameMetadata] = []
|
||||||
|
|
||||||
private let dctQuality: Int
|
private let dctQualityLuma: Int
|
||||||
|
private let dctQualityChroma: Int
|
||||||
|
|
||||||
private let lock = Lock()
|
private let lock = Lock()
|
||||||
|
|
||||||
init?(queue: Queue, allocateTempFile: @escaping () -> String, completion: @escaping (CompressedResult?) -> Void) {
|
init?(queue: Queue, allocateTempFile: @escaping () -> String, completion: @escaping (CompressedResult?) -> Void) {
|
||||||
self.dctQuality = 70
|
self.dctQualityLuma = 70
|
||||||
|
self.dctQualityChroma = 75
|
||||||
|
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.compressedPath = allocateTempFile()
|
self.compressedPath = allocateTempFile()
|
||||||
@ -511,7 +515,7 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
|
|||||||
if let current = self.currentDctData {
|
if let current = self.currentDctData {
|
||||||
dctData = current
|
dctData = current
|
||||||
} else {
|
} else {
|
||||||
dctData = DctData(generatingTablesAtQuality: self.dctQuality)
|
dctData = DctData(generatingTablesAtQualityLuma: self.dctQualityLuma, chroma: self.dctQualityChroma)
|
||||||
self.currentDctData = dctData
|
self.currentDctData = dctData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,11 +173,11 @@ final class DctData {
|
|||||||
self.chromaDct = ImageDCT(table: chromaTableData)
|
self.chromaDct = ImageDCT(table: chromaTableData)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(generatingTablesAtQuality quality: Int) {
|
init(generatingTablesAtQualityLuma lumaQuality: Int, chroma chromaQuality: Int) {
|
||||||
self.lumaTable = ImageDCTTable(quality: quality, isChroma: false)
|
self.lumaTable = ImageDCTTable(quality: lumaQuality, isChroma: false)
|
||||||
self.lumaDct = ImageDCT(table: self.lumaTable)
|
self.lumaDct = ImageDCT(table: self.lumaTable)
|
||||||
|
|
||||||
self.chromaTable = ImageDCTTable(quality: quality, isChroma: true)
|
self.chromaTable = ImageDCTTable(quality: chromaQuality, isChroma: true)
|
||||||
self.chromaDct = ImageDCT(table: self.chromaTable)
|
self.chromaDct = ImageDCT(table: self.chromaTable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -599,7 +599,8 @@ private final class GroupHeaderLayer: UIView {
|
|||||||
color = theme.chat.inputPanel.primaryTextColor
|
color = theme.chat.inputPanel.primaryTextColor
|
||||||
needsTintText = false
|
needsTintText = false
|
||||||
} else {
|
} else {
|
||||||
color = theme.chat.inputMediaPanel.stickersSectionTextColor.withMultipliedAlpha(0.1)
|
//color = theme.chat.inputMediaPanel.stickersSectionTextColor.withMultipliedAlpha(0.1)
|
||||||
|
color = UIColor(white: 1.0, alpha: 0.0)
|
||||||
needsTintText = true
|
needsTintText = true
|
||||||
}
|
}
|
||||||
let subtitleColor = theme.chat.inputMediaPanel.stickersSectionTextColor.withMultipliedAlpha(0.1)
|
let subtitleColor = theme.chat.inputMediaPanel.stickersSectionTextColor.withMultipliedAlpha(0.1)
|
||||||
@ -1520,6 +1521,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
var itemFeaturedHeaderHeight: CGFloat
|
var itemFeaturedHeaderHeight: CGFloat
|
||||||
var nativeItemSize: CGFloat
|
var nativeItemSize: CGFloat
|
||||||
let visibleItemSize: CGFloat
|
let visibleItemSize: CGFloat
|
||||||
|
let playbackItemSize: CGFloat
|
||||||
var horizontalSpacing: CGFloat
|
var horizontalSpacing: CGFloat
|
||||||
var verticalSpacing: CGFloat
|
var verticalSpacing: CGFloat
|
||||||
var verticalGroupDefaultSpacing: CGFloat
|
var verticalGroupDefaultSpacing: CGFloat
|
||||||
@ -1543,6 +1545,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
case .compact:
|
case .compact:
|
||||||
minItemsPerRow = 8
|
minItemsPerRow = 8
|
||||||
self.nativeItemSize = 36.0
|
self.nativeItemSize = 36.0
|
||||||
|
self.playbackItemSize = 48.0
|
||||||
self.verticalSpacing = 9.0
|
self.verticalSpacing = 9.0
|
||||||
minSpacing = 9.0
|
minSpacing = 9.0
|
||||||
self.itemDefaultHeaderHeight = 24.0
|
self.itemDefaultHeaderHeight = 24.0
|
||||||
@ -1552,6 +1555,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
case .detailed:
|
case .detailed:
|
||||||
minItemsPerRow = 5
|
minItemsPerRow = 5
|
||||||
self.nativeItemSize = 71.0
|
self.nativeItemSize = 71.0
|
||||||
|
self.playbackItemSize = 96.0
|
||||||
self.verticalSpacing = 2.0
|
self.verticalSpacing = 2.0
|
||||||
minSpacing = 12.0
|
minSpacing = 12.0
|
||||||
self.itemDefaultHeaderHeight = 24.0
|
self.itemDefaultHeaderHeight = 24.0
|
||||||
@ -1820,79 +1824,59 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
if let file = file {
|
if let file = file {
|
||||||
if file.isAnimatedSticker || file.isVideoEmoji {
|
let loadAnimation: () -> Void = { [weak self] in
|
||||||
let loadAnimation: () -> Void = { [weak self] in
|
guard let strongSelf = self else {
|
||||||
guard let strongSelf = self else {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: { size, writer in
|
|
||||||
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
|
|
||||||
|
|
||||||
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
|
|
||||||
guard let result = result else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if file.isVideoEmoji {
|
|
||||||
cacheVideoAnimation(path: result, width: Int(size.width), height: Int(size.height), writer: writer)
|
|
||||||
} else if file.isAnimatedSticker {
|
|
||||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
|
|
||||||
writer.finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cacheLottieAnimation(data: data, width: Int(size.width), height: Int(size.height), writer: writer)
|
|
||||||
} else {
|
|
||||||
cacheStillSticker(path: result, width: Int(size.width), height: Int(size.height), writer: writer)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
let fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, fileReference: stickerPackFileReference(file), resource: file.resource).start()
|
|
||||||
|
|
||||||
return ActionDisposable {
|
|
||||||
dataDisposable.dispose()
|
|
||||||
fetchDisposable.dispose()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if attemptSynchronousLoad {
|
strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: { size, writer in
|
||||||
if !renderer.loadFirstFrameSynchronously(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize) {
|
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
|
||||||
self.updateDisplayPlaceholder(displayPlaceholder: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
loadAnimation()
|
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
|
||||||
} else {
|
guard let result = result else {
|
||||||
let _ = renderer.loadFirstFrame(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, completion: { [weak self] success in
|
return
|
||||||
loadAnimation()
|
}
|
||||||
|
|
||||||
if !success {
|
if file.isVideoEmoji {
|
||||||
guard let strongSelf = self else {
|
cacheVideoAnimation(path: result, width: Int(size.width), height: Int(size.height), writer: writer)
|
||||||
|
} else if file.isAnimatedSticker {
|
||||||
|
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
|
||||||
|
writer.finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
cacheLottieAnimation(data: data, width: Int(size.width), height: Int(size.height), writer: writer)
|
||||||
strongSelf.updateDisplayPlaceholder(displayPlaceholder: true)
|
} else {
|
||||||
|
cacheStillSticker(path: result, width: Int(size.width), height: Int(size.height), writer: writer)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
} else if let _ = file.dimensions {
|
|
||||||
let isSmall: Bool = false
|
|
||||||
self.disposable = (chatMessageSticker(account: context.account, file: file, small: isSmall, synchronousLoad: attemptSynchronousLoad)).start(next: { [weak self] resultTransform in
|
|
||||||
let boundingSize = CGSize(width: 93.0, height: 93.0)
|
|
||||||
let imageSize = boundingSize//dimensions.cgSize.aspectFitted(boundingSize)
|
|
||||||
|
|
||||||
if let image = resultTransform(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), resizeMode: .fill(.clear)))?.generateImage() {
|
let fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, fileReference: stickerPackFileReference(file), resource: file.resource).start()
|
||||||
Queue.mainQueue().async {
|
|
||||||
guard let strongSelf = self else {
|
return ActionDisposable {
|
||||||
return
|
dataDisposable.dispose()
|
||||||
}
|
fetchDisposable.dispose()
|
||||||
|
|
||||||
strongSelf.contents = image.cgImage
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if attemptSynchronousLoad {
|
||||||
|
if !renderer.loadFirstFrameSynchronously(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize) {
|
||||||
|
self.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||||
|
}
|
||||||
|
|
||||||
self.fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: isSmall)).start()
|
loadAnimation()
|
||||||
|
} else {
|
||||||
|
let _ = renderer.loadFirstFrame(target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, completion: { [weak self] success in
|
||||||
|
loadAnimation()
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.updateDisplayPlaceholder(displayPlaceholder: true)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else if let staticEmoji = staticEmoji {
|
} else if let staticEmoji = staticEmoji {
|
||||||
let image = generateImage(self.size, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in
|
let image = generateImage(self.size, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in
|
||||||
@ -1907,7 +1891,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
let string = NSAttributedString(string: staticEmoji, font: Font.regular(floor(32.0 * scaleFactor)), textColor: .black)
|
let string = NSAttributedString(string: staticEmoji, font: Font.regular(floor(32.0 * scaleFactor)), textColor: .black)
|
||||||
let boundingRect = string.boundingRect(with: scaledSize, options: .usesLineFragmentOrigin, context: nil)
|
let boundingRect = string.boundingRect(with: scaledSize, options: .usesLineFragmentOrigin, context: nil)
|
||||||
UIGraphicsPushContext(context)
|
UIGraphicsPushContext(context)
|
||||||
string.draw(at: CGPoint(x: (scaledSize.width - boundingRect.width) / 2.0 + boundingRect.minX, y: (scaledSize.height - boundingRect.height) / 2.0 + boundingRect.minY))
|
string.draw(at: CGPoint(x: floor((scaledSize.width - boundingRect.width) / 2.0 + boundingRect.minX), y: floor((scaledSize.height - boundingRect.height) / 2.0 + boundingRect.minY)))
|
||||||
UIGraphicsPopContext()
|
UIGraphicsPopContext()
|
||||||
})
|
})
|
||||||
self.contents = image?.cgImage
|
self.contents = image?.cgImage
|
||||||
@ -2120,8 +2104,8 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let shimmerHostView: PortalSourceView
|
private let shimmerHostView: PortalSourceView?
|
||||||
private let standaloneShimmerEffect: StandaloneShimmerEffect
|
private let standaloneShimmerEffect: StandaloneShimmerEffect?
|
||||||
|
|
||||||
private let backgroundView: BlurredBackgroundView
|
private let backgroundView: BlurredBackgroundView
|
||||||
private var vibrancyEffectView: UIVisualEffectView?
|
private var vibrancyEffectView: UIVisualEffectView?
|
||||||
@ -2155,8 +2139,13 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.backgroundView = BlurredBackgroundView(color: nil)
|
self.backgroundView = BlurredBackgroundView(color: nil)
|
||||||
|
|
||||||
self.shimmerHostView = PortalSourceView()
|
if ProcessInfo.processInfo.activeProcessorCount > 2 {
|
||||||
self.standaloneShimmerEffect = StandaloneShimmerEffect()
|
self.shimmerHostView = PortalSourceView()
|
||||||
|
self.standaloneShimmerEffect = StandaloneShimmerEffect()
|
||||||
|
} else {
|
||||||
|
self.shimmerHostView = nil
|
||||||
|
self.standaloneShimmerEffect = nil
|
||||||
|
}
|
||||||
|
|
||||||
self.mirrorContentScrollView = UIView()
|
self.mirrorContentScrollView = UIView()
|
||||||
self.mirrorContentScrollView.layer.anchorPoint = CGPoint()
|
self.mirrorContentScrollView.layer.anchorPoint = CGPoint()
|
||||||
@ -2170,13 +2159,15 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
|
|
||||||
self.addSubview(self.backgroundView)
|
self.addSubview(self.backgroundView)
|
||||||
|
|
||||||
self.shimmerHostView.alpha = 0.0
|
if let shimmerHostView = self.shimmerHostView {
|
||||||
self.addSubview(self.shimmerHostView)
|
shimmerHostView.alpha = 0.0
|
||||||
|
self.addSubview(shimmerHostView)
|
||||||
|
}
|
||||||
|
|
||||||
self.boundsChangeTrackerLayer.opacity = 0.0
|
self.boundsChangeTrackerLayer.opacity = 0.0
|
||||||
self.layer.addSublayer(self.boundsChangeTrackerLayer)
|
self.layer.addSublayer(self.boundsChangeTrackerLayer)
|
||||||
self.boundsChangeTrackerLayer.didEnterHierarchy = { [weak self] in
|
self.boundsChangeTrackerLayer.didEnterHierarchy = { [weak self] in
|
||||||
self?.standaloneShimmerEffect.updateLayer()
|
self?.standaloneShimmerEffect?.updateLayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.scrollView.delaysContentTouches = false
|
self.scrollView.delaysContentTouches = false
|
||||||
@ -3304,6 +3295,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
let itemNativeFitSize = itemDimensions.fitted(CGSize(width: itemLayout.nativeItemSize, height: itemLayout.nativeItemSize))
|
let itemNativeFitSize = itemDimensions.fitted(CGSize(width: itemLayout.nativeItemSize, height: itemLayout.nativeItemSize))
|
||||||
let itemVisibleFitSize = itemDimensions.fitted(CGSize(width: itemLayout.visibleItemSize, height: itemLayout.visibleItemSize))
|
let itemVisibleFitSize = itemDimensions.fitted(CGSize(width: itemLayout.visibleItemSize, height: itemLayout.visibleItemSize))
|
||||||
|
let itemPlaybackSize = itemDimensions.fitted(CGSize(width: itemLayout.playbackItemSize, height: itemLayout.playbackItemSize))
|
||||||
|
|
||||||
var animateItemIn = false
|
var animateItemIn = false
|
||||||
var updateItemLayerPlaceholder = false
|
var updateItemLayerPlaceholder = false
|
||||||
@ -3326,7 +3318,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
renderer: component.animationRenderer,
|
renderer: component.animationRenderer,
|
||||||
placeholderColor: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1),
|
placeholderColor: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1),
|
||||||
blurredBadgeColor: theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(0.5),
|
blurredBadgeColor: theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(0.5),
|
||||||
pointSize: itemVisibleFitSize,
|
pointSize: item.staticEmoji == nil ? itemPlaybackSize : itemVisibleFitSize,
|
||||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder, duration in
|
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder, duration in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -3525,10 +3517,12 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateShimmerIfNeeded() {
|
private func updateShimmerIfNeeded() {
|
||||||
if self.placeholdersContainerView.subviews.isEmpty {
|
if let standaloneShimmerEffect = self.standaloneShimmerEffect, let shimmerHostView = self.shimmerHostView {
|
||||||
self.standaloneShimmerEffect.layer = nil
|
if self.placeholdersContainerView.subviews.isEmpty {
|
||||||
} else {
|
standaloneShimmerEffect.layer = nil
|
||||||
self.standaloneShimmerEffect.layer = self.shimmerHostView.layer
|
} else {
|
||||||
|
standaloneShimmerEffect.layer = shimmerHostView.layer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3544,11 +3538,16 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.vibrancyEffectView == nil {
|
if self.vibrancyEffectView == nil {
|
||||||
let blurEffect = UIBlurEffect(style: .extraLight)
|
let style: UIBlurEffect.Style
|
||||||
|
style = .extraLight
|
||||||
|
let blurEffect = UIBlurEffect(style: style)
|
||||||
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
|
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
|
||||||
let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect)
|
let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect)
|
||||||
self.vibrancyEffectView = vibrancyEffectView
|
self.vibrancyEffectView = vibrancyEffectView
|
||||||
self.backgroundView.addSubview(vibrancyEffectView)
|
self.backgroundView.addSubview(vibrancyEffectView)
|
||||||
|
for subview in vibrancyEffectView.subviews {
|
||||||
|
let _ = subview
|
||||||
|
}
|
||||||
vibrancyEffectView.contentView.addSubview(self.mirrorContentScrollView)
|
vibrancyEffectView.contentView.addSubview(self.mirrorContentScrollView)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3577,11 +3576,15 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
|
|
||||||
self.pagerEnvironment = pagerEnvironment
|
self.pagerEnvironment = pagerEnvironment
|
||||||
|
|
||||||
transition.setFrame(view: self.shimmerHostView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
if let shimmerHostView = self.shimmerHostView {
|
||||||
|
transition.setFrame(view: shimmerHostView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||||
|
}
|
||||||
|
|
||||||
let shimmerBackgroundColor = keyboardChildEnvironment.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08)
|
if let standaloneShimmerEffect = self.standaloneShimmerEffect {
|
||||||
let shimmerForegroundColor = keyboardChildEnvironment.theme.list.itemBlocksBackgroundColor.withMultipliedAlpha(0.15)
|
let shimmerBackgroundColor = keyboardChildEnvironment.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08)
|
||||||
self.standaloneShimmerEffect.update(background: shimmerBackgroundColor, foreground: shimmerForegroundColor)
|
let shimmerForegroundColor = keyboardChildEnvironment.theme.list.itemBlocksBackgroundColor.withMultipliedAlpha(0.15)
|
||||||
|
standaloneShimmerEffect.update(background: shimmerBackgroundColor, foreground: shimmerForegroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
var previousItemPositions: [ItemLayer.Key: CGPoint]?
|
var previousItemPositions: [ItemLayer.Key: CGPoint]?
|
||||||
|
|
||||||
|
@ -449,7 +449,7 @@ public final class EntityKeyboardComponent: Component {
|
|||||||
if let file = itemGroup.items[0].file {
|
if let file = itemGroup.items[0].file {
|
||||||
topEmojiItems.append(EntityKeyboardTopPanelComponent.Item(
|
topEmojiItems.append(EntityKeyboardTopPanelComponent.Item(
|
||||||
id: itemGroup.supergroupId,
|
id: itemGroup.supergroupId,
|
||||||
isReorderable: true,
|
isReorderable: !itemGroup.isFeatured,
|
||||||
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
content: AnyComponent(EntityKeyboardAnimationTopPanelComponent(
|
||||||
context: component.emojiContent.context,
|
context: component.emojiContent.context,
|
||||||
file: file,
|
file: file,
|
||||||
|
@ -1449,6 +1449,8 @@ final class EntityKeyboardTopPanelComponent: Component {
|
|||||||
visibleBounds.origin.x -= 200.0
|
visibleBounds.origin.x -= 200.0
|
||||||
visibleBounds.size.width += 400.0
|
visibleBounds.size.width += 400.0
|
||||||
|
|
||||||
|
let scale = max(0.01, self.visibilityFraction)
|
||||||
|
|
||||||
var validIds = Set<AnyHashable>()
|
var validIds = Set<AnyHashable>()
|
||||||
let visibleItemRange = itemLayout.visibleItemRange(for: visibleBounds)
|
let visibleItemRange = itemLayout.visibleItemRange(for: visibleBounds)
|
||||||
if !self.items.isEmpty && visibleItemRange.maxIndex >= visibleItemRange.minIndex {
|
if !self.items.isEmpty && visibleItemRange.maxIndex >= visibleItemRange.minIndex {
|
||||||
@ -1477,10 +1479,10 @@ final class EntityKeyboardTopPanelComponent: Component {
|
|||||||
containerSize: itemOuterFrame.size
|
containerSize: itemOuterFrame.size
|
||||||
)
|
)
|
||||||
let itemFrame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize)
|
let itemFrame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize)
|
||||||
/*if index == visibleItemRange.minIndex, !itemTransition.animation.isImmediate {
|
|
||||||
print("\(index): \(itemView.frame) -> \(itemFrame)")
|
|
||||||
}*/
|
|
||||||
itemTransition.setFrame(view: itemView, frame: itemFrame)
|
itemTransition.setFrame(view: itemView, frame: itemFrame)
|
||||||
|
|
||||||
|
transition.setSublayerTransform(view: itemView, transform: CATransform3DMakeScale(scale, scale, 1.0))
|
||||||
|
transition.setAlpha(view: itemView, alpha: self.visibilityFraction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var removedIds: [AnyHashable] = []
|
var removedIds: [AnyHashable] = []
|
||||||
@ -1804,6 +1806,13 @@ final class EntityKeyboardTopPanelComponent: Component {
|
|||||||
self.highlightedIconBackgroundView.isHidden = true
|
self.highlightedIconBackgroundView.isHidden = true
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
if self.visibilityFraction < 0.5 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return super.hitTest(point, with: event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeView() -> View {
|
func makeView() -> View {
|
||||||
|
@ -127,14 +127,14 @@ public final class TextNodeWithEntities {
|
|||||||
var found = false
|
var found = false
|
||||||
string.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: fullRange, options: [], using: { value, range, stop in
|
string.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: fullRange, options: [], using: { value, range, stop in
|
||||||
if let value = value as? ChatTextInputTextCustomEmojiAttribute, let font = string.attribute(.font, at: range.location, effectiveRange: nil) as? UIFont {
|
if let value = value as? ChatTextInputTextCustomEmojiAttribute, let font = string.attribute(.font, at: range.location, effectiveRange: nil) as? UIFont {
|
||||||
let updatedSubstring = NSMutableAttributedString(string: "😀")
|
let updatedSubstring = NSMutableAttributedString(string: "&")
|
||||||
|
|
||||||
let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
|
let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
|
||||||
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
|
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
|
||||||
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange)
|
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange)
|
||||||
updatedSubstring.addAttribute(originalTextAttributeKey, value: string.attributedSubstring(from: range).string, range: replacementRange)
|
updatedSubstring.addAttribute(originalTextAttributeKey, value: string.attributedSubstring(from: range).string, range: replacementRange)
|
||||||
|
|
||||||
let itemSize = (font.pointSize * 24.0 / 17.0) * 0.5
|
let itemSize = (font.pointSize * 24.0 / 17.0)
|
||||||
|
|
||||||
let runDelegateData = RunDelegateData(
|
let runDelegateData = RunDelegateData(
|
||||||
ascent: font.ascender,
|
ascent: font.ascender,
|
||||||
|
@ -20,7 +20,7 @@ private func roundUp(_ numToRound: Int, multiple: Int) -> Int {
|
|||||||
|
|
||||||
public func cacheVideoAnimation(path: String, width: Int, height: Int, writer: AnimationCacheItemWriter) {
|
public func cacheVideoAnimation(path: String, width: Int, height: Int, writer: AnimationCacheItemWriter) {
|
||||||
writer.queue.async {
|
writer.queue.async {
|
||||||
guard let frameSource = makeVideoStickerDirectFrameSource(queue: writer.queue, path: path, width: roundUp(width, multiple: 16), height: roundUp(height, multiple: 16), cachePathPrefix: nil) else {
|
guard let frameSource = makeVideoStickerDirectFrameSource(queue: writer.queue, path: path, width: roundUp(width, multiple: 16), height: roundUp(height, multiple: 16), cachePathPrefix: nil, unpremultiplyAlpha: false) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let frameDuration = 1.0 / Double(frameSource.frameRate)
|
let frameDuration = 1.0 / Double(frameSource.frameRate)
|
||||||
|
@ -560,7 +560,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
self.historyNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
self.historyNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||||
|
|
||||||
self.textInputPanelNode = ChatTextInputPanelNode(presentationInterfaceState: chatPresentationInterfaceState, presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode), presentController: { [weak self] controller in
|
self.textInputPanelNode = ChatTextInputPanelNode(context: context, presentationInterfaceState: chatPresentationInterfaceState, presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode), presentController: { [weak self] controller in
|
||||||
self?.interfaceInteraction?.presentController(controller, nil)
|
self?.interfaceInteraction?.presentController(controller, nil)
|
||||||
})
|
})
|
||||||
self.textInputPanelNode?.storedInputLanguage = chatPresentationInterfaceState.interfaceState.inputLanguage
|
self.textInputPanelNode?.storedInputLanguage = chatPresentationInterfaceState.interfaceState.inputLanguage
|
||||||
|
@ -86,7 +86,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError> {
|
static func emojiInputData(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, isStandalone: Bool) -> Signal<EmojiPagerContentComponent, NoError> {
|
||||||
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||||
|> map { peer -> Bool in
|
|> map { peer -> Bool in
|
||||||
guard case let .user(user) = peer else {
|
guard case let .user(user) = peer else {
|
||||||
@ -218,29 +218,31 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for featuredEmojiPack in featuredEmojiPacks {
|
if !isStandalone {
|
||||||
if installedCollectionIds.contains(featuredEmojiPack.info.id) {
|
for featuredEmojiPack in featuredEmojiPacks {
|
||||||
continue
|
if installedCollectionIds.contains(featuredEmojiPack.info.id) {
|
||||||
}
|
|
||||||
|
|
||||||
for item in featuredEmojiPack.topItems {
|
|
||||||
let resultItem = EmojiPagerContentComponent.Item(
|
|
||||||
file: item.file,
|
|
||||||
staticEmoji: nil,
|
|
||||||
subgroupId: nil
|
|
||||||
)
|
|
||||||
|
|
||||||
let supergroupId = featuredEmojiPack.info.id
|
|
||||||
let groupId: AnyHashable = supergroupId
|
|
||||||
let isPremiumLocked: Bool = item.file.isPremiumEmoji && !hasPremium
|
|
||||||
if isPremiumLocked && isPremiumDisabled {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if let groupIndex = itemGroupIndexById[groupId] {
|
|
||||||
itemGroups[groupIndex].items.append(resultItem)
|
for item in featuredEmojiPack.topItems {
|
||||||
} else {
|
let resultItem = EmojiPagerContentComponent.Item(
|
||||||
itemGroupIndexById[groupId] = itemGroups.count
|
file: item.file,
|
||||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: true, isExpandable: true, items: [resultItem]))
|
staticEmoji: nil,
|
||||||
|
subgroupId: nil
|
||||||
|
)
|
||||||
|
|
||||||
|
let supergroupId = featuredEmojiPack.info.id
|
||||||
|
let groupId: AnyHashable = supergroupId
|
||||||
|
let isPremiumLocked: Bool = item.file.isPremiumEmoji && !hasPremium
|
||||||
|
if isPremiumLocked && isPremiumDisabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if let groupIndex = itemGroupIndexById[groupId] {
|
||||||
|
itemGroups[groupIndex].items.append(resultItem)
|
||||||
|
} else {
|
||||||
|
itemGroupIndexById[groupId] = itemGroups.count
|
||||||
|
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: true, isExpandable: true, items: [resultItem]))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,7 +303,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
animationRenderer = MultiAnimationRendererImpl()
|
animationRenderer = MultiAnimationRendererImpl()
|
||||||
//}
|
//}
|
||||||
|
|
||||||
let emojiItems = emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer)
|
let emojiItems = emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false)
|
||||||
|
|
||||||
let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks]
|
let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks]
|
||||||
let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.PremiumStickers, Namespaces.OrderedItemList.CloudPremiumStickers]
|
let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.PremiumStickers, Namespaces.OrderedItemList.CloudPremiumStickers]
|
||||||
@ -1484,6 +1486,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
stickerContent?.inputInteractionHolder.inputInteraction = self.stickerInputInteraction
|
stickerContent?.inputInteractionHolder.inputInteraction = self.stickerInputInteraction
|
||||||
self.currentInputData.emoji.inputInteractionHolder.inputInteraction = self.emojiInputInteraction
|
self.currentInputData.emoji.inputInteractionHolder.inputInteraction = self.emojiInputInteraction
|
||||||
|
|
||||||
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
let entityKeyboardSize = self.entityKeyboardView.update(
|
let entityKeyboardSize = self.entityKeyboardView.update(
|
||||||
transition: mappedTransition,
|
transition: mappedTransition,
|
||||||
component: AnyComponent(EntityKeyboardComponent(
|
component: AnyComponent(EntityKeyboardComponent(
|
||||||
@ -1559,6 +1563,13 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
)
|
)
|
||||||
transition.updateFrame(view: self.entityKeyboardView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: entityKeyboardSize))
|
transition.updateFrame(view: self.entityKeyboardView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: entityKeyboardSize))
|
||||||
|
|
||||||
|
let layoutTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||||
|
if layoutTime > 0.1 {
|
||||||
|
#if DEBUG
|
||||||
|
print("EntityKeyboard layout in \(layoutTime * 1000.0) ms")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
return (expandedHeight, 0.0)
|
return (expandedHeight, 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1849,7 +1860,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
|
|||||||
|
|
||||||
let semaphore = DispatchSemaphore(value: 0)
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
var emojiComponent: EmojiPagerContentComponent?
|
var emojiComponent: EmojiPagerContentComponent?
|
||||||
let _ = ChatEntityKeyboardInputNode.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer).start(next: { value in
|
let _ = ChatEntityKeyboardInputNode.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true).start(next: { value in
|
||||||
emojiComponent = value
|
emojiComponent = value
|
||||||
semaphore.signal()
|
semaphore.signal()
|
||||||
})
|
})
|
||||||
|
@ -39,7 +39,7 @@ func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceS
|
|||||||
editPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
editPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||||
return editPanelNode
|
return editPanelNode
|
||||||
} else {
|
} else {
|
||||||
let panelNode = EditAccessoryPanelNode(context: context, messageId: editMessage.messageId, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, nameDisplayOrder: chatPresentationInterfaceState.nameDisplayOrder, dateTimeFormat: chatPresentationInterfaceState.dateTimeFormat)
|
let panelNode = EditAccessoryPanelNode(context: context, messageId: editMessage.messageId, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, nameDisplayOrder: chatPresentationInterfaceState.nameDisplayOrder, dateTimeFormat: chatPresentationInterfaceState.dateTimeFormat, animationCache: chatControllerInteraction?.presentationContext.animationCache, animationRenderer: chatControllerInteraction?.presentationContext.animationRenderer)
|
||||||
panelNode.interfaceInteraction = interfaceInteraction
|
panelNode.interfaceInteraction = interfaceInteraction
|
||||||
return panelNode
|
return panelNode
|
||||||
}
|
}
|
||||||
|
@ -297,7 +297,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
|||||||
textInputPanelNode.context = context
|
textInputPanelNode.context = context
|
||||||
return (textInputPanelNode, nil)
|
return (textInputPanelNode, nil)
|
||||||
} else {
|
} else {
|
||||||
let panel = ChatTextInputPanelNode(presentationInterfaceState: chatPresentationInterfaceState, presentationContext: nil, presentController: { [weak interfaceInteraction] controller in
|
let panel = ChatTextInputPanelNode(context: context, presentationInterfaceState: chatPresentationInterfaceState, presentationContext: nil, presentController: { [weak interfaceInteraction] controller in
|
||||||
interfaceInteraction?.presentController(controller, nil)
|
interfaceInteraction?.presentController(controller, nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@ import PhotoResources
|
|||||||
import WebsiteType
|
import WebsiteType
|
||||||
import ChatMessageInteractiveMediaBadge
|
import ChatMessageInteractiveMediaBadge
|
||||||
import GalleryData
|
import GalleryData
|
||||||
|
import TextNodeWithEntities
|
||||||
|
import AnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
|
||||||
private let buttonFont = Font.semibold(13.0)
|
private let buttonFont = Font.semibold(13.0)
|
||||||
|
|
||||||
@ -217,7 +220,7 @@ final class ChatMessageAttachedContentButtonNode: HighlightTrackingButtonNode {
|
|||||||
|
|
||||||
final class ChatMessageAttachedContentNode: ASDisplayNode {
|
final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||||
private let lineNode: ASImageNode
|
private let lineNode: ASImageNode
|
||||||
private let textNode: TextNode
|
private let textNode: TextNodeWithEntities
|
||||||
private let inlineImageNode: TransformImageNode
|
private let inlineImageNode: TransformImageNode
|
||||||
private var contentImageNode: ChatMessageInteractiveMediaNode?
|
private var contentImageNode: ChatMessageInteractiveMediaNode?
|
||||||
private var contentInstantVideoNode: ChatMessageInteractiveInstantVideoNode?
|
private var contentInstantVideoNode: ChatMessageInteractiveInstantVideoNode?
|
||||||
@ -239,8 +242,20 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
|
|
||||||
var visibility: ListViewItemNodeVisibility = .none {
|
var visibility: ListViewItemNodeVisibility = .none {
|
||||||
didSet {
|
didSet {
|
||||||
self.contentImageNode?.visibility = self.visibility != .none
|
if oldValue != self.visibility {
|
||||||
self.contentInstantVideoNode?.visibility = self.visibility != .none
|
self.contentImageNode?.visibility = self.visibility != .none
|
||||||
|
self.contentInstantVideoNode?.visibility = self.visibility != .none
|
||||||
|
|
||||||
|
switch self.visibility {
|
||||||
|
case .none:
|
||||||
|
self.textNode.visibilityRect = nil
|
||||||
|
case let .visible(_, subRect):
|
||||||
|
var subRect = subRect
|
||||||
|
subRect.origin.x = 0.0
|
||||||
|
subRect.size.width = 10000.0
|
||||||
|
self.textNode.visibilityRect = subRect
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,11 +265,11 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
self.lineNode.displaysAsynchronously = false
|
self.lineNode.displaysAsynchronously = false
|
||||||
self.lineNode.displayWithoutProcessing = true
|
self.lineNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
self.textNode = TextNode()
|
self.textNode = TextNodeWithEntities()
|
||||||
self.textNode.isUserInteractionEnabled = false
|
self.textNode.textNode.isUserInteractionEnabled = false
|
||||||
self.textNode.displaysAsynchronously = false
|
self.textNode.textNode.displaysAsynchronously = false
|
||||||
self.textNode.contentsScale = UIScreenScale
|
self.textNode.textNode.contentsScale = UIScreenScale
|
||||||
self.textNode.contentMode = .topLeft
|
self.textNode.textNode.contentMode = .topLeft
|
||||||
|
|
||||||
self.inlineImageNode = TransformImageNode()
|
self.inlineImageNode = TransformImageNode()
|
||||||
self.inlineImageNode.contentAnimations = [.subsequentUpdates]
|
self.inlineImageNode.contentAnimations = [.subsequentUpdates]
|
||||||
@ -266,13 +281,13 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.lineNode)
|
self.addSubnode(self.lineNode)
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode.textNode)
|
||||||
|
|
||||||
self.addSubnode(self.statusNode)
|
self.addSubnode(self.statusNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
|
func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize, _ animationCache: AnimationCache, _ animationRenderer: MultiAnimationRenderer) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
|
||||||
let textAsyncLayout = TextNode.asyncLayout(self.textNode)
|
let textAsyncLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||||
let currentImage = self.media as? TelegramMediaImage
|
let currentImage = self.media as? TelegramMediaImage
|
||||||
let imageLayout = self.inlineImageNode.asyncLayout()
|
let imageLayout = self.inlineImageNode.asyncLayout()
|
||||||
let statusLayout = self.statusNode.asyncLayout()
|
let statusLayout = self.statusNode.asyncLayout()
|
||||||
@ -284,7 +299,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
|
|
||||||
let currentAdditionalImageBadgeNode = self.additionalImageBadgeNode
|
let currentAdditionalImageBadgeNode = self.additionalImageBadgeNode
|
||||||
|
|
||||||
return { presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, chatLocation, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize in
|
return { presentationData, automaticDownloadSettings, associatedData, attributes, context, controllerInteraction, message, messageRead, chatLocation, title, subtitle, text, entities, mediaAndFlags, mediaBadge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize, animationCache, animationRenderer in
|
||||||
let isPreview = presentationData.isPreview
|
let isPreview = presentationData.isPreview
|
||||||
let fontSize: CGFloat
|
let fontSize: CGFloat
|
||||||
if message.adAttribute != nil {
|
if message.adAttribute != nil {
|
||||||
@ -847,9 +862,24 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
animation.animator.updateFrame(layer: strongSelf.lineNode.layer, frame: CGRect(origin: CGPoint(x: 13.0, y: insets.top), size: CGSize(width: 2.0, height: adjustedLineHeight - insets.top - insets.bottom - 2.0)), completion: nil)
|
animation.animator.updateFrame(layer: strongSelf.lineNode.layer, frame: CGRect(origin: CGPoint(x: 13.0, y: insets.top), size: CGSize(width: 2.0, height: adjustedLineHeight - insets.top - insets.bottom - 2.0)), completion: nil)
|
||||||
strongSelf.lineNode.isHidden = !displayLine
|
strongSelf.lineNode.isHidden = !displayLine
|
||||||
|
|
||||||
strongSelf.textNode.displaysAsynchronously = !isPreview
|
strongSelf.textNode.textNode.displaysAsynchronously = !isPreview
|
||||||
|
|
||||||
let _ = textApply()
|
let _ = textApply(TextNodeWithEntities.Arguments(
|
||||||
|
context: context,
|
||||||
|
cache: animationCache,
|
||||||
|
renderer: animationRenderer,
|
||||||
|
placeholderColor: messageTheme.mediaPlaceholderColor,
|
||||||
|
attemptSynchronous: synchronousLoads
|
||||||
|
))
|
||||||
|
switch strongSelf.visibility {
|
||||||
|
case .none:
|
||||||
|
strongSelf.textNode.visibilityRect = nil
|
||||||
|
case let .visible(_, subRect):
|
||||||
|
var subRect = subRect
|
||||||
|
subRect.origin.x = 0.0
|
||||||
|
subRect.size.width = 10000.0
|
||||||
|
strongSelf.textNode.visibilityRect = subRect
|
||||||
|
}
|
||||||
|
|
||||||
if let imageFrame = imageFrame {
|
if let imageFrame = imageFrame {
|
||||||
if let updateImageSignal = updateInlineImageSignal {
|
if let updateImageSignal = updateInlineImageSignal {
|
||||||
@ -976,9 +1006,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
textVerticalOffset = contentMediaHeight + 7.0
|
textVerticalOffset = contentMediaHeight + 7.0
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.textNode.frame = textFrame.offsetBy(dx: 0.0, dy: textVerticalOffset)
|
strongSelf.textNode.textNode.frame = textFrame.offsetBy(dx: 0.0, dy: textVerticalOffset)
|
||||||
if let statusSizeAndApply = statusSizeAndApply {
|
if let statusSizeAndApply = statusSizeAndApply {
|
||||||
var statusFrame = CGRect(origin: CGPoint(x: strongSelf.textNode.frame.minX, y: strongSelf.textNode.frame.maxY), size: statusSizeAndApply.0)
|
var statusFrame = CGRect(origin: CGPoint(x: strongSelf.textNode.textNode.frame.minX, y: strongSelf.textNode.textNode.frame.maxY), size: statusSizeAndApply.0)
|
||||||
if let imageFrame = imageFrame {
|
if let imageFrame = imageFrame {
|
||||||
if statusFrame.maxY < imageFrame.maxY + 10.0 {
|
if statusFrame.maxY < imageFrame.maxY + 10.0 {
|
||||||
statusFrame.origin.y = max(statusFrame.minY, imageFrame.maxY + 2.0)
|
statusFrame.origin.y = max(statusFrame.minY, imageFrame.maxY + 2.0)
|
||||||
@ -1067,11 +1097,11 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||||
let textNodeFrame = self.textNode.frame
|
let textNodeFrame = self.textNode.textNode.frame
|
||||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
if let (index, attributes) = self.textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
var concealed = true
|
var concealed = true
|
||||||
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
if let (attributeText, fullText) = self.textNode.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||||
}
|
}
|
||||||
return .url(url: url, concealed: concealed)
|
return .url(url: url, concealed: concealed)
|
||||||
@ -1095,8 +1125,8 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
if let context = self.context, let message = self.message, let theme = self.theme {
|
if let context = self.context, let message = self.message, let theme = self.theme {
|
||||||
var rects: [CGRect]?
|
var rects: [CGRect]?
|
||||||
if let point = point {
|
if let point = point {
|
||||||
let textNodeFrame = self.textNode.frame
|
let textNodeFrame = self.textNode.textNode.frame
|
||||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
if let (index, attributes) = self.textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||||
let possibleNames: [String] = [
|
let possibleNames: [String] = [
|
||||||
TelegramTextAttributes.URL,
|
TelegramTextAttributes.URL,
|
||||||
TelegramTextAttributes.PeerMention,
|
TelegramTextAttributes.PeerMention,
|
||||||
@ -1107,7 +1137,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
]
|
]
|
||||||
for name in possibleNames {
|
for name in possibleNames {
|
||||||
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
|
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
|
||||||
rects = self.textNode.attributeRects(name: name, at: index)
|
rects = self.textNode.textNode.attributeRects(name: name, at: index)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1121,9 +1151,9 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
linkHighlightingNode = LinkHighlightingNode(color: message.effectivelyIncoming(context.account.peerId) ? theme.theme.chat.message.incoming.linkHighlightColor : theme.theme.chat.message.outgoing.linkHighlightColor)
|
linkHighlightingNode = LinkHighlightingNode(color: message.effectivelyIncoming(context.account.peerId) ? theme.theme.chat.message.incoming.linkHighlightColor : theme.theme.chat.message.outgoing.linkHighlightColor)
|
||||||
self.linkHighlightingNode = linkHighlightingNode
|
self.linkHighlightingNode = linkHighlightingNode
|
||||||
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode.textNode)
|
||||||
}
|
}
|
||||||
linkHighlightingNode.frame = self.textNode.frame
|
linkHighlightingNode.frame = self.textNode.textNode.frame
|
||||||
linkHighlightingNode.updateRects(rects)
|
linkHighlightingNode.updateRects(rects)
|
||||||
} else if let linkHighlightingNode = self.linkHighlightingNode {
|
} else if let linkHighlightingNode = self.linkHighlightingNode {
|
||||||
self.linkHighlightingNode = nil
|
self.linkHighlightingNode = nil
|
||||||
|
@ -9,6 +9,12 @@ import TelegramCore
|
|||||||
final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubbleContentNode {
|
final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubbleContentNode {
|
||||||
private let contentNode: ChatMessageAttachedContentNode
|
private let contentNode: ChatMessageAttachedContentNode
|
||||||
|
|
||||||
|
override var visibility: ListViewItemNodeVisibility {
|
||||||
|
didSet {
|
||||||
|
self.contentNode.visibility = visibility
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
self.contentNode = ChatMessageAttachedContentNode()
|
self.contentNode = ChatMessageAttachedContentNode()
|
||||||
|
|
||||||
@ -43,7 +49,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble
|
|||||||
}
|
}
|
||||||
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -9,6 +9,12 @@ import TelegramCore
|
|||||||
final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContentNode {
|
final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContentNode {
|
||||||
private let contentNode: ChatMessageAttachedContentNode
|
private let contentNode: ChatMessageAttachedContentNode
|
||||||
|
|
||||||
|
override var visibility: ListViewItemNodeVisibility {
|
||||||
|
didSet {
|
||||||
|
self.contentNode.visibility = visibility
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
self.contentNode = ChatMessageAttachedContentNode()
|
self.contentNode = ChatMessageAttachedContentNode()
|
||||||
|
|
||||||
@ -38,7 +44,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent
|
|||||||
let text: String = item.message.text
|
let text: String = item.message.text
|
||||||
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -9,6 +9,12 @@ import TelegramCore
|
|||||||
final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleContentNode {
|
final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleContentNode {
|
||||||
private let contentNode: ChatMessageAttachedContentNode
|
private let contentNode: ChatMessageAttachedContentNode
|
||||||
|
|
||||||
|
override var visibility: ListViewItemNodeVisibility {
|
||||||
|
didSet {
|
||||||
|
self.contentNode.visibility = visibility
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
self.contentNode = ChatMessageAttachedContentNode()
|
self.contentNode = ChatMessageAttachedContentNode()
|
||||||
|
|
||||||
@ -43,7 +49,7 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont
|
|||||||
}
|
}
|
||||||
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
override var visibility: ListViewItemNodeVisibility {
|
override var visibility: ListViewItemNodeVisibility {
|
||||||
didSet {
|
didSet {
|
||||||
self.contentNode.visibility = self.visibility
|
self.contentNode.visibility = visibility
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, .peer(id: item.message.id.peerId), title, nil, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, .peer(id: item.message.id.peerId), title, nil, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, automaticDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, nil, mediaAndFlags, nil, nil, nil, false, layoutConstants, preparePosition, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, automaticDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, nil, mediaAndFlags, nil, nil, nil, false, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@ import PhotoResources
|
|||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
import TextFormat
|
import TextFormat
|
||||||
import InvisibleInkDustNode
|
import InvisibleInkDustNode
|
||||||
|
import TextNodeWithEntities
|
||||||
|
import AnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
|
||||||
public final class ChatMessageNotificationItem: NotificationItem {
|
public final class ChatMessageNotificationItem: NotificationItem {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -69,7 +72,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
|||||||
private let avatarNode: AvatarNode
|
private let avatarNode: AvatarNode
|
||||||
private let titleIconNode: ASImageNode
|
private let titleIconNode: ASImageNode
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let textNode: TextNode
|
private let textNode: TextNodeWithEntities
|
||||||
private var dustNode: InvisibleInkDustNode?
|
private var dustNode: InvisibleInkDustNode?
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
|
|
||||||
@ -79,6 +82,9 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
|||||||
private var compact: Bool?
|
private var compact: Bool?
|
||||||
private var validLayout: CGFloat?
|
private var validLayout: CGFloat?
|
||||||
|
|
||||||
|
private var animationCache: AnimationCache?
|
||||||
|
private var multiAnimationRenderer: MultiAnimationRenderer?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.avatarNode = AvatarNode(font: avatarFont)
|
self.avatarNode = AvatarNode(font: avatarFont)
|
||||||
|
|
||||||
@ -90,8 +96,8 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
|||||||
self.titleIconNode.displayWithoutProcessing = true
|
self.titleIconNode.displayWithoutProcessing = true
|
||||||
self.titleIconNode.displaysAsynchronously = false
|
self.titleIconNode.displaysAsynchronously = false
|
||||||
|
|
||||||
self.textNode = TextNode()
|
self.textNode = TextNodeWithEntities()
|
||||||
self.textNode.isUserInteractionEnabled = false
|
self.textNode.textNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
|
|
||||||
@ -100,12 +106,20 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
|||||||
self.addSubnode(self.avatarNode)
|
self.addSubnode(self.avatarNode)
|
||||||
self.addSubnode(self.titleIconNode)
|
self.addSubnode(self.titleIconNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode.textNode)
|
||||||
self.addSubnode(self.imageNode)
|
self.addSubnode(self.imageNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupItem(_ item: ChatMessageNotificationItem, compact: Bool) {
|
func setupItem(_ item: ChatMessageNotificationItem, compact: Bool) {
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
|
if self.animationCache == nil {
|
||||||
|
self.animationCache = AnimationCacheImpl(basePath: item.context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||||
|
return TempBox.shared.tempFile(fileName: "file").path
|
||||||
|
})
|
||||||
|
self.multiAnimationRenderer = MultiAnimationRendererImpl()
|
||||||
|
}
|
||||||
|
|
||||||
self.compact = compact
|
self.compact = compact
|
||||||
if compact {
|
if compact {
|
||||||
self.avatarNode.font = compactAvatarFont
|
self.avatarNode.font = compactAvatarFont
|
||||||
@ -190,6 +204,8 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
|||||||
messageEntities = message.textEntitiesAttribute?.entities.filter { entity in
|
messageEntities = message.textEntitiesAttribute?.entities.filter { entity in
|
||||||
if case .Spoiler = entity.type {
|
if case .Spoiler = entity.type {
|
||||||
return true
|
return true
|
||||||
|
} else if case .CustomEmoji = entity.type {
|
||||||
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -384,10 +400,24 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
|||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: self.titleAttributedText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset - titleInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: self.titleAttributedText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset - titleInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
|
|
||||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
let makeTextLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: self.textAttributedText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: self.textAttributedText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
let _ = textApply()
|
|
||||||
|
if let item = self.item, let cache = self.animationCache, let renderer = self.multiAnimationRenderer {
|
||||||
|
let theme = item.context.sharedContext.currentPresentationData.with({ $0 }).theme
|
||||||
|
let _ = textApply(TextNodeWithEntities.Arguments(
|
||||||
|
context: item.context,
|
||||||
|
cache: cache,
|
||||||
|
renderer: renderer,
|
||||||
|
placeholderColor: theme.list.mediaPlaceholderColor,
|
||||||
|
attemptSynchronous: false
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let _ = textApply(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.textNode.visibilityRect = CGRect.infinite
|
||||||
|
|
||||||
let textSpacing: CGFloat = 1.0
|
let textSpacing: CGFloat = 1.0
|
||||||
|
|
||||||
@ -399,7 +429,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + textSpacing), size: textLayout.size)
|
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + textSpacing), size: textLayout.size)
|
||||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
transition.updateFrame(node: self.textNode.textNode, frame: textFrame)
|
||||||
|
|
||||||
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: width - 10.0 - imageSize.width, y: (panelHeight - imageSize.height) / 2.0), size: imageSize))
|
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: width - 10.0 - imageSize.width, y: (panelHeight - imageSize.height) / 2.0), size: imageSize))
|
||||||
|
|
||||||
@ -411,7 +441,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
|||||||
dustNode = InvisibleInkDustNode(textNode: nil)
|
dustNode = InvisibleInkDustNode(textNode: nil)
|
||||||
dustNode.isUserInteractionEnabled = false
|
dustNode.isUserInteractionEnabled = false
|
||||||
self.dustNode = dustNode
|
self.dustNode = dustNode
|
||||||
self.insertSubnode(dustNode, aboveSubnode: self.textNode)
|
self.insertSubnode(dustNode, aboveSubnode: self.textNode.textNode)
|
||||||
}
|
}
|
||||||
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
|
||||||
dustNode.update(size: dustNode.frame.size, color: presentationData.theme.inAppNotification.primaryTextColor, textColor: presentationData.theme.inAppNotification.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
dustNode.update(size: dustNode.frame.size, color: presentationData.theme.inAppNotification.primaryTextColor, textColor: presentationData.theme.inAppNotification.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
|
||||||
|
@ -373,7 +373,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
displayLine = false
|
displayLine = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize)
|
let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, item.read, item.chatLocation, title, subtitle, text, entities, mediaAndFlags, badge, actionIcon, actionTitle, displayLine, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer)
|
||||||
|
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||||
|
|
||||||
|
@ -588,7 +588,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
|
private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?
|
||||||
|
|
||||||
init(presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) {
|
init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) {
|
||||||
self.presentationInterfaceState = presentationInterfaceState
|
self.presentationInterfaceState = presentationInterfaceState
|
||||||
|
|
||||||
var hasSpoilers = true
|
var hasSpoilers = true
|
||||||
@ -652,6 +652,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.context = context
|
||||||
|
|
||||||
self.addSubnode(self.clippingNode)
|
self.addSubnode(self.clippingNode)
|
||||||
|
|
||||||
self.sendAsAvatarContainerNode.activated = { [weak self] gesture, _ in
|
self.sendAsAvatarContainerNode.activated = { [weak self] gesture, _ in
|
||||||
@ -1109,9 +1111,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
let baseFontSize = max(minInputFontSize, interfaceState.fontSize.baseDisplaySize)
|
let baseFontSize = max(minInputFontSize, interfaceState.fontSize.baseDisplaySize)
|
||||||
|
|
||||||
if let textInputNode = self.textInputNode {
|
if let textInputNode = self.textInputNode {
|
||||||
if let text = textInputNode.attributedText?.string {
|
if let text = textInputNode.attributedText {
|
||||||
let range = textInputNode.selectedRange
|
let range = textInputNode.selectedRange
|
||||||
textInputNode.attributedText = NSAttributedString(string: text, font: Font.regular(baseFontSize), textColor: textColor)
|
let updatedText = NSMutableAttributedString(attributedString: text)
|
||||||
|
updatedText.addAttribute(NSAttributedString.Key.foregroundColor, value: textColor, range: NSRange(location: 0, length: updatedText.length))
|
||||||
|
textInputNode.attributedText = updatedText
|
||||||
textInputNode.selectedRange = range
|
textInputNode.selectedRange = range
|
||||||
}
|
}
|
||||||
textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(baseFontSize), NSAttributedString.Key.foregroundColor.rawValue: textColor]
|
textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(baseFontSize), NSAttributedString.Key.foregroundColor.rawValue: textColor]
|
||||||
|
@ -13,6 +13,10 @@ import RadialStatusNode
|
|||||||
import PhotoResources
|
import PhotoResources
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
import ChatPresentationInterfaceState
|
import ChatPresentationInterfaceState
|
||||||
|
import TextNodeWithEntities
|
||||||
|
import AnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
import TextFormat
|
||||||
|
|
||||||
final class EditAccessoryPanelNode: AccessoryPanelNode {
|
final class EditAccessoryPanelNode: AccessoryPanelNode {
|
||||||
let dateTimeFormat: PresentationDateTimeFormat
|
let dateTimeFormat: PresentationDateTimeFormat
|
||||||
@ -22,7 +26,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
let lineNode: ASImageNode
|
let lineNode: ASImageNode
|
||||||
let iconNode: ASImageNode
|
let iconNode: ASImageNode
|
||||||
let titleNode: ImmediateTextNode
|
let titleNode: ImmediateTextNode
|
||||||
let textNode: ImmediateTextNode
|
let textNode: ImmediateTextNodeWithEntities
|
||||||
let imageNode: TransformImageNode
|
let imageNode: TransformImageNode
|
||||||
let dimNode: ASDisplayNode
|
let dimNode: ASDisplayNode
|
||||||
let drawIconNode: ASImageNode
|
let drawIconNode: ASImageNode
|
||||||
@ -71,7 +75,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
|
|
||||||
private var validLayout: (size: CGSize, inset: CGFloat, interfaceState: ChatPresentationInterfaceState)?
|
private var validLayout: (size: CGSize, inset: CGFloat, interfaceState: ChatPresentationInterfaceState)?
|
||||||
|
|
||||||
init(context: AccountContext, messageId: MessageId, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat) {
|
init(context: AccountContext, messageId: MessageId, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.messageId = messageId
|
self.messageId = messageId
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
@ -99,10 +103,21 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
self.titleNode.maximumNumberOfLines = 1
|
self.titleNode.maximumNumberOfLines = 1
|
||||||
self.titleNode.displaysAsynchronously = false
|
self.titleNode.displaysAsynchronously = false
|
||||||
|
|
||||||
self.textNode = ImmediateTextNode()
|
self.textNode = ImmediateTextNodeWithEntities()
|
||||||
self.textNode.maximumNumberOfLines = 1
|
self.textNode.maximumNumberOfLines = 1
|
||||||
self.textNode.displaysAsynchronously = false
|
self.textNode.displaysAsynchronously = false
|
||||||
self.textNode.isUserInteractionEnabled = true
|
self.textNode.isUserInteractionEnabled = true
|
||||||
|
self.textNode.visibility = true
|
||||||
|
|
||||||
|
if let animationCache = animationCache, let animationRenderer = animationRenderer {
|
||||||
|
self.textNode.arguments = TextNodeWithEntities.Arguments(
|
||||||
|
context: context,
|
||||||
|
cache: animationCache,
|
||||||
|
renderer: animationRenderer,
|
||||||
|
placeholderColor: theme.list.mediaPlaceholderColor,
|
||||||
|
attemptSynchronous: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
self.imageNode.contentAnimations = [.subsequentUpdates]
|
self.imageNode.contentAnimations = [.subsequentUpdates]
|
||||||
@ -238,6 +253,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
self.isPhoto = isPhoto
|
self.isPhoto = isPhoto
|
||||||
|
|
||||||
let isMedia: Bool
|
let isMedia: Bool
|
||||||
|
let isText: Bool
|
||||||
if let message = message {
|
if let message = message {
|
||||||
var effectiveMessage = message
|
var effectiveMessage = message
|
||||||
if let currentEditMediaReference = self.currentEditMediaReference {
|
if let currentEditMediaReference = self.currentEditMediaReference {
|
||||||
@ -247,11 +263,14 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
switch messageContentKind(contentSettings: self.context.currentContentSettings.with { $0 }, message: EngineMessage(effectiveMessage), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: self.context.account.peerId) {
|
switch messageContentKind(contentSettings: self.context.currentContentSettings.with { $0 }, message: EngineMessage(effectiveMessage), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: self.context.account.peerId) {
|
||||||
case .text:
|
case .text:
|
||||||
isMedia = false
|
isMedia = false
|
||||||
|
isText = true
|
||||||
default:
|
default:
|
||||||
isMedia = true
|
isMedia = true
|
||||||
|
isText = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
isMedia = false
|
isMedia = false
|
||||||
|
isText = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let canEditMedia: Bool
|
let canEditMedia: Bool
|
||||||
@ -268,7 +287,29 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
titleString = self.strings.Conversation_EditingMessagePanelTitle
|
titleString = self.strings.Conversation_EditingMessagePanelTitle
|
||||||
}
|
}
|
||||||
self.titleNode.attributedText = NSAttributedString(string: titleString, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor)
|
self.titleNode.attributedText = NSAttributedString(string: titleString, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor)
|
||||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: isMedia ? self.theme.chat.inputPanel.secondaryTextColor : self.theme.chat.inputPanel.primaryTextColor)
|
|
||||||
|
let textFont = Font.regular(14.0)
|
||||||
|
let messageText: NSAttributedString
|
||||||
|
if isText, let message = message {
|
||||||
|
let entities = (message.textEntitiesAttribute?.entities ?? []).filter { entity in
|
||||||
|
switch entity.type {
|
||||||
|
case .Spoiler, .CustomEmoji:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let textColor = self.theme.chat.inputPanel.primaryTextColor
|
||||||
|
if entities.count > 0 {
|
||||||
|
messageText = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message)
|
||||||
|
} else {
|
||||||
|
messageText = NSAttributedString(string: text, font: textFont, textColor: isMedia ? self.theme.chat.inputPanel.secondaryTextColor : self.theme.chat.inputPanel.primaryTextColor)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
messageText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: isMedia ? self.theme.chat.inputPanel.secondaryTextColor : self.theme.chat.inputPanel.primaryTextColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.textNode.attributedText = messageText
|
||||||
|
|
||||||
let headerString: String = titleString
|
let headerString: String = titleString
|
||||||
self.actionArea.accessibilityLabel = "\(headerString).\n\(text)"
|
self.actionArea.accessibilityLabel = "\(headerString).\n\(text)"
|
||||||
@ -325,8 +366,10 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
self.titleNode.attributedText = NSAttributedString(string: text, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor)
|
self.titleNode.attributedText = NSAttributedString(string: text, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let text = self.textNode.attributedText?.string {
|
if let text = self.textNode.attributedText {
|
||||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: self.theme.chat.inputPanel.primaryTextColor)
|
let mutableText = NSMutableAttributedString(attributedString: text)
|
||||||
|
mutableText.addAttribute(NSAttributedString.Key.foregroundColor, value: self.theme.chat.inputPanel.primaryTextColor, range: NSRange(location: 0, length: mutableText.length))
|
||||||
|
self.textNode.attributedText = mutableText
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (size, inset, interfaceState) = self.validLayout {
|
if let (size, inset, interfaceState) = self.validLayout {
|
||||||
|
@ -4,4 +4,4 @@ void encodeRGBAToYUVA(uint8_t *yuva, uint8_t const *argb, int width, int height,
|
|||||||
void resizeAndEncodeRGBAToYUVA(uint8_t *yuva, uint8_t const *argb, int width, int height, int bytesPerRow, int originalWidth, int originalHeight, int originalBytesPerRow, bool unpremultiply);
|
void resizeAndEncodeRGBAToYUVA(uint8_t *yuva, uint8_t const *argb, int width, int height, int bytesPerRow, int originalWidth, int originalHeight, int originalBytesPerRow, bool unpremultiply);
|
||||||
|
|
||||||
void decodeYUVAToRGBA(uint8_t const *yuva, uint8_t *argb, int width, int height, int bytesPerRow);
|
void decodeYUVAToRGBA(uint8_t const *yuva, uint8_t *argb, int width, int height, int bytesPerRow);
|
||||||
void decodeYUVAPlanesToRGBA(uint8_t const *srcYpData, int srcYpBytesPerRow, uint8_t const *srcCbData, int srcCbBytesPerRow, uint8_t const *srcCrData, int srcCrBytesPerRow, bool hasAlpha, uint8_t const *alphaData, uint8_t *argb, int width, int height, int bytesPerRow);
|
void decodeYUVAPlanesToRGBA(uint8_t const *srcYpData, int srcYpBytesPerRow, uint8_t const *srcCbData, int srcCbBytesPerRow, uint8_t const *srcCrData, int srcCrBytesPerRow, bool hasAlpha, uint8_t const *alphaData, uint8_t *argb, int width, int height, int bytesPerRow, bool unpremultiply);
|
||||||
|
@ -173,7 +173,7 @@ void decodeYUVAToRGBA(uint8_t const *yuva, uint8_t *argb, int width, int height,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void decodeYUVAPlanesToRGBA(uint8_t const *srcYpData, int srcYpBytesPerRow, uint8_t const *srcCbData, int srcCbBytesPerRow, uint8_t const *srcCrData, int srcCrBytesPerRow, bool hasAlpha, uint8_t const *alphaData, uint8_t *argb, int width, int height, int bytesPerRow) {
|
void decodeYUVAPlanesToRGBA(uint8_t const *srcYpData, int srcYpBytesPerRow, uint8_t const *srcCbData, int srcCbBytesPerRow, uint8_t const *srcCrData, int srcCrBytesPerRow, bool hasAlpha, uint8_t const *alphaData, uint8_t *argb, int width, int height, int bytesPerRow, bool unpremultiply) {
|
||||||
static vImage_YpCbCrToARGB info;
|
static vImage_YpCbCrToARGB info;
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
@ -220,7 +220,11 @@ void decodeYUVAPlanesToRGBA(uint8_t const *srcYpData, int srcYpBytesPerRow, uint
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t permuteMap[4] = {3, 2, 1, 0};
|
uint8_t permuteMap[4] = {3, 2, 1, 0};
|
||||||
error = vImageUnpremultiplyData_ARGB8888(&dest, &dest, kvImageDoNotTile);
|
if (unpremultiply) {
|
||||||
|
error = vImageUnpremultiplyData_ARGB8888(&dest, &dest, kvImageDoNotTile);
|
||||||
|
} else {
|
||||||
|
error = vImagePremultiplyData_ARGB8888(&dest, &dest, kvImageDoNotTile);
|
||||||
|
}
|
||||||
error = vImagePermuteChannels_ARGB8888(&dest, &dest, permuteMap, kvImageDoNotTile);
|
error = vImagePermuteChannels_ARGB8888(&dest, &dest, permuteMap, kvImageDoNotTile);
|
||||||
|
|
||||||
if (error != kvImageNoError) {
|
if (error != kvImageNoError) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user