mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit 'b997987108fb3b9ff5dfcfcd064074db2ef0ecff'
This commit is contained in:
commit
6fabcaf7df
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user