Merge commit 'b997987108fb3b9ff5dfcfcd064074db2ef0ecff'

This commit is contained in:
Ali 2022-05-06 21:14:31 +04:00
commit 6fabcaf7df
23 changed files with 727 additions and 469 deletions

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import ItemListUI
@ -19,6 +20,10 @@ import ContextUI
import ChatInterfaceState
import TextFormat
import InvisibleInkDustNode
import TelegramUniversalVideoContent
import UniversalMediaPlayer
import GalleryUI
import HierarchyTrackingLayer
public enum ChatListItemContent {
public final class DraftState: Equatable {
@ -415,6 +420,8 @@ private final class ChatListMediaPreviewNode: ASDisplayNode {
}
}
private let maxVideoLoopCount = 3
class ChatListItemNode: ItemListRevealOptionsItemNode {
var item: ChatListItem?
@ -424,6 +431,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let contextContainer: ContextControllerSourceNode
let avatarNode: AvatarNode
var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private let playbackStartDisposable = MetaDisposable()
private var videoLoopCount = 0
let titleNode: TextNode
let authorNode: TextNode
let measureNode: TextNode
@ -442,6 +454,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var credibilityIconNode: ASImageNode?
let mutedIconNode: ASImageNode
private var hierarchyTrackingLayer: HierarchyTrackingLayer?
private var cachedDataDisposable = MetaDisposable()
private var currentTextLeftCutout: CGFloat = 0.0
private var currentMediaPreviewSpecs: [(message: EngineMessage, media: EngineMedia, size: CGSize)] = []
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() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
@ -675,6 +730,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
}
deinit {
self.cachedDataDisposable.dispose()
self.playbackStartDisposable.dispose()
}
func setupItem(item: ChatListItem, synchronousLoads: Bool) {
let previousItem = self.item
self.item = item
@ -711,6 +771,65 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
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))
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
@ -1364,7 +1483,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if isSecret {
currentSecretIconImage = PresentationResourcesChatList.secretIcon(item.presentationData.theme)
}
var credibilityIconOffset: CGFloat = 0.0
if !isPeerGroup && item.index.messageIndex.id.peerId != item.context.account.peerId {
if displayAsMessage {
@ -1373,16 +1491,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let peer = messages.last?.author {
if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
} else if peer.isPremium {
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
credibilityIconOffset = 2.0
}
}
default:
@ -1391,16 +1505,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isFake {
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
} else if peer.isPremium {
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))
transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame)
strongSelf.updateVideoVisibility()
let onlineFrame: CGRect
if onlineIsVoiceChat {
@ -1743,41 +1854,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.secretIconNode = nil
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))
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)
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)
@ -1917,6 +1997,37 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
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
if case let .groupReference(_, _, _, _, hiddenByDefault) = item.content, hiddenByDefault {
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) {
super.updateRevealOffset(offset: offset, transition: transition)
@ -2032,6 +2217,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var avatarFrame = self.avatarNode.frame
avatarFrame.origin.x = leftInset - avatarLeftInset + editingOffset + 10.0 + offset
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
if let videoNode = self.videoNode {
transition.updateFrame(node: videoNode, frame: avatarFrame)
}
var onlineFrame = self.onlineNode.frame
if self.onlineIsVoiceChat {

View File

@ -333,6 +333,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
private let avatarNode: AvatarNode
private let titleNode: TextNode
private var credibilityIconNode: ASImageNode?
private var verificationIconNode: ASImageNode?
private let statusNode: TextNode
private var badgeBackgroundNode: ASImageNode?
@ -562,11 +563,19 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
}
}
var verificationIconImage: UIImage?
var currentCredibilityIconImage: UIImage?
switch item.peer {
case let .peer(peer, _):
if let peer = peer, peer.isVerified {
verificationIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
if let peer = peer {
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:
break
@ -726,8 +735,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
}
var additionalTitleInset: CGFloat = 0.0
if let verificationIconImage = verificationIconImage {
additionalTitleInset += 3.0 + verificationIconImage.size.width
if let currentCredibilityIconImage = currentCredibilityIconImage {
additionalTitleInset += 3.0 + currentCredibilityIconImage.size.width
}
if let actionButtons = actionButtons {
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)))
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.statusNode.alpha = item.enabled ? 1.0 : 1.0
@ -912,23 +922,23 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
strongSelf.statusNode.frame = statusFrame
transition.animatePositionAdditive(node: strongSelf.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
if let verificationIconImage = verificationIconImage {
if strongSelf.verificationIconNode == nil {
let verificationIconNode = ASImageNode()
verificationIconNode.isLayerBacked = true
verificationIconNode.displayWithoutProcessing = true
verificationIconNode.displaysAsynchronously = false
strongSelf.verificationIconNode = verificationIconNode
strongSelf.offsetContainerNode.addSubnode(verificationIconNode)
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
}
if let verificationIconNode = strongSelf.verificationIconNode {
verificationIconNode.image = verificationIconImage
transition.updateFrame(node: verificationIconNode, frame: CGRect(origin: CGPoint(x: revealOffset + titleFrame.maxX + 3.0, y: titleFrame.minY + 3.0 + UIScreenPixel), size: verificationIconImage.size))
}
} else if let verificationIconNode = strongSelf.verificationIconNode {
strongSelf.verificationIconNode = nil
verificationIconNode.removeFromSupernode()
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 actionButtons = actionButtons {
@ -1128,10 +1138,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
self.statusNode.frame = statusFrame
transition.animatePositionAdditive(node: self.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
if let verificationIconNode = self.verificationIconNode {
var iconFrame = verificationIconNode.frame
iconFrame.origin.x = titleFrame.maxX + 3.0
transition.updateFrame(node: verificationIconNode, frame: iconFrame)
if let credibilityIconNode = self.credibilityIconNode {
var iconFrame = credibilityIconNode.frame
iconFrame.origin.x = titleFrame.maxX + 4.0
transition.updateFrame(node: credibilityIconNode, frame: iconFrame)
}
if let badgeBackgroundNode = self.badgeBackgroundNode, let badgeTextNode = self.badgeTextNode {

View File

@ -23,6 +23,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
case iPadPro11Inch
case iPadPro
case iPadPro3rdGen
case iPadMini6thGen
case unknown(screenSize: CGSize, statusBarHeight: CGFloat, onScreenNavigationHeight: CGFloat?)
public static var allCases: [DeviceMetrics] {
@ -43,7 +44,8 @@ public enum DeviceMetrics: CaseIterable, Equatable {
.iPadPro10Inch,
.iPadPro11Inch,
.iPadPro,
.iPadPro3rdGen
.iPadPro3rdGen,
.iPadMini6thGen
]
}
@ -84,7 +86,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
switch self {
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen:
return .tablet
case let .unknown(screenSize, _, _) where screenSize.width >= 768.0 && screenSize.height >= 1024.0:
case let .unknown(screenSize, _, _) where screenSize.width >= 744.0 && screenSize.height >= 1024.0:
return .tablet
default:
return .phone
@ -123,6 +125,8 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return CGSize(width: 834.0, height: 1194.0)
case .iPadPro, .iPadPro3rdGen:
return CGSize(width: 1024.0, height: 1366.0)
case .iPadMini6thGen:
return CGSize(width: 744.0, height: 1133.0)
case let .unknown(screenSize, _, _):
return screenSize
}
@ -166,7 +170,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return inLandscape ? 21.0 : 34.0
case .iPadPro3rdGen, .iPadPro11Inch:
return 21.0
case .iPad, .iPadPro, .iPadPro10Inch, .iPadMini:
case .iPad, .iPadPro, .iPadPro10Inch, .iPadMini, .iPadMini6thGen:
if let systemOnScreenNavigationHeight = systemOnScreenNavigationHeight, !systemOnScreenNavigationHeight.isZero {
return 21.0
} else {
@ -196,7 +200,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
switch self {
case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax:
return 44.0
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini:
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
return 24.0
case let .unknown(_, statusBarHeight, _):
return statusBarHeight
@ -216,7 +220,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return 172.0
case .iPad, .iPad102Inch, .iPadPro10Inch:
return 348.0
case .iPadPro11Inch, .iPadMini:
case .iPadPro11Inch, .iPadMini, .iPadMini6thGen:
return 368.0
case .iPadPro:
return 421.0
@ -239,7 +243,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return 263.0
case .iPadPro11Inch:
return 283.0
case .iPadPro, .iPadMini:
case .iPadPro, .iPadMini, .iPadMini6thGen:
return 328.0
case .iPadPro3rdGen:
return 348.0
@ -254,7 +258,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
switch self {
case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax:
return 37.0
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini:
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
return 50.0
case .unknown:
return 37.0
@ -267,7 +271,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
return 44.0
case .iPhone6Plus:
return 45.0
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini:
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
return 50.0
case .unknown:
return 44.0

View File

@ -3676,7 +3676,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
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 {
let itemFrame = itemNode.apparentFrame
var visibility: ListViewItemNodeVisibility = .none

View File

@ -460,6 +460,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
private let labelBadgeNode: ASImageNode
private var labelArrowNode: ASImageNode?
private let statusNode: TextNode
private var credibilityIconNode: ASImageNode?
private var switchNode: SwitchNode?
private var checkNode: ASImageNode?
@ -600,6 +601,22 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
let labelDisclosureFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
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?
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 (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()))
var insets = itemListNeighborsGroupedInsets(neighbors, params)
@ -921,7 +938,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
} else {
transition = .immediate
}
if let currentDisabledOverlayNode = currentDisabledOverlayNode {
if currentDisabledOverlayNode != strongSelf.disabledOverlayNode {
strongSelf.disabledOverlayNode = currentDisabledOverlayNode
@ -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.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))
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 currentSwitchNode !== strongSelf.switchNode {
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.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
if let labelArrowNode = self.labelArrowNode {

View File

@ -89,7 +89,7 @@ struct PasscodeKeyboardLayout {
self.topOffset = 329.0
self.biometricsOffset = 30.0
self.deleteOffset = 20.0
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini:
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
self.buttonSize = 81.0
self.horizontalSecond = 106.0
self.horizontalThird = 212.0
@ -159,7 +159,7 @@ public struct PasscodeLayout {
self.titleOffset = 180.0
self.subtitleOffset = 0.0
self.inputFieldOffset = 226.0
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini:
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
self.titleOffset = self.keyboard.topOffset - 120.0
self.subtitleOffset = -2.0
self.inputFieldOffset = self.keyboard.topOffset - 76.0

View File

@ -63,6 +63,7 @@ public final class CachedUserData: CachedPeerData {
public let hasScheduledMessages: Bool
public let autoremoveTimeout: CachedPeerAutoremoveTimeout
public let themeEmoticon: String?
public let photo: TelegramMediaImage?
public let peerIds: Set<PeerId>
public let messageIds: Set<MessageId>
@ -82,11 +83,12 @@ public final class CachedUserData: CachedPeerData {
self.hasScheduledMessages = false
self.autoremoveTimeout = .unknown
self.themeEmoticon = nil
self.photo = nil
self.peerIds = 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.botInfo = botInfo
self.peerStatusSettings = peerStatusSettings
@ -100,6 +102,7 @@ public final class CachedUserData: CachedPeerData {
self.hasScheduledMessages = hasScheduledMessages
self.autoremoveTimeout = autoremoveTimeout
self.themeEmoticon = themeEmoticon
self.photo = photo
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.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>()
var messageIds = Set<MessageId>()
@ -182,6 +191,12 @@ public final class CachedUserData: CachedPeerData {
} else {
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 {
@ -196,58 +211,62 @@ public final class CachedUserData: CachedPeerData {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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()
}
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 isBlocked = (userFullFlags & (1 << 0)) != 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 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)
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
.withUpdatedThemeEmoticon(userFullThemeEmoticon)
.withUpdatedPhoto(photo)
}
})
}

View File

@ -2,15 +2,45 @@ import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import AvatarNode
import ContextUI
import TelegramPresentationData
import TelegramUniversalVideoContent
import UniversalMediaPlayer
import GalleryUI
import HierarchyTrackingLayer
import AccountContext
private let normalFont = avatarPlaceholderFont(size: 16.0)
private let smallFont = avatarPlaceholderFont(size: 12.0)
private let maxVideoLoopCount = 3
final class ChatAvatarNavigationNode: ASDisplayNode {
private var context: AccountContext?
private let containerNode: ContextControllerSourceNode
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 contextActionIsEnabled: Bool = true {
@ -41,11 +71,79 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
self.avatarNode.frame = self.containerNode.bounds
}
deinit {
self.cachedDataDisposable.dispose()
self.playbackStartDisposable.dispose()
}
override func didLoad() {
super.didLoad()
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 {
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)
}
}
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 {
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.accessibilityLabel = presentationInterfaceState.strings.Conversation_ContextMenuOpenProfile
}

View File

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

View File

@ -1673,44 +1673,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> InternalBubbleTapAction? {
switch gesture {
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
}
case .tap:
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
if let item = self.item {
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

@ -832,11 +832,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
return .fail
}
}
if let avatarNode = strongSelf.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(point) {
return .waitForSingleTap
}
if let nameNode = strongSelf.nameNode, nameNode.frame.contains(point) {
if let item = strongSelf.item {
for attribute in item.message.attributes {
@ -3115,46 +3111,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
switch gesture {
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 item = self.item {
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 let actionButtonsNode = self.actionButtonsNode, let result = actionButtonsNode.hitTest(self.view.convert(point, to: actionButtonsNode.view), with: event) {
return result

View File

@ -2,11 +2,16 @@ import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramPresentationData
import SwiftSignalKit
import Postbox
import TelegramPresentationData
import AccountContext
import AvatarNode
import TelegramCore
import TelegramUniversalVideoContent
import UniversalMediaPlayer
import GalleryUI
import HierarchyTrackingLayer
private let timezoneOffset: Int32 = {
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
@ -345,7 +350,7 @@ final class ChatMessageAvatarHeader: ListViewItemHeader {
self.controllerInteraction = controllerInteraction
self.id = ListViewItemNode.HeaderId(space: 1, id: Id(peerId: peerId, timestampId: dateHeaderTimestampId(timestamp: timestamp)))
}
let stickDirection: ListViewItemHeaderStickDirection = .top
let stickOverInsets: Bool = false
@ -376,17 +381,40 @@ final class ChatMessageAvatarHeader: ListViewItemHeader {
private let avatarFont = avatarPlaceholderFont(size: 16.0)
private let maxVideoLoopCount = 3
final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
private let context: AccountContext
private var presentationData: ChatPresentationData
private let controllerInteraction: ChatControllerInteraction
private let peerId: PeerId
private let messageReference: MessageReference?
private let peer: Peer?
private let containerNode: ContextControllerSourceNode
private let avatarNode: AvatarNode
private var presentationData: ChatPresentationData
private let context: AccountContext
private let controllerInteraction: ChatControllerInteraction
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 {
if self.trackingIsInHierarchy {
self.videoLoopCount = 0
}
self.updateVideoVisibility()
}
}
}
}
init(peerId: PeerId, peer: Peer?, messageReference: MessageReference?, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction, synchronousLoad: Bool) {
self.peerId = peerId
self.peer = peer
@ -423,6 +451,11 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
self.updateSelectionState(animated: false)
}
deinit {
self.cachedDataDisposable.dispose()
self.playbackStartDisposable.dispose()
}
func setCustomLetters(context: AccountContext, theme: PresentationTheme, synchronousLoad: Bool, letters: [String], emptyColor: UIColor) {
self.containerNode.isGestureEnabled = false
@ -438,6 +471,64 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
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))
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() {
@ -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

@ -880,42 +880,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> InternalBubbleTapAction? {
switch gesture {
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])
}
})
}
}
case .tap:
if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) {
if let item = self.item {
for attribute in item.message.attributes {

View File

@ -358,7 +358,6 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
}
if !hasActionMedia && !isBroadcastChannel {
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)
}
}

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() {
}

View File

@ -1090,43 +1090,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private func gestureRecognized(gesture: TapLongTapOrDoubleTapGesture, location: CGPoint, recognizer: TapLongTapOrDoubleTapGestureRecognizer?) -> InternalBubbleTapAction? {
switch gesture {
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])
}
})
}
}
case .tap:
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
if let item = self.item {
for attribute in item.message.attributes {

View File

@ -413,29 +413,29 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
let representations: [ImageRepresentationWithReference]
let videoRepresentations: [VideoRepresentationWithReference]
let immediateThumbnailData: Data?
var id: Int64
var videoId: Int64
switch item {
case .custom:
representations = []
videoRepresentations = []
immediateThumbnailData = nil
id = 0
videoId = 0
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
representations = topRepresentations
videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail
id = peer.id.id._internalGetInt64Value()
videoId = peer.id.id._internalGetInt64Value()
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
id = id &+ resource.photoId
videoId = videoId &+ resource.photoId
}
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail):
representations = imageRepresentations
videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail
if case let .cloud(imageId, _, _) = reference {
id = imageId
videoId = imageId
} 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) {
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 {
self.videoNode?.removeFromSupernode()

View File

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