Various improvements

This commit is contained in:
Ilya Laktyushin 2022-05-05 19:20:18 +04:00
parent 725bf7ae29
commit b997987108
21 changed files with 713 additions and 459 deletions

View File

@ -63,10 +63,11 @@ public protocol UniversalVideoDecoration: AnyObject {
} }
public enum UniversalVideoPriority: Int32, Comparable { public enum UniversalVideoPriority: Int32, Comparable {
case secondaryOverlay = 0 case minimal = 0
case embedded = 1 case secondaryOverlay = 1
case gallery = 2 case embedded = 2
case overlay = 3 case gallery = 3
case overlay = 4
public static func <(lhs: UniversalVideoPriority, rhs: UniversalVideoPriority) -> Bool { public static func <(lhs: UniversalVideoPriority, rhs: UniversalVideoPriority) -> Bool {
return lhs.rawValue < rhs.rawValue return lhs.rawValue < rhs.rawValue

View File

@ -52,6 +52,7 @@ swift_library(
"//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge", "//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge",
"//submodules/MediaPlayer:UniversalMediaPlayer", "//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/GalleryData:GalleryData", "//submodules/GalleryData:GalleryData",
"//submodules/GalleryUI:GalleryUI",
"//submodules/InstantPageUI:InstantPageUI", "//submodules/InstantPageUI:InstantPageUI",
"//submodules/ListSectionHeaderNode:ListSectionHeaderNode", "//submodules/ListSectionHeaderNode:ListSectionHeaderNode",
"//submodules/ChatInterfaceState:ChatInterfaceState", "//submodules/ChatInterfaceState:ChatInterfaceState",
@ -68,6 +69,8 @@ swift_library(
"//submodules/Components/ProgressIndicatorComponent:ProgressIndicatorComponent", "//submodules/Components/ProgressIndicatorComponent:ProgressIndicatorComponent",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/PremiumUI:PremiumUI", "//submodules/PremiumUI:PremiumUI",
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
"//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -3,6 +3,7 @@ import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import SwiftSignalKit import SwiftSignalKit
import Postbox
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import ItemListUI import ItemListUI
@ -19,6 +20,10 @@ import ContextUI
import ChatInterfaceState import ChatInterfaceState
import TextFormat import TextFormat
import InvisibleInkDustNode import InvisibleInkDustNode
import TelegramUniversalVideoContent
import UniversalMediaPlayer
import GalleryUI
import HierarchyTrackingLayer
public enum ChatListItemContent { public enum ChatListItemContent {
public final class DraftState: Equatable { public final class DraftState: Equatable {
@ -415,6 +420,8 @@ private final class ChatListMediaPreviewNode: ASDisplayNode {
} }
} }
private let maxVideoLoopCount = 3
class ChatListItemNode: ItemListRevealOptionsItemNode { class ChatListItemNode: ItemListRevealOptionsItemNode {
var item: ChatListItem? var item: ChatListItem?
@ -424,6 +431,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let contextContainer: ContextControllerSourceNode let contextContainer: ContextControllerSourceNode
let avatarNode: AvatarNode let avatarNode: AvatarNode
var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private let playbackStartDisposable = MetaDisposable()
private var videoLoopCount = 0
let titleNode: TextNode let titleNode: TextNode
let authorNode: TextNode let authorNode: TextNode
let measureNode: TextNode let measureNode: TextNode
@ -442,6 +454,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var credibilityIconNode: ASImageNode? var credibilityIconNode: ASImageNode?
let mutedIconNode: ASImageNode let mutedIconNode: ASImageNode
private var hierarchyTrackingLayer: HierarchyTrackingLayer?
private var cachedDataDisposable = MetaDisposable()
private var currentTextLeftCutout: CGFloat = 0.0 private var currentTextLeftCutout: CGFloat = 0.0
private var currentMediaPreviewSpecs: [(message: EngineMessage, media: EngineMedia, size: CGSize)] = [] private var currentMediaPreviewSpecs: [(message: EngineMessage, media: EngineMedia, size: CGSize)] = []
private var mediaPreviewNodes: [EngineMedia.Id: ChatListMediaPreviewNode] = [:] private var mediaPreviewNodes: [EngineMedia.Id: ChatListMediaPreviewNode] = [:]
@ -586,6 +601,46 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
} }
override var visibility: ListViewItemNodeVisibility {
didSet {
let wasVisible = self.visibilityStatus
let isVisible: Bool
switch self.visibility {
case let .visible(fraction):
isVisible = fraction > 0.2
case .none:
isVisible = false
}
if wasVisible != isVisible {
self.visibilityStatus = isVisible
}
}
}
private var visibilityStatus: Bool = false {
didSet {
if self.visibilityStatus != oldValue {
if self.visibilityStatus {
self.videoLoopCount = 0
}
self.updateVideoVisibility()
}
}
}
private var trackingIsInHierarchy: Bool = false {
didSet {
if self.trackingIsInHierarchy != oldValue {
Queue.mainQueue().justDispatch {
if self.trackingIsInHierarchy {
self.videoLoopCount = 0
}
self.updateVideoVisibility()
}
}
}
}
required init() { required init() {
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true self.backgroundNode.isLayerBacked = true
@ -675,6 +730,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
} }
deinit {
self.cachedDataDisposable.dispose()
self.playbackStartDisposable.dispose()
}
func setupItem(item: ChatListItem, synchronousLoads: Bool) { func setupItem(item: ChatListItem, synchronousLoads: Bool) {
let previousItem = self.item let previousItem = self.item
self.item = item self.item = item
@ -711,6 +771,65 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
overrideImage = .deletedIcon overrideImage = .deletedIcon
} }
self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0)) self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0))
if peer.isPremium {
let context = item.context
self.cachedDataDisposable.set((context.account.postbox.peerView(id: peer.id)
|> deliverOnMainQueue).start(next: { [weak self] peerView in
guard let strongSelf = self else {
return
}
let cachedPeerData = peerView.cachedData
if let cachedPeerData = cachedPeerData as? CachedUserData {
if let photo = cachedPeerData.photo, let video = photo.videoRepresentations.last, let peerReference = PeerReference(peer._asPeer()) {
let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value()
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false)
if videoContent.id != strongSelf.videoContent?.id {
strongSelf.videoNode?.removeFromSupernode()
strongSelf.videoContent = videoContent
}
if strongSelf.hierarchyTrackingLayer == nil {
let hierarchyTrackingLayer = HierarchyTrackingLayer()
hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.trackingIsInHierarchy = true
}
hierarchyTrackingLayer.didExitHierarchy = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.trackingIsInHierarchy = false
}
strongSelf.hierarchyTrackingLayer = hierarchyTrackingLayer
strongSelf.layer.addSublayer(hierarchyTrackingLayer)
}
} else {
strongSelf.videoContent = nil
strongSelf.hierarchyTrackingLayer?.removeFromSuperlayer()
strongSelf.hierarchyTrackingLayer = nil
}
strongSelf.updateVideoVisibility()
} else {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
}
}))
} else {
self.cachedDataDisposable.set(nil)
self.videoContent = nil
self.videoNode?.removeFromSupernode()
self.videoNode = nil
self.hierarchyTrackingLayer?.removeFromSuperlayer()
self.hierarchyTrackingLayer = nil
}
} }
self.contextContainer.isGestureEnabled = enablePreview && !item.editing self.contextContainer.isGestureEnabled = enablePreview && !item.editing
@ -1364,7 +1483,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if isSecret { if isSecret {
currentSecretIconImage = PresentationResourcesChatList.secretIcon(item.presentationData.theme) currentSecretIconImage = PresentationResourcesChatList.secretIcon(item.presentationData.theme)
} }
var credibilityIconOffset: CGFloat = 0.0
if !isPeerGroup && item.index.messageIndex.id.peerId != item.context.account.peerId { if !isPeerGroup && item.index.messageIndex.id.peerId != item.context.account.peerId {
if displayAsMessage { if displayAsMessage {
@ -1373,16 +1491,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let peer = messages.last?.author { if let peer = messages.last?.author {
if peer.isScam { if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isFake { } else if peer.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified { } else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
} else if peer.isPremium { } else if peer.isPremium {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
credibilityIconOffset = 2.0
} }
} }
default: default:
@ -1391,16 +1505,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer { } else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
if peer.isScam { if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isFake { } else if peer.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified { } else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
} else if peer.isPremium { } else if peer.isPremium {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme) currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
credibilityIconOffset = 2.0
} }
} }
} }
@ -1654,6 +1764,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame) transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame)
strongSelf.updateVideoVisibility()
let onlineFrame: CGRect let onlineFrame: CGRect
if onlineIsVoiceChat { if onlineIsVoiceChat {
@ -1744,40 +1855,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
secretIconNode.removeFromSupernode() secretIconNode.removeFromSupernode()
} }
var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleLayout.size.width + 3.0 + titleOffset
if let currentCredibilityIconImage = currentCredibilityIconImage {
let iconNode: ASImageNode
if let current = strongSelf.credibilityIconNode {
iconNode = current
} else {
iconNode = ASImageNode()
iconNode.isLayerBacked = true
iconNode.displaysAsynchronously = false
iconNode.displayWithoutProcessing = true
strongSelf.contextContainer.addSubnode(iconNode)
strongSelf.credibilityIconNode = iconNode
}
iconNode.image = currentCredibilityIconImage
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: contentRect.origin.y + credibilityIconOffset), size: currentCredibilityIconImage.size))
nextTitleIconOrigin += currentCredibilityIconImage.size.width + 5.0
} else if let credibilityIconNode = strongSelf.credibilityIconNode {
strongSelf.credibilityIconNode = nil
credibilityIconNode.removeFromSupernode()
}
if let currentMutedIconImage = currentMutedIconImage {
strongSelf.mutedIconNode.image = currentMutedIconImage
strongSelf.mutedIconNode.isHidden = false
transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: contentRect.origin.y - 3.0 + UIScreenPixel), size: currentMutedIconImage.size))
nextTitleIconOrigin += currentMutedIconImage.size.width + 1.0
} else {
strongSelf.mutedIconNode.image = nil
strongSelf.mutedIconNode.isHidden = true
}
let contentDelta = CGPoint(x: contentRect.origin.x - (strongSelf.titleNode.frame.minX - titleOffset), y: contentRect.origin.y - (strongSelf.titleNode.frame.minY - UIScreenPixel)) let contentDelta = CGPoint(x: contentRect.origin.x - (strongSelf.titleNode.frame.minX - titleOffset), y: contentRect.origin.y - (strongSelf.titleNode.frame.minY - UIScreenPixel))
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: contentRect.origin.x + titleOffset, y: contentRect.origin.y + UIScreenPixel), size: titleLayout.size) let titleFrame = CGRect(origin: CGPoint(x: contentRect.origin.x + titleOffset, y: contentRect.origin.y + UIScreenPixel), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame
let authorNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height), size: authorLayout.size) let authorNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height), size: authorLayout.size)
strongSelf.authorNode.frame = authorNodeFrame strongSelf.authorNode.frame = authorNodeFrame
let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.size.height.isZero ? 0.0 : (authorLayout.size.height - 3.0))), size: textLayout.size) let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.size.height.isZero ? 0.0 : (authorLayout.size.height - 3.0))), size: textLayout.size)
@ -1917,6 +1997,37 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.textNode.recursivelyEnsureDisplaySynchronously(true) strongSelf.textNode.recursivelyEnsureDisplaySynchronously(true)
} }
var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleLayout.size.width + 3.0 + titleOffset
if let currentCredibilityIconImage = currentCredibilityIconImage {
let iconNode: ASImageNode
if let current = strongSelf.credibilityIconNode {
iconNode = current
} else {
iconNode = ASImageNode()
iconNode.isLayerBacked = true
iconNode.displaysAsynchronously = false
iconNode.displayWithoutProcessing = true
strongSelf.contextContainer.addSubnode(iconNode)
strongSelf.credibilityIconNode = iconNode
}
iconNode.image = currentCredibilityIconImage
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.midY - currentCredibilityIconImage.size.height / 2.0) - UIScreenPixel), size: currentCredibilityIconImage.size))
nextTitleIconOrigin += currentCredibilityIconImage.size.width + 4.0
} else if let credibilityIconNode = strongSelf.credibilityIconNode {
strongSelf.credibilityIconNode = nil
credibilityIconNode.removeFromSupernode()
}
if let currentMutedIconImage = currentMutedIconImage {
strongSelf.mutedIconNode.image = currentMutedIconImage
strongSelf.mutedIconNode.isHidden = false
transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: floorToScreenPixels(titleFrame.midY - currentMutedIconImage.size.height / 2.0) - UIScreenPixel), size: currentMutedIconImage.size))
nextTitleIconOrigin += currentMutedIconImage.size.width + 1.0
} else {
strongSelf.mutedIconNode.image = nil
strongSelf.mutedIconNode.isHidden = true
}
let separatorInset: CGFloat let separatorInset: CGFloat
if case let .groupReference(_, _, _, _, hiddenByDefault) = item.content, hiddenByDefault { if case let .groupReference(_, _, _, _, hiddenByDefault) = item.content, hiddenByDefault {
separatorInset = 0.0 separatorInset = 0.0
@ -1998,6 +2109,80 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
} }
private func updateVideoVisibility() {
guard let item = self.item else {
return
}
let isVisible = self.visibilityStatus && self.trackingIsInHierarchy
if isVisible, let videoContent = self.videoContent, self.videoLoopCount != maxVideoLoopCount {
if self.videoNode == nil {
let context = item.context
let mediaManager = context.sharedContext.mediaManager
let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded)
videoNode.clipsToBounds = true
videoNode.isUserInteractionEnabled = false
videoNode.isHidden = true
videoNode.playbackCompleted = { [weak self] in
if let strongSelf = self {
strongSelf.videoLoopCount += 1
if strongSelf.videoLoopCount == maxVideoLoopCount {
if let videoNode = strongSelf.videoNode {
strongSelf.videoNode = nil
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in
videoNode?.removeFromSupernode()
})
}
}
}
}
if let _ = videoContent.startTimestamp {
self.playbackStartDisposable.set((videoNode.status
|> map { status -> Bool in
if let status = status, case .playing = status.status {
return true
} else {
return false
}
}
|> filter { playing in
return playing
}
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.15) {
strongSelf.videoNode?.isHidden = false
}
}
}))
} else {
self.playbackStartDisposable.set(nil)
videoNode.isHidden = false
}
videoNode.layer.cornerRadius = self.avatarNode.frame.size.width / 2.0
if #available(iOS 13.0, *) {
videoNode.layer.cornerCurve = .circular
}
videoNode.canAttachContent = true
videoNode.play()
self.contextContainer.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
self.videoNode = videoNode
}
} else if let videoNode = self.videoNode {
self.videoNode = nil
videoNode.removeFromSupernode()
}
if let videoNode = self.videoNode {
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
videoNode.frame = self.avatarNode.frame
}
}
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
super.updateRevealOffset(offset: offset, transition: transition) super.updateRevealOffset(offset: offset, transition: transition)
@ -2032,6 +2217,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var avatarFrame = self.avatarNode.frame var avatarFrame = self.avatarNode.frame
avatarFrame.origin.x = leftInset - avatarLeftInset + editingOffset + 10.0 + offset avatarFrame.origin.x = leftInset - avatarLeftInset + editingOffset + 10.0 + offset
transition.updateFrame(node: self.avatarNode, frame: avatarFrame) transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
if let videoNode = self.videoNode {
transition.updateFrame(node: videoNode, frame: avatarFrame)
}
var onlineFrame = self.onlineNode.frame var onlineFrame = self.onlineNode.frame
if self.onlineIsVoiceChat { if self.onlineIsVoiceChat {

View File

@ -333,6 +333,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
private let avatarNode: AvatarNode private let avatarNode: AvatarNode
private let titleNode: TextNode private let titleNode: TextNode
private var credibilityIconNode: ASImageNode?
private var verificationIconNode: ASImageNode? private var verificationIconNode: ASImageNode?
private let statusNode: TextNode private let statusNode: TextNode
private var badgeBackgroundNode: ASImageNode? private var badgeBackgroundNode: ASImageNode?
@ -562,11 +563,19 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
} }
} }
var verificationIconImage: UIImage? var currentCredibilityIconImage: UIImage?
switch item.peer { switch item.peer {
case let .peer(peer, _): case let .peer(peer, _):
if let peer = peer, peer.isVerified { if let peer = peer {
verificationIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme) if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if peer.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
} else if peer.isPremium {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
}
} }
case .deviceContact: case .deviceContact:
break break
@ -726,8 +735,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
} }
var additionalTitleInset: CGFloat = 0.0 var additionalTitleInset: CGFloat = 0.0
if let verificationIconImage = verificationIconImage { if let currentCredibilityIconImage = currentCredibilityIconImage {
additionalTitleInset += 3.0 + verificationIconImage.size.width additionalTitleInset += 3.0 + currentCredibilityIconImage.size.width
} }
if let actionButtons = actionButtons { if let actionButtons = actionButtons {
additionalTitleInset += 3.0 additionalTitleInset += 3.0
@ -900,7 +909,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 50.0, y: floor((nodeLayout.contentSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))) transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 50.0, y: floor((nodeLayout.contentSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)))
let _ = titleApply() let _ = titleApply()
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame.offsetBy(dx: revealOffset, dy: 0.0)) let titleFrame = titleFrame.offsetBy(dx: revealOffset, dy: 0.0)
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
strongSelf.titleNode.alpha = item.enabled ? 1.0 : 0.4 strongSelf.titleNode.alpha = item.enabled ? 1.0 : 0.4
strongSelf.statusNode.alpha = item.enabled ? 1.0 : 1.0 strongSelf.statusNode.alpha = item.enabled ? 1.0 : 1.0
@ -912,23 +922,23 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
strongSelf.statusNode.frame = statusFrame strongSelf.statusNode.frame = statusFrame
transition.animatePositionAdditive(node: strongSelf.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0)) transition.animatePositionAdditive(node: strongSelf.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
if let verificationIconImage = verificationIconImage { if let currentCredibilityIconImage = currentCredibilityIconImage {
if strongSelf.verificationIconNode == nil { let iconNode: ASImageNode
let verificationIconNode = ASImageNode() if let current = strongSelf.credibilityIconNode {
verificationIconNode.isLayerBacked = true iconNode = current
verificationIconNode.displayWithoutProcessing = true } else {
verificationIconNode.displaysAsynchronously = false iconNode = ASImageNode()
strongSelf.verificationIconNode = verificationIconNode iconNode.isLayerBacked = true
strongSelf.offsetContainerNode.addSubnode(verificationIconNode) iconNode.displaysAsynchronously = false
iconNode.displayWithoutProcessing = true
strongSelf.containerNode.addSubnode(iconNode)
strongSelf.credibilityIconNode = iconNode
} }
if let verificationIconNode = strongSelf.verificationIconNode { iconNode.image = currentCredibilityIconImage
verificationIconNode.image = verificationIconImage transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - currentCredibilityIconImage.size.height / 2.0) - UIScreenPixel), size: currentCredibilityIconImage.size))
} else if let credibilityIconNode = strongSelf.credibilityIconNode {
transition.updateFrame(node: verificationIconNode, frame: CGRect(origin: CGPoint(x: revealOffset + titleFrame.maxX + 3.0, y: titleFrame.minY + 3.0 + UIScreenPixel), size: verificationIconImage.size)) strongSelf.credibilityIconNode = nil
} credibilityIconNode.removeFromSupernode()
} else if let verificationIconNode = strongSelf.verificationIconNode {
strongSelf.verificationIconNode = nil
verificationIconNode.removeFromSupernode()
} }
if let actionButtons = actionButtons { if let actionButtons = actionButtons {
@ -1128,10 +1138,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
self.statusNode.frame = statusFrame self.statusNode.frame = statusFrame
transition.animatePositionAdditive(node: self.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0)) transition.animatePositionAdditive(node: self.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
if let verificationIconNode = self.verificationIconNode { if let credibilityIconNode = self.credibilityIconNode {
var iconFrame = verificationIconNode.frame var iconFrame = credibilityIconNode.frame
iconFrame.origin.x = titleFrame.maxX + 3.0 iconFrame.origin.x = titleFrame.maxX + 4.0
transition.updateFrame(node: verificationIconNode, frame: iconFrame) transition.updateFrame(node: credibilityIconNode, frame: iconFrame)
} }
if let badgeBackgroundNode = self.badgeBackgroundNode, let badgeTextNode = self.badgeTextNode { if let badgeBackgroundNode = self.badgeBackgroundNode, let badgeTextNode = self.badgeTextNode {

View File

@ -3676,7 +3676,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
private func updateItemNodesVisibilities(onlyPositive: Bool) { private func updateItemNodesVisibilities(onlyPositive: Bool) {
let visibilityRect = CGRect(origin: CGPoint(x: 0.0, y: self.insets.top), size: CGSize(width: self.visibleSize.width, height: self.visibleSize.height - self.insets.top - self.insets.bottom)) let insets: UIEdgeInsets = self.visualInsets ?? self.insets
let visibilityRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: self.visibleSize.width, height: self.visibleSize.height - insets.top - insets.bottom))
for itemNode in self.itemNodes { for itemNode in self.itemNodes {
let itemFrame = itemNode.apparentFrame let itemFrame = itemNode.apparentFrame
var visibility: ListViewItemNodeVisibility = .none var visibility: ListViewItemNodeVisibility = .none

View File

@ -460,6 +460,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
private let labelBadgeNode: ASImageNode private let labelBadgeNode: ASImageNode
private var labelArrowNode: ASImageNode? private var labelArrowNode: ASImageNode?
private let statusNode: TextNode private let statusNode: TextNode
private var credibilityIconNode: ASImageNode?
private var switchNode: SwitchNode? private var switchNode: SwitchNode?
private var checkNode: ASImageNode? private var checkNode: ASImageNode?
@ -600,6 +601,22 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
let labelDisclosureFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) let labelDisclosureFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
var updatedLabelBadgeImage: UIImage? var updatedLabelBadgeImage: UIImage?
var currentCredibilityIconImage: UIImage?
if item.peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if item.peer.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if item.peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
} else if item.peer.isPremium {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
}
var titleIconsWidth: CGFloat = 0.0
if let currentCredibilityIconImage = currentCredibilityIconImage {
titleIconsWidth += 4.0 + currentCredibilityIconImage.size.width
}
var badgeColor: UIColor? var badgeColor: UIColor?
if case .badge = item.label { if case .badge = item.label {
@ -842,7 +859,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - editingOffset - rightInset - labelLayout.size.width - labelInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - editingOffset - rightInset - labelLayout.size.width - labelInset - titleIconsWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset - labelLayout.size.width - labelInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset - labelLayout.size.width - labelInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var insets = itemListNeighborsGroupedInsets(neighbors, params) var insets = itemListNeighborsGroupedInsets(neighbors, params)
@ -1036,9 +1053,29 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size)) let titleFrame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size)
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: statusLayout.size)) transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: statusLayout.size))
if let currentCredibilityIconImage = currentCredibilityIconImage {
let iconNode: ASImageNode
if let current = strongSelf.credibilityIconNode {
iconNode = current
} else {
iconNode = ASImageNode()
iconNode.isLayerBacked = true
iconNode.displaysAsynchronously = false
iconNode.displayWithoutProcessing = true
strongSelf.containerNode.addSubnode(iconNode)
strongSelf.credibilityIconNode = iconNode
}
iconNode.image = currentCredibilityIconImage
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - currentCredibilityIconImage.size.height / 2.0) - UIScreenPixel), size: currentCredibilityIconImage.size))
} else if let credibilityIconNode = strongSelf.credibilityIconNode {
strongSelf.credibilityIconNode = nil
credibilityIconNode.removeFromSupernode()
}
if let currentSwitchNode = currentSwitchNode { if let currentSwitchNode = currentSwitchNode {
if currentSwitchNode !== strongSelf.switchNode { if currentSwitchNode !== strongSelf.switchNode {
strongSelf.switchNode = currentSwitchNode strongSelf.switchNode = currentSwitchNode
@ -1278,6 +1315,10 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size)) transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size))
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.statusNode.frame.minY), size: self.statusNode.bounds.size)) transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.statusNode.frame.minY), size: self.statusNode.bounds.size))
if let credibilityIconNode = self.credibilityIconNode {
transition.updateFrame(node: credibilityIconNode, frame: CGRect(origin: CGPoint(x: self.titleNode.frame.maxX + 4.0, y: credibilityIconNode.frame.minY), size: credibilityIconNode.bounds.size))
}
var rightLabelInset: CGFloat = 15.0 + params.rightInset var rightLabelInset: CGFloat = 15.0 + params.rightInset
if let labelArrowNode = self.labelArrowNode { if let labelArrowNode = self.labelArrowNode {

View File

@ -63,6 +63,7 @@ public final class CachedUserData: CachedPeerData {
public let hasScheduledMessages: Bool public let hasScheduledMessages: Bool
public let autoremoveTimeout: CachedPeerAutoremoveTimeout public let autoremoveTimeout: CachedPeerAutoremoveTimeout
public let themeEmoticon: String? public let themeEmoticon: String?
public let photo: TelegramMediaImage?
public let peerIds: Set<PeerId> public let peerIds: Set<PeerId>
public let messageIds: Set<MessageId> public let messageIds: Set<MessageId>
@ -82,11 +83,12 @@ public final class CachedUserData: CachedPeerData {
self.hasScheduledMessages = false self.hasScheduledMessages = false
self.autoremoveTimeout = .unknown self.autoremoveTimeout = .unknown
self.themeEmoticon = nil self.themeEmoticon = nil
self.photo = nil
self.peerIds = Set() self.peerIds = Set()
self.messageIds = Set() self.messageIds = Set()
} }
public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?) { public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: TelegramMediaImage?) {
self.about = about self.about = about
self.botInfo = botInfo self.botInfo = botInfo
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
@ -100,6 +102,7 @@ public final class CachedUserData: CachedPeerData {
self.hasScheduledMessages = hasScheduledMessages self.hasScheduledMessages = hasScheduledMessages
self.autoremoveTimeout = autoremoveTimeout self.autoremoveTimeout = autoremoveTimeout
self.themeEmoticon = themeEmoticon self.themeEmoticon = themeEmoticon
self.photo = photo
self.peerIds = Set<PeerId>() self.peerIds = Set<PeerId>()
@ -135,6 +138,12 @@ public final class CachedUserData: CachedPeerData {
self.autoremoveTimeout = decoder.decodeObjectForKey("artv", decoder: CachedPeerAutoremoveTimeout.init(decoder:)) as? CachedPeerAutoremoveTimeout ?? .unknown self.autoremoveTimeout = decoder.decodeObjectForKey("artv", decoder: CachedPeerAutoremoveTimeout.init(decoder:)) as? CachedPeerAutoremoveTimeout ?? .unknown
self.themeEmoticon = decoder.decodeOptionalStringForKey("te") self.themeEmoticon = decoder.decodeOptionalStringForKey("te")
if let photo = decoder.decodeObjectForKey("ph", decoder: { TelegramMediaImage(decoder: $0) }) as? TelegramMediaImage {
self.photo = photo
} else {
self.photo = nil
}
self.peerIds = Set<PeerId>() self.peerIds = Set<PeerId>()
var messageIds = Set<MessageId>() var messageIds = Set<MessageId>()
@ -182,6 +191,12 @@ public final class CachedUserData: CachedPeerData {
} else { } else {
encoder.encodeNil(forKey: "te") encoder.encodeNil(forKey: "te")
} }
if let photo = self.photo {
encoder.encodeObject(photo, forKey: "ph")
} else {
encoder.encodeNil(forKey: "ph")
}
} }
public func isEqual(to: CachedPeerData) -> Bool { public func isEqual(to: CachedPeerData) -> Bool {
@ -196,58 +211,62 @@ public final class CachedUserData: CachedPeerData {
return false return false
} }
return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.themeEmoticon == other.themeEmoticon return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.themeEmoticon == other.themeEmoticon && self.photo == other.photo
} }
public func withUpdatedAbout(_ about: String?) -> CachedUserData { public func withUpdatedAbout(_ about: String?) -> CachedUserData {
return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData { public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData { public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData { public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData { public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData { public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData { public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData { public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData { public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData { public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData { public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedUserData { public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
} }
public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedUserData { public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo)
}
public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo)
} }
} }

View File

@ -234,7 +234,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
previous = CachedUserData() previous = CachedUserData()
} }
switch fullUser { switch fullUser {
case let .userFull(userFullFlags, _, userFullAbout, userFullSettings, _, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _): case let .userFull(userFullFlags, _, userFullAbout, userFullSettings, profilePhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _):
let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:)) let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:))
let isBlocked = (userFullFlags & (1 << 0)) != 0 let isBlocked = (userFullFlags & (1 << 0)) != 0
let voiceCallsAvailable = (userFullFlags & (1 << 4)) != 0 let voiceCallsAvailable = (userFullFlags & (1 << 4)) != 0
@ -250,9 +250,12 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(userFullTtlPeriod)) let autoremoveTimeout: CachedPeerAutoremoveTimeout = .known(CachedPeerAutoremoveTimeout.Value(userFullTtlPeriod))
let photo = profilePhoto.flatMap { telegramMediaImageFromApiPhoto($0) }
return previous.withUpdatedAbout(userFullAbout).withUpdatedBotInfo(botInfo).withUpdatedCommonGroupCount(userFullCommonChatsCount).withUpdatedIsBlocked(isBlocked).withUpdatedVoiceCallsAvailable(voiceCallsAvailable).withUpdatedVideoCallsAvailable(videoCallsAvailable).withUpdatedCallsPrivate(callsPrivate).withUpdatedCanPinMessages(canPinMessages).withUpdatedPeerStatusSettings(peerStatusSettings).withUpdatedPinnedMessageId(pinnedMessageId).withUpdatedHasScheduledMessages(hasScheduledMessages) return previous.withUpdatedAbout(userFullAbout).withUpdatedBotInfo(botInfo).withUpdatedCommonGroupCount(userFullCommonChatsCount).withUpdatedIsBlocked(isBlocked).withUpdatedVoiceCallsAvailable(voiceCallsAvailable).withUpdatedVideoCallsAvailable(videoCallsAvailable).withUpdatedCallsPrivate(callsPrivate).withUpdatedCanPinMessages(canPinMessages).withUpdatedPeerStatusSettings(peerStatusSettings).withUpdatedPinnedMessageId(pinnedMessageId).withUpdatedHasScheduledMessages(hasScheduledMessages)
.withUpdatedAutoremoveTimeout(autoremoveTimeout) .withUpdatedAutoremoveTimeout(autoremoveTimeout)
.withUpdatedThemeEmoticon(userFullThemeEmoticon) .withUpdatedThemeEmoticon(userFullThemeEmoticon)
.withUpdatedPhoto(photo)
} }
}) })
} }

View File

@ -2,15 +2,45 @@ import Foundation
import UIKit import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import AvatarNode import AvatarNode
import ContextUI import ContextUI
import TelegramPresentationData
import TelegramUniversalVideoContent
import UniversalMediaPlayer
import GalleryUI
import HierarchyTrackingLayer
import AccountContext
private let normalFont = avatarPlaceholderFont(size: 16.0) private let normalFont = avatarPlaceholderFont(size: 16.0)
private let smallFont = avatarPlaceholderFont(size: 12.0) private let smallFont = avatarPlaceholderFont(size: 12.0)
private let maxVideoLoopCount = 3
final class ChatAvatarNavigationNode: ASDisplayNode { final class ChatAvatarNavigationNode: ASDisplayNode {
private var context: AccountContext?
private let containerNode: ContextControllerSourceNode private let containerNode: ContextControllerSourceNode
let avatarNode: AvatarNode let avatarNode: AvatarNode
private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private let playbackStartDisposable = MetaDisposable()
private var cachedDataDisposable = MetaDisposable()
private var hierarchyTrackingLayer: HierarchyTrackingLayer?
private var videoLoopCount = 0
private var trackingIsInHierarchy: Bool = false {
didSet {
if self.trackingIsInHierarchy != oldValue {
Queue.mainQueue().justDispatch {
self.updateVideoVisibility()
}
}
}
}
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
var contextActionIsEnabled: Bool = true { var contextActionIsEnabled: Bool = true {
@ -41,11 +71,79 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
self.avatarNode.frame = self.containerNode.bounds self.avatarNode.frame = self.containerNode.bounds
} }
deinit {
self.cachedDataDisposable.dispose()
self.playbackStartDisposable.dispose()
}
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
self.view.isOpaque = false self.view.isOpaque = false
} }
public func setPeer(context: AccountContext, theme: PresentationTheme, peer: EnginePeer?, authorOfMessage: MessageReference? = nil, overrideImage: AvatarNodeImageOverride? = nil, emptyColor: UIColor? = nil, clipStyle: AvatarNodeClipStyle = .round, synchronousLoad: Bool = false, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), storeUnrounded: Bool = false) {
self.context = context
self.avatarNode.setPeer(context: context, theme: theme, peer: peer, authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: displayDimensions, storeUnrounded: storeUnrounded)
if let peer = peer, peer.isPremium {
self.cachedDataDisposable.set((context.account.postbox.peerView(id: peer.id)
|> deliverOnMainQueue).start(next: { [weak self] peerView in
guard let strongSelf = self else {
return
}
let cachedPeerData = peerView.cachedData
if let cachedPeerData = cachedPeerData as? CachedUserData {
if let photo = cachedPeerData.photo, let video = photo.videoRepresentations.last, let peerReference = PeerReference(peer._asPeer()) {
let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value()
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false)
if videoContent.id != strongSelf.videoContent?.id {
strongSelf.videoNode?.removeFromSupernode()
strongSelf.videoContent = videoContent
}
if strongSelf.hierarchyTrackingLayer == nil {
let hierarchyTrackingLayer = HierarchyTrackingLayer()
hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.trackingIsInHierarchy = true
}
hierarchyTrackingLayer.didExitHierarchy = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.trackingIsInHierarchy = false
}
strongSelf.hierarchyTrackingLayer = hierarchyTrackingLayer
strongSelf.layer.addSublayer(hierarchyTrackingLayer)
}
} else {
strongSelf.videoContent = nil
strongSelf.hierarchyTrackingLayer?.removeFromSuperlayer()
strongSelf.hierarchyTrackingLayer = nil
}
strongSelf.updateVideoVisibility()
} else {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
}
}))
} else {
self.cachedDataDisposable.set(nil)
self.videoContent = nil
self.videoNode?.removeFromSupernode()
self.videoNode = nil
self.hierarchyTrackingLayer?.removeFromSuperlayer()
self.hierarchyTrackingLayer = nil
}
}
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: 37.0, height: 37.0) return CGSize(width: 37.0, height: 37.0)
} }
@ -82,4 +180,77 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
} }
} }
private func updateVideoVisibility() {
guard let context = self.context else {
return
}
let isVisible = self.trackingIsInHierarchy
if isVisible, let videoContent = self.videoContent, self.videoLoopCount != maxVideoLoopCount {
if self.videoNode == nil {
let mediaManager = context.sharedContext.mediaManager
let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .minimal)
videoNode.clipsToBounds = true
videoNode.isUserInteractionEnabled = false
videoNode.isHidden = true
videoNode.playbackCompleted = { [weak self] in
if let strongSelf = self {
strongSelf.videoLoopCount += 1
if strongSelf.videoLoopCount == maxVideoLoopCount {
if let videoNode = strongSelf.videoNode {
strongSelf.videoNode = nil
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in
videoNode?.removeFromSupernode()
})
}
}
}
}
if let _ = videoContent.startTimestamp {
self.playbackStartDisposable.set((videoNode.status
|> map { status -> Bool in
if let status = status, case .playing = status.status {
return true
} else {
return false
}
}
|> filter { playing in
return playing
}
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.15) {
strongSelf.videoNode?.isHidden = false
}
}
}))
} else {
self.playbackStartDisposable.set(nil)
videoNode.isHidden = false
}
videoNode.layer.cornerRadius = self.avatarNode.frame.size.width / 2.0
if #available(iOS 13.0, *) {
videoNode.layer.cornerCurve = .circular
}
videoNode.canAttachContent = true
videoNode.play()
self.containerNode.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
self.videoNode = videoNode
}
} else if let videoNode = self.videoNode {
self.videoNode = nil
videoNode.removeFromSupernode()
}
if let videoNode = self.videoNode {
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
videoNode.frame = self.avatarNode.frame
}
}
} }

View File

@ -3895,7 +3895,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else { } else {
imageOverride = nil imageOverride = nil
} }
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: EnginePeer(peer), overrideImage: imageOverride) (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: EnginePeer(peer), overrideImage: imageOverride)
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil
strongSelf.chatInfoNavigationButton?.buttonItem.accessibilityLabel = presentationInterfaceState.strings.Conversation_ContextMenuOpenProfile strongSelf.chatInfoNavigationButton?.buttonItem.accessibilityLabel = presentationInterfaceState.strings.Conversation_ContextMenuOpenProfile
} }

View File

@ -461,7 +461,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
loadStickerSaveStatus = file.fileId loadStickerSaveStatus = file.fileId
} }
} }
if loadStickerSaveStatus == nil {
loadCopyMediaResource = file.resource loadCopyMediaResource = file.resource
}
} else if media is TelegramMediaAction || media is TelegramMediaExpiredContent { } else if media is TelegramMediaAction || media is TelegramMediaExpiredContent {
isAction = true isAction = true
} else if let image = media as? TelegramMediaImage { } else if let image = media as? TelegramMediaImage {

View File

@ -1674,43 +1674,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> InternalBubbleTapAction? { private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> InternalBubbleTapAction? {
switch gesture { switch gesture {
case .tap: case .tap:
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
if let item = self.item, let author = item.content.firstMessage.author {
return .optionalAction({
var openPeerId = item.effectiveAuthorId ?? author.id
var navigate: ChatControllerInteractionNavigateToPeer
if item.content.firstMessage.id.peerId == item.context.account.peerId {
navigate = .chat(textInputState: nil, subject: nil, peekData: nil)
} else {
navigate = .info
}
for attribute in item.content.firstMessage.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
openPeerId = attribute.messageId.peerId
navigate = .chat(textInputState: nil, subject: .message(id: .id(attribute.messageId), highlight: true, timecode: nil), peekData: nil)
}
}
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty {
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame)
} else if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.flags.contains(.isImported), forwardInfo.author == nil {
item.controllerInteraction.displayImportedMessageTooltip(avatarNode)
} else {
if !item.message.id.peerId.isReplies, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
if case .member = channel.participationStatus {
} else {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
}
}
item.controllerInteraction.openPeer(openPeerId, navigate, MessageReference(item.message), item.message.peers[openPeerId])
}
})
}
return nil
}
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) { if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
if let item = self.item { if let item = self.item {
for attribute in item.message.attributes { for attribute in item.message.attributes {

View File

@ -1,183 +0,0 @@
import Foundation
import UIKit
import Postbox
import AsyncDisplayKit
import Display
import TelegramCore
import TelegramPresentationData
import AvatarNode
import AccountContext
private let avatarFont = avatarPlaceholderFont(size: 16.0)
final class ChatMessageAvatarAccessoryItem: ListViewAccessoryItem {
private let context: AccountContext
private let peerId: PeerId
private let peer: Peer?
private let messageReference: MessageReference?
private let messageTimestamp: Int32
private let forwardInfo: MessageForwardInfo?
private let emptyColor: UIColor
private let controllerInteraction: ChatControllerInteraction
private let day: Int32
init(context: AccountContext, peerId: PeerId, peer: Peer?, messageReference: MessageReference?, messageTimestamp: Int32, forwardInfo: MessageForwardInfo?, emptyColor: UIColor, controllerInteraction: ChatControllerInteraction) {
self.context = context
self.peerId = peerId
self.peer = peer
self.messageReference = messageReference
self.messageTimestamp = messageTimestamp
self.forwardInfo = forwardInfo
self.emptyColor = emptyColor
self.controllerInteraction = controllerInteraction
var t: time_t = time_t(messageTimestamp)
var timeinfo: tm = tm()
gmtime_r(&t, &timeinfo)
self.day = timeinfo.tm_mday
}
func isEqualToItem(_ other: ListViewAccessoryItem) -> Bool {
if case let other as ChatMessageAvatarAccessoryItem = other {
if other.peerId != self.peerId {
return false
}
if self.day != other.day {
return false
}
var effectiveTimestamp = self.messageTimestamp
if let forwardInfo = self.forwardInfo, forwardInfo.flags.contains(.isImported) {
effectiveTimestamp = forwardInfo.date
}
var effectiveOtherTimestamp = other.messageTimestamp
if let otherForwardInfo = other.forwardInfo, otherForwardInfo.flags.contains(.isImported) {
effectiveOtherTimestamp = otherForwardInfo.date
}
if abs(effectiveTimestamp - effectiveOtherTimestamp) >= 10 * 60 {
return false
}
if let forwardInfo = self.forwardInfo, let otherForwardInfo = other.forwardInfo, forwardInfo.flags.contains(.isImported), otherForwardInfo.flags.contains(.isImported) {
if (forwardInfo.authorSignature != nil) == (otherForwardInfo.authorSignature != nil) && (forwardInfo.author != nil) == (otherForwardInfo.author != nil) {
if let authorSignature = forwardInfo.authorSignature, let otherAuthorSignature = otherForwardInfo.authorSignature {
if authorSignature != otherAuthorSignature {
return false
}
} else if let authorId = forwardInfo.author?.id, let otherAuthorId = otherForwardInfo.author?.id {
if authorId != otherAuthorId {
return false
}
}
} else {
return false
}
} else if let forwardInfo = self.forwardInfo, forwardInfo.flags.contains(.isImported) {
return false
} else if let otherForwardInfo = other.forwardInfo, otherForwardInfo.flags.contains(.isImported) {
return false
}
return true
} else {
return false
}
}
func node(synchronous: Bool) -> ListViewAccessoryItemNode {
let node = ChatMessageAvatarAccessoryItemNode()
node.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
if let forwardInfo = self.forwardInfo, forwardInfo.flags.contains(.isImported) {
if let author = forwardInfo.author {
node.setPeer(context: self.context, theme: self.context.sharedContext.currentPresentationData.with({ $0 }).theme, synchronousLoad: synchronous, peer: author, authorOfMessage: self.messageReference, emptyColor: self.emptyColor, controllerInteraction: self.controllerInteraction)
} else if let authorSignature = forwardInfo.authorSignature, !authorSignature.isEmpty {
let components = authorSignature.components(separatedBy: " ")
if !components.isEmpty, !components[0].hasPrefix("+") {
var letters: [String] = []
letters.append(String(components[0][components[0].startIndex]))
if components.count > 1 {
letters.append(String(components[1][components[1].startIndex]))
}
node.setCustomLetters(context: self.context, theme: self.context.sharedContext.currentPresentationData.with({ $0 }).theme, synchronousLoad: synchronous, letters: letters, emptyColor: self.emptyColor, controllerInteraction: self.controllerInteraction)
} else {
node.setCustomLetters(context: self.context, theme: self.context.sharedContext.currentPresentationData.with({ $0 }).theme, synchronousLoad: synchronous, letters: [], emptyColor: self.emptyColor, controllerInteraction: self.controllerInteraction)
}
} else {
node.setCustomLetters(context: self.context, theme: self.context.sharedContext.currentPresentationData.with({ $0 }).theme, synchronousLoad: synchronous, letters: [], emptyColor: self.emptyColor, controllerInteraction: self.controllerInteraction)
}
} else if let peer = self.peer {
node.setPeer(context: self.context, theme: self.context.sharedContext.currentPresentationData.with({ $0 }).theme, synchronousLoad: synchronous, peer: peer, authorOfMessage: self.messageReference, emptyColor: self.emptyColor, controllerInteraction: self.controllerInteraction)
}
return node
}
}
final class ChatMessageAvatarAccessoryItemNode: ListViewAccessoryItemNode {
var controllerInteraction: ChatControllerInteraction?
var peer: Peer?
var messageId: MessageId?
let containerNode: ContextControllerSourceNode
let avatarNode: AvatarNode
var contextActionIsEnabled: Bool = true {
didSet {
if self.contextActionIsEnabled != oldValue {
self.containerNode.isGestureEnabled = self.contextActionIsEnabled
}
}
}
override init() {
self.containerNode = ContextControllerSourceNode()
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
self.avatarNode.frame = self.containerNode.bounds
self.avatarNode.isUserInteractionEnabled = false
super.init()
self.isLayerBacked = false
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.avatarNode)
self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction, let peer = strongSelf.peer else {
return
}
controllerInteraction.openPeerContextMenu(peer, strongSelf.messageId, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture)
}
}
func setCustomLetters(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, letters: [String], emptyColor: UIColor, controllerInteraction: ChatControllerInteraction) {
self.controllerInteraction = controllerInteraction
self.peer = nil
self.contextActionIsEnabled = false
self.avatarNode.setCustomLetters(letters, icon: !letters.isEmpty ? nil : .phone)
}
func setPeer(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, peer: Peer, authorOfMessage: MessageReference?, emptyColor: UIColor, controllerInteraction: ChatControllerInteraction) {
self.controllerInteraction = controllerInteraction
self.peer = peer
if let messageReference = authorOfMessage, case let .message(_, id, _, _, _) = messageReference.content {
self.messageId = id
}
self.contextActionIsEnabled = peer.smallProfileImage != nil
var overrideImage: AvatarNodeImageOverride?
if peer.isDeleted {
overrideImage = .deletedIcon
}
self.avatarNode.setPeer(context: context, theme: theme, peer: EnginePeer(peer), authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: 38.0, height: 38.0))
}
}

View File

@ -833,10 +833,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
} }
if let avatarNode = strongSelf.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(point) {
return .waitForSingleTap
}
if let nameNode = strongSelf.nameNode, nameNode.frame.contains(point) { if let nameNode = strongSelf.nameNode, nameNode.frame.contains(point) {
if let item = strongSelf.item { if let item = strongSelf.item {
for attribute in item.message.attributes { for attribute in item.message.attributes {
@ -3115,46 +3111,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
switch gesture { switch gesture {
case .tap: case .tap:
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
return .action({
if let item = self.item, let author = item.content.firstMessage.author {
var openPeerId = item.effectiveAuthorId ?? author.id
var navigate: ChatControllerInteractionNavigateToPeer
if item.content.firstMessage.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) {
navigate = .chat(textInputState: nil, subject: nil, peekData: nil)
} else if openPeerId.namespace == Namespaces.Peer.CloudUser {
navigate = .info
} else {
navigate = .chat(textInputState: nil, subject: nil, peekData: nil)
}
for attribute in item.content.firstMessage.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
openPeerId = attribute.messageId.peerId
navigate = .chat(textInputState: nil, subject: .message(id: .id(attribute.messageId), highlight: true, timecode: nil), peekData: nil)
}
}
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty {
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame)
} else if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.flags.contains(.isImported), forwardInfo.author == nil {
item.controllerInteraction.displayImportedMessageTooltip(avatarNode)
} else {
if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId), let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) {
} else if case .member = channel.participationStatus {
} else if !item.message.id.peerId.isReplies {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
return
}
}
item.controllerInteraction.openPeer(openPeerId, navigate, MessageReference(item.message), item.message.peers[openPeerId])
}
}
})
}
if let nameNode = self.nameNode, nameNode.frame.contains(location) { if let nameNode = self.nameNode, nameNode.frame.contains(location) {
if let item = self.item { if let item = self.item {
for attribute in item.message.attributes { for attribute in item.message.attributes {
@ -3484,10 +3440,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
} }
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(point) {
return avatarNode.containerNode.view
}
if !self.backgroundNode.frame.contains(point) { if !self.backgroundNode.frame.contains(point) {
if let actionButtonsNode = self.actionButtonsNode, let result = actionButtonsNode.hitTest(self.view.convert(point, to: actionButtonsNode.view), with: event) { if let actionButtonsNode = self.actionButtonsNode, let result = actionButtonsNode.hitTest(self.view.convert(point, to: actionButtonsNode.view), with: event) {
return result return result

View File

@ -2,11 +2,16 @@ import Foundation
import UIKit import UIKit
import Display import Display
import AsyncDisplayKit import AsyncDisplayKit
import TelegramPresentationData import SwiftSignalKit
import Postbox import Postbox
import TelegramPresentationData
import AccountContext import AccountContext
import AvatarNode import AvatarNode
import TelegramCore import TelegramCore
import TelegramUniversalVideoContent
import UniversalMediaPlayer
import GalleryUI
import HierarchyTrackingLayer
private let timezoneOffset: Int32 = { private let timezoneOffset: Int32 = {
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
@ -376,16 +381,39 @@ final class ChatMessageAvatarHeader: ListViewItemHeader {
private let avatarFont = avatarPlaceholderFont(size: 16.0) private let avatarFont = avatarPlaceholderFont(size: 16.0)
private let maxVideoLoopCount = 3
final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
private let context: AccountContext
private var presentationData: ChatPresentationData
private let controllerInteraction: ChatControllerInteraction
private let peerId: PeerId private let peerId: PeerId
private let messageReference: MessageReference? private let messageReference: MessageReference?
private let peer: Peer? private let peer: Peer?
private let containerNode: ContextControllerSourceNode private let containerNode: ContextControllerSourceNode
private let avatarNode: AvatarNode private let avatarNode: AvatarNode
private var presentationData: ChatPresentationData private var videoNode: UniversalVideoNode?
private let context: AccountContext
private let controllerInteraction: ChatControllerInteraction private var videoContent: NativeVideoContent?
private let playbackStartDisposable = MetaDisposable()
private var cachedDataDisposable = MetaDisposable()
private var hierarchyTrackingLayer: HierarchyTrackingLayer?
private var videoLoopCount = 0
private var trackingIsInHierarchy: Bool = false {
didSet {
if self.trackingIsInHierarchy != oldValue {
Queue.mainQueue().justDispatch {
if self.trackingIsInHierarchy {
self.videoLoopCount = 0
}
self.updateVideoVisibility()
}
}
}
}
init(peerId: PeerId, peer: Peer?, messageReference: MessageReference?, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction, synchronousLoad: Bool) { init(peerId: PeerId, peer: Peer?, messageReference: MessageReference?, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction, synchronousLoad: Bool) {
self.peerId = peerId self.peerId = peerId
@ -424,6 +452,11 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
self.updateSelectionState(animated: false) self.updateSelectionState(animated: false)
} }
deinit {
self.cachedDataDisposable.dispose()
self.playbackStartDisposable.dispose()
}
func setCustomLetters(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, letters: [String], emptyColor: UIColor) { func setCustomLetters(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, letters: [String], emptyColor: UIColor) {
self.containerNode.isGestureEnabled = false self.containerNode.isGestureEnabled = false
@ -438,6 +471,64 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
overrideImage = .deletedIcon overrideImage = .deletedIcon
} }
self.avatarNode.setPeer(context: context, theme: theme, peer: EnginePeer(peer), authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: 38.0, height: 38.0)) self.avatarNode.setPeer(context: context, theme: theme, peer: EnginePeer(peer), authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: 38.0, height: 38.0))
if peer.isPremium {
self.cachedDataDisposable.set((context.account.postbox.peerView(id: peer.id)
|> deliverOnMainQueue).start(next: { [weak self] peerView in
guard let strongSelf = self else {
return
}
let cachedPeerData = peerView.cachedData
if let cachedPeerData = cachedPeerData as? CachedUserData {
if let photo = cachedPeerData.photo, let video = photo.videoRepresentations.last, let peerReference = PeerReference(peer) {
let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value()
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false)
if videoContent.id != strongSelf.videoContent?.id {
strongSelf.videoNode?.removeFromSupernode()
strongSelf.videoContent = videoContent
}
if strongSelf.hierarchyTrackingLayer == nil {
let hierarchyTrackingLayer = HierarchyTrackingLayer()
hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.trackingIsInHierarchy = true
}
hierarchyTrackingLayer.didExitHierarchy = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.trackingIsInHierarchy = false
}
strongSelf.hierarchyTrackingLayer = hierarchyTrackingLayer
strongSelf.layer.addSublayer(hierarchyTrackingLayer)
}
} else {
strongSelf.videoContent = nil
strongSelf.hierarchyTrackingLayer?.removeFromSuperlayer()
strongSelf.hierarchyTrackingLayer = nil
}
strongSelf.updateVideoVisibility()
} else {
let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peer.id).start()
}
}))
} else {
self.cachedDataDisposable.set(nil)
self.videoContent = nil
self.videoNode?.removeFromSupernode()
self.videoNode = nil
self.hierarchyTrackingLayer?.removeFromSuperlayer()
self.hierarchyTrackingLayer = nil
}
} }
override func didLoad() { override func didLoad() {
@ -509,4 +600,74 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
} }
} }
} }
private func updateVideoVisibility() {
let context = self.context
let isVisible = self.trackingIsInHierarchy
if isVisible, let videoContent = self.videoContent, self.videoLoopCount != maxVideoLoopCount {
if self.videoNode == nil {
let mediaManager = context.sharedContext.mediaManager
let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded)
videoNode.clipsToBounds = true
videoNode.isUserInteractionEnabled = false
videoNode.isHidden = true
videoNode.playbackCompleted = { [weak self] in
if let strongSelf = self {
strongSelf.videoLoopCount += 1
if strongSelf.videoLoopCount == maxVideoLoopCount {
if let videoNode = strongSelf.videoNode {
strongSelf.videoNode = nil
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in
videoNode?.removeFromSupernode()
})
}
}
}
}
if let _ = videoContent.startTimestamp {
self.playbackStartDisposable.set((videoNode.status
|> map { status -> Bool in
if let status = status, case .playing = status.status {
return true
} else {
return false
}
}
|> filter { playing in
return playing
}
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.15) {
strongSelf.videoNode?.isHidden = false
}
}
}))
} else {
self.playbackStartDisposable.set(nil)
videoNode.isHidden = false
}
videoNode.layer.cornerRadius = self.avatarNode.frame.size.width / 2.0
if #available(iOS 13.0, *) {
videoNode.layer.cornerCurve = .circular
}
videoNode.canAttachContent = true
videoNode.play()
self.containerNode.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
self.videoNode = videoNode
}
} else if let videoNode = self.videoNode {
self.videoNode = nil
videoNode.removeFromSupernode()
}
if let videoNode = self.videoNode {
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
videoNode.frame = self.avatarNode.frame
}
}
} }

View File

@ -881,41 +881,6 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> InternalBubbleTapAction? { private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> InternalBubbleTapAction? {
switch gesture { switch gesture {
case .tap: case .tap:
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
if let item = self.item, let author = item.content.firstMessage.author {
var openPeerId = item.effectiveAuthorId ?? author.id
var navigate: ChatControllerInteractionNavigateToPeer
if item.content.firstMessage.id.peerId == item.context.account.peerId {
navigate = .chat(textInputState: nil, subject: nil, peekData: nil)
} else {
navigate = .info
}
for attribute in item.content.firstMessage.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
openPeerId = attribute.messageId.peerId
navigate = .chat(textInputState: nil, subject: .message(id: .id(attribute.messageId), highlight: true, timecode: nil), peekData: nil)
}
}
return .optionalAction({
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty {
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame)
} else {
if !item.message.id.peerId.isReplies, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
if case .member = channel.participationStatus {
} else {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
return
}
}
item.controllerInteraction.openPeer(openPeerId, navigate, MessageReference(item.message), item.message.peers[openPeerId])
}
})
}
}
if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) { if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) {
if let item = self.item { if let item = self.item {
for attribute in item.message.attributes { for attribute in item.message.attributes {

View File

@ -358,7 +358,6 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
} }
if !hasActionMedia && !isBroadcastChannel { if !hasActionMedia && !isBroadcastChannel {
if let effectiveAuthor = effectiveAuthor { if let effectiveAuthor = effectiveAuthor {
//accessoryItem = ChatMessageAvatarAccessoryItem(context: context, peerId: effectiveAuthor.id, peer: effectiveAuthor, messageReference: MessageReference(message), messageTimestamp: content.index.timestamp, forwardInfo: message.forwardInfo, emptyColor: presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill, controllerInteraction: controllerInteraction)
avatarHeader = ChatMessageAvatarHeader(timestamp: content.index.timestamp, peerId: effectiveAuthor.id, peer: effectiveAuthor, messageReference: MessageReference(message), message: message, presentationData: presentationData, context: context, controllerInteraction: controllerInteraction) avatarHeader = ChatMessageAvatarHeader(timestamp: content.index.timestamp, peerId: effectiveAuthor.id, peer: effectiveAuthor, messageReference: MessageReference(message), message: message, presentationData: presentationData, context: context, controllerInteraction: controllerInteraction)
} }
} }

View File

@ -723,12 +723,6 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol
} }
} }
override public func layoutAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode, leftInset: CGFloat, rightInset: CGFloat) {
if let avatarNode = accessoryItemNode as? ChatMessageAvatarAccessoryItemNode {
avatarNode.frame = CGRect(origin: CGPoint(x: leftInset + 3.0, y: self.apparentFrame.height - 38.0 - self.insets.top - 2.0 - UIScreenPixel), size: CGSize(width: 38.0, height: 38.0))
}
}
func cancelInsertionAnimations() { func cancelInsertionAnimations() {
} }

View File

@ -1091,42 +1091,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> InternalBubbleTapAction? { private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> InternalBubbleTapAction? {
switch gesture { switch gesture {
case .tap: case .tap:
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
if let item = self.item, let author = item.content.firstMessage.author {
return .optionalAction({
var openPeerId = item.effectiveAuthorId ?? author.id
var navigate: ChatControllerInteractionNavigateToPeer
if item.content.firstMessage.id.peerId == item.context.account.peerId {
navigate = .chat(textInputState: nil, subject: nil, peekData: nil)
} else {
navigate = .info
}
for attribute in item.content.firstMessage.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
openPeerId = attribute.messageId.peerId
navigate = .chat(textInputState: nil, subject: .message(id: .id(attribute.messageId), highlight: true, timecode: nil), peekData: nil)
}
}
if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty {
item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame)
} else if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.flags.contains(.isImported), forwardInfo.author == nil {
item.controllerInteraction.displayImportedMessageTooltip(avatarNode)
} else {
if !item.message.id.peerId.isReplies, let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil {
if case .member = channel.participationStatus {
} else {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
}
}
item.controllerInteraction.openPeer(openPeerId, navigate, MessageReference(item.message), item.message.peers[openPeerId])
}
})
}
}
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) { if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
if let item = self.item { if let item = self.item {
for attribute in item.message.attributes { for attribute in item.message.attributes {

View File

@ -413,29 +413,29 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
let representations: [ImageRepresentationWithReference] let representations: [ImageRepresentationWithReference]
let videoRepresentations: [VideoRepresentationWithReference] let videoRepresentations: [VideoRepresentationWithReference]
let immediateThumbnailData: Data? let immediateThumbnailData: Data?
var id: Int64 var videoId: Int64
switch item { switch item {
case .custom: case .custom:
representations = [] representations = []
videoRepresentations = [] videoRepresentations = []
immediateThumbnailData = nil immediateThumbnailData = nil
id = 0 videoId = 0
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail): case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
representations = topRepresentations representations = topRepresentations
videoRepresentations = videoRepresentationsValue videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail immediateThumbnailData = immediateThumbnail
id = peer.id.id._internalGetInt64Value() videoId = peer.id.id._internalGetInt64Value()
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource { if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
id = id &+ resource.photoId videoId = videoId &+ resource.photoId
} }
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail): case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail):
representations = imageRepresentations representations = imageRepresentations
videoRepresentations = videoRepresentationsValue videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail immediateThumbnailData = immediateThumbnail
if case let .cloud(imageId, _, _) = reference { if case let .cloud(imageId, _, _) = reference {
id = imageId videoId = imageId
} else { } else {
id = peer.id.id._internalGetInt64Value() videoId = peer.id.id._internalGetInt64Value()
} }
} }
@ -443,7 +443,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
if let video = videoRepresentations.last, let peerReference = PeerReference(peer) { if let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled) let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled)
if videoContent.id != self.videoContent?.id { if videoContent.id != self.videoContent?.id {
self.videoNode?.removeFromSupernode() self.videoNode?.removeFromSupernode()

View File

@ -33,7 +33,7 @@ public final class NativeVideoContent: UniversalVideoContent {
let onlyFullSizeThumbnail: Bool let onlyFullSizeThumbnail: Bool
let useLargeThumbnail: Bool let useLargeThumbnail: Bool
let autoFetchFullSizeThumbnail: Bool let autoFetchFullSizeThumbnail: Bool
let startTimestamp: Double? public let startTimestamp: Double?
let endTimestamp: Double? let endTimestamp: Double?
let continuePlayingWithoutSoundOnLostAudioSession: Bool let continuePlayingWithoutSoundOnLostAudioSession: Bool
let placeholderColor: UIColor let placeholderColor: UIColor