mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Avatar playback improvements
This commit is contained in:
parent
cc0595de4b
commit
394c0d7a26
@ -25,6 +25,9 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
|
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
|
||||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||||
|
"//submodules/AnimatedStickerNode",
|
||||||
|
"//submodules/TelegramAnimatedStickerNode",
|
||||||
|
"//submodules/StickerResources:StickerResources",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -13,6 +13,9 @@ import GradientBackground
|
|||||||
import AnimationCache
|
import AnimationCache
|
||||||
import MultiAnimationRenderer
|
import MultiAnimationRenderer
|
||||||
import EntityKeyboard
|
import EntityKeyboard
|
||||||
|
import AnimatedStickerNode
|
||||||
|
import TelegramAnimatedStickerNode
|
||||||
|
import StickerResources
|
||||||
|
|
||||||
private let maxVideoLoopCount = 3
|
private let maxVideoLoopCount = 3
|
||||||
|
|
||||||
@ -26,6 +29,8 @@ public final class AvatarVideoNode: ASDisplayNode {
|
|||||||
private var fileDisposable: Disposable?
|
private var fileDisposable: Disposable?
|
||||||
private var animationFile: TelegramMediaFile?
|
private var animationFile: TelegramMediaFile?
|
||||||
private var itemLayer: EmojiPagerContentComponent.View.ItemLayer?
|
private var itemLayer: EmojiPagerContentComponent.View.ItemLayer?
|
||||||
|
private var useAnimationNode = false
|
||||||
|
private var animationNode: AnimatedStickerNode?
|
||||||
|
|
||||||
private var videoNode: UniversalVideoNode?
|
private var videoNode: UniversalVideoNode?
|
||||||
private var videoContent: NativeVideoContent?
|
private var videoContent: NativeVideoContent?
|
||||||
@ -69,56 +74,74 @@ public final class AvatarVideoNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let itemNativeFitSize = self.internalSize.width > 100.0 ? CGSize(width: 256.0, height: 256.0) : CGSize(width: 128.0, height: 128.0)
|
if self.useAnimationNode {
|
||||||
|
let animationNode = DefaultAnimatedStickerNodeImpl()
|
||||||
let animationData = EntityKeyboardAnimationData(file: animationFile)
|
animationNode.autoplay = false
|
||||||
let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
|
self.animationNode = animationNode
|
||||||
item: EmojiPagerContentComponent.Item(
|
animationNode.started = { [weak self] in
|
||||||
animationData: animationData,
|
if let self {
|
||||||
content: .animation(animationData),
|
if !self.didAppear {
|
||||||
itemFile: animationFile,
|
self.didAppear = true
|
||||||
subgroupId: nil,
|
Queue.mainQueue().after(0.15) {
|
||||||
icon: .none,
|
self.backgroundNode.isHidden = false
|
||||||
tintMode: animationData.isTemplate ? .primary : .none
|
}
|
||||||
),
|
|
||||||
context: context,
|
|
||||||
attemptSynchronousLoad: false,
|
|
||||||
content: .animation(animationData),
|
|
||||||
cache: context.animationCache,
|
|
||||||
renderer: context.animationRenderer,
|
|
||||||
placeholderColor: .clear,
|
|
||||||
blurredBadgeColor: .clear,
|
|
||||||
accentIconColor: .white,
|
|
||||||
pointSize: itemNativeFitSize,
|
|
||||||
onUpdateDisplayPlaceholder: { _, _ in
|
|
||||||
}
|
|
||||||
)
|
|
||||||
itemLayer.onContentsUpdate = { [weak self] in
|
|
||||||
if let self {
|
|
||||||
if !self.didAppear {
|
|
||||||
self.didAppear = true
|
|
||||||
Queue.mainQueue().after(0.15) {
|
|
||||||
self.backgroundNode.isHidden = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.backgroundNode.addSubnode(animationNode)
|
||||||
|
} else {
|
||||||
|
let itemNativeFitSize = self.internalSize.width > 100.0 ? CGSize(width: 192.0, height: 192.0) : CGSize(width: 64.0, height: 64.0)
|
||||||
|
|
||||||
|
let animationData = EntityKeyboardAnimationData(file: animationFile)
|
||||||
|
let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
|
||||||
|
item: EmojiPagerContentComponent.Item(
|
||||||
|
animationData: animationData,
|
||||||
|
content: .animation(animationData),
|
||||||
|
itemFile: animationFile,
|
||||||
|
subgroupId: nil,
|
||||||
|
icon: .none,
|
||||||
|
tintMode: animationData.isTemplate ? .primary : .none
|
||||||
|
),
|
||||||
|
context: context,
|
||||||
|
attemptSynchronousLoad: false,
|
||||||
|
content: .animation(animationData),
|
||||||
|
cache: context.animationCache,
|
||||||
|
renderer: context.animationRenderer,
|
||||||
|
placeholderColor: .clear,
|
||||||
|
blurredBadgeColor: .clear,
|
||||||
|
accentIconColor: .white,
|
||||||
|
pointSize: itemNativeFitSize,
|
||||||
|
onUpdateDisplayPlaceholder: { _, _ in
|
||||||
|
}
|
||||||
|
)
|
||||||
|
itemLayer.onContentsUpdate = { [weak self] in
|
||||||
|
if let self {
|
||||||
|
if !self.didAppear {
|
||||||
|
self.didAppear = true
|
||||||
|
Queue.mainQueue().after(0.15) {
|
||||||
|
self.backgroundNode.isHidden = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemLayer.layerTintColor = UIColor.white.cgColor
|
||||||
|
itemLayer.isVisibleForAnimations = self.visibility
|
||||||
|
self.itemLayer = itemLayer
|
||||||
|
self.backgroundNode.layer.addSublayer(itemLayer)
|
||||||
}
|
}
|
||||||
itemLayer.layerTintColor = UIColor.white.cgColor
|
|
||||||
itemLayer.isVisibleForAnimations = self.visibility
|
|
||||||
self.itemLayer = itemLayer
|
|
||||||
self.backgroundNode.layer.addSublayer(itemLayer)
|
|
||||||
|
|
||||||
if let (size, cornerRadius) = self.validLayout {
|
if let (size, cornerRadius) = self.validLayout {
|
||||||
self.updateLayout(size: size, cornerRadius: cornerRadius, transition: .immediate)
|
self.updateLayout(size: size, cornerRadius: cornerRadius, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(markup: TelegramMediaImage.EmojiMarkup, size: CGSize) {
|
public func update(markup: TelegramMediaImage.EmojiMarkup, size: CGSize, useAnimationNode: Bool = true) {
|
||||||
guard markup != self.emojiMarkup else {
|
guard markup != self.emojiMarkup else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.emojiMarkup = markup
|
self.emojiMarkup = markup
|
||||||
self.internalSize = size
|
self.internalSize = size
|
||||||
|
//self.useAnimationNode = useAnimationNode
|
||||||
|
|
||||||
let colors = markup.backgroundColors.map { UInt32(bitPattern: $0) }
|
let colors = markup.backgroundColors.map { UInt32(bitPattern: $0) }
|
||||||
if colors.count == 1 {
|
if colors.count == 1 {
|
||||||
@ -160,7 +183,7 @@ public final class AvatarVideoNode: ASDisplayNode {
|
|||||||
public func update(peer: EnginePeer, photo: TelegramMediaImage, size: CGSize) {
|
public func update(peer: EnginePeer, photo: TelegramMediaImage, size: CGSize) {
|
||||||
self.internalSize = size
|
self.internalSize = size
|
||||||
if let markup = photo.emojiMarkup {
|
if let markup = photo.emojiMarkup {
|
||||||
self.update(markup: markup, size: size)
|
self.update(markup: markup, size: size, useAnimationNode: false)
|
||||||
} else if let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer._asPeer()) {
|
} else if let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer._asPeer()) {
|
||||||
self.backgroundNode.image = nil
|
self.backgroundNode.image = nil
|
||||||
|
|
||||||
@ -177,6 +200,14 @@ public final class AvatarVideoNode: ASDisplayNode {
|
|||||||
private var visibility = false
|
private var visibility = false
|
||||||
public func updateVisibility(_ isVisible: Bool) {
|
public func updateVisibility(_ isVisible: Bool) {
|
||||||
self.visibility = isVisible
|
self.visibility = isVisible
|
||||||
|
if isVisible, let animationNode = self.animationNode, let file = self.animationFile {
|
||||||
|
let pathPrefix = self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
||||||
|
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||||
|
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0))
|
||||||
|
let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker || file.mimeType == "video/webm")
|
||||||
|
animationNode.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
|
||||||
|
}
|
||||||
|
self.animationNode?.visibility = isVisible
|
||||||
if isVisible, let videoContent = self.videoContent, self.videoLoopCount != maxVideoLoopCount {
|
if isVisible, let videoContent = self.videoContent, self.videoLoopCount != maxVideoLoopCount {
|
||||||
if self.videoNode == nil {
|
if self.videoNode == nil {
|
||||||
let context = self.context
|
let context = self.context
|
||||||
@ -247,9 +278,13 @@ public final class AvatarVideoNode: ASDisplayNode {
|
|||||||
videoNode.updateLayout(size: size, transition: transition)
|
videoNode.updateLayout(size: size, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let itemSize = CGSize(width: size.width * 0.67, height: size.height * 0.67)
|
||||||
|
let itemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - itemSize.width) / 2.0), y: floorToScreenPixels((size.height - itemSize.height) / 2.0)), size: itemSize)
|
||||||
|
if let animationNode = self.animationNode {
|
||||||
|
animationNode.frame = itemFrame
|
||||||
|
animationNode.updateLayout(size: itemSize)
|
||||||
|
}
|
||||||
if let itemLayer = self.itemLayer {
|
if let itemLayer = self.itemLayer {
|
||||||
let itemSize = CGSize(width: size.width * 0.67, height: size.height * 0.67)
|
|
||||||
let itemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - itemSize.width) / 2.0), y: floorToScreenPixels((size.height - itemSize.height) / 2.0)), size: itemSize)
|
|
||||||
itemLayer.frame = itemFrame
|
itemLayer.frame = itemFrame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import UniversalMediaPlayer
|
|||||||
import RadialStatusNode
|
import RadialStatusNode
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AvatarNode
|
import AvatarNode
|
||||||
|
import AvatarVideoNode
|
||||||
|
|
||||||
private class PeerInfoAvatarListLoadingStripNode: ASImageNode {
|
private class PeerInfoAvatarListLoadingStripNode: ASImageNode {
|
||||||
private var currentInHierarchy = false
|
private var currentInHierarchy = false
|
||||||
@ -210,6 +211,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
|||||||
private var videoContent: NativeVideoContent?
|
private var videoContent: NativeVideoContent?
|
||||||
private var videoStartTimestamp: Double?
|
private var videoStartTimestamp: Double?
|
||||||
private let playbackStartDisposable = MetaDisposable()
|
private let playbackStartDisposable = MetaDisposable()
|
||||||
|
private var markupNode: AvatarVideoNode?
|
||||||
private let statusDisposable = MetaDisposable()
|
private let statusDisposable = MetaDisposable()
|
||||||
private let preloadDisposable = MetaDisposable()
|
private let preloadDisposable = MetaDisposable()
|
||||||
private let statusNode: RadialStatusNode
|
private let statusNode: RadialStatusNode
|
||||||
@ -260,6 +262,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
|||||||
self.preloadDisposable.set(preloadVideoResource(postbox: self.context.account.postbox, userLocation: .other, userContentType: .video, resourceReference: videoContent.fileReference.resourceReference(videoContent.fileReference.media.resource), duration: duration).start())
|
self.preloadDisposable.set(preloadVideoResource(postbox: self.context.account.postbox, userLocation: .other, userContentType: .video, resourceReference: videoContent.fileReference.resourceReference(videoContent.fileReference.media.resource), duration: duration).start())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.markupNode?.updateVisibility(isCentral)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,6 +349,12 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
transition.updateAlpha(node: videoNode, alpha: 1.0 - fraction)
|
transition.updateAlpha(node: videoNode, alpha: 1.0 - fraction)
|
||||||
}
|
}
|
||||||
|
if let markupNode = self.markupNode {
|
||||||
|
if case .immediate = transition, fraction == 1.0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
transition.updateAlpha(node: markupNode, alpha: 1.0 - fraction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupVideoPlayback() {
|
private func setupVideoPlayback() {
|
||||||
@ -443,24 +452,27 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
|||||||
let videoRepresentations: [VideoRepresentationWithReference]
|
let videoRepresentations: [VideoRepresentationWithReference]
|
||||||
let immediateThumbnailData: Data?
|
let immediateThumbnailData: Data?
|
||||||
var id: Int64
|
var id: Int64
|
||||||
|
let markup: TelegramMediaImage.EmojiMarkup?
|
||||||
switch item {
|
switch item {
|
||||||
case let .custom(node):
|
case let .custom(node):
|
||||||
id = 0
|
|
||||||
representations = []
|
representations = []
|
||||||
videoRepresentations = []
|
videoRepresentations = []
|
||||||
immediateThumbnailData = nil
|
immediateThumbnailData = nil
|
||||||
|
id = 0
|
||||||
|
markup = nil
|
||||||
if !synchronous {
|
if !synchronous {
|
||||||
self.addSubnode(node)
|
self.addSubnode(node)
|
||||||
}
|
}
|
||||||
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
|
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
|
||||||
|
id = self.peer.id.id._internalGetInt64Value()
|
||||||
representations = topRepresentations
|
representations = topRepresentations
|
||||||
videoRepresentations = videoRepresentationsValue
|
videoRepresentations = videoRepresentationsValue
|
||||||
immediateThumbnailData = immediateThumbnail
|
immediateThumbnailData = immediateThumbnail
|
||||||
id = self.peer.id.id._internalGetInt64Value()
|
|
||||||
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
|
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
|
||||||
id = id &+ resource.photoId
|
id = id &+ resource.photoId
|
||||||
}
|
}
|
||||||
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, _):
|
markup = nil
|
||||||
|
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, markupValue):
|
||||||
representations = imageRepresentations
|
representations = imageRepresentations
|
||||||
videoRepresentations = videoRepresentationsValue
|
videoRepresentations = videoRepresentationsValue
|
||||||
immediateThumbnailData = immediateThumbnail
|
immediateThumbnailData = immediateThumbnail
|
||||||
@ -469,10 +481,37 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
id = self.peer.id.id._internalGetInt64Value()
|
id = self.peer.id.id._internalGetInt64Value()
|
||||||
}
|
}
|
||||||
|
markup = markupValue
|
||||||
}
|
}
|
||||||
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations, immediateThumbnailData: immediateThumbnailData, autoFetchFullSize: true, attemptSynchronously: synchronous, skipThumbnail: fullSizeOnly, skipBlurIfLarge: isMain), attemptSynchronously: synchronous, dispatchOnDisplayLink: false)
|
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations, immediateThumbnailData: immediateThumbnailData, autoFetchFullSize: true, attemptSynchronously: synchronous, skipThumbnail: fullSizeOnly, skipBlurIfLarge: isMain), attemptSynchronously: synchronous, dispatchOnDisplayLink: false)
|
||||||
|
|
||||||
if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer) {
|
if let markup {
|
||||||
|
if let videoNode = self.videoNode {
|
||||||
|
self.videoContent = nil
|
||||||
|
self.videoStartTimestamp = nil
|
||||||
|
self.videoNode = nil
|
||||||
|
|
||||||
|
videoNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
self.statusPromise.set(.single(nil))
|
||||||
|
self.statusDisposable.set(nil)
|
||||||
|
|
||||||
|
let markupNode: AvatarVideoNode
|
||||||
|
if let current = self.markupNode {
|
||||||
|
markupNode = current
|
||||||
|
} else {
|
||||||
|
markupNode = AvatarVideoNode(context: self.context)
|
||||||
|
self.insertSubnode(markupNode, belowSubnode: self.statusNode)
|
||||||
|
self.markupNode = markupNode
|
||||||
|
}
|
||||||
|
markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
|
||||||
|
markupNode.updateVisibility(self.isCentral ?? true)
|
||||||
|
|
||||||
|
if !self.didSetReady {
|
||||||
|
self.didSetReady = true
|
||||||
|
self.isReady.set(.single(true))
|
||||||
|
}
|
||||||
|
} else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer) {
|
||||||
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
|
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
|
||||||
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: fullSizeOnly, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil)
|
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: fullSizeOnly, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil)
|
||||||
|
|
||||||
@ -491,7 +530,6 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.statusPromise.set(.single(nil))
|
self.statusPromise.set(.single(nil))
|
||||||
|
|
||||||
self.statusDisposable.set(nil)
|
self.statusDisposable.set(nil)
|
||||||
|
|
||||||
self.imageNode.imageUpdated = { [weak self] _ in
|
self.imageNode.imageUpdated = { [weak self] _ in
|
||||||
@ -520,6 +558,10 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
|||||||
videoNode.updateLayout(size: imageSize, transition: .immediate)
|
videoNode.updateLayout(size: imageSize, transition: .immediate)
|
||||||
videoNode.frame = imageFrame
|
videoNode.frame = imageFrame
|
||||||
}
|
}
|
||||||
|
if let markupNode = self.markupNode {
|
||||||
|
markupNode.updateLayout(size: imageSize, cornerRadius: 0.0, transition: .immediate)
|
||||||
|
markupNode.frame = imageFrame
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ private let phrases = [
|
|||||||
"Halo"
|
"Halo"
|
||||||
]
|
]
|
||||||
|
|
||||||
private var activeCount = 13
|
private var simultaneousDisplayCount = 13
|
||||||
|
|
||||||
private let referenceWidth: CGFloat = 1180
|
private let referenceWidth: CGFloat = 1180
|
||||||
private let positions: [CGPoint] = [
|
private let positions: [CGPoint] = [
|
||||||
@ -69,7 +69,7 @@ final class HelloView: UIView, PhoneDemoDecorationView {
|
|||||||
let phraseIds = Array(self.availablePhraseIds()).shuffled()
|
let phraseIds = Array(self.availablePhraseIds()).shuffled()
|
||||||
let positionIds = Array(self.availablePositionIds()).shuffled()
|
let positionIds = Array(self.availablePositionIds()).shuffled()
|
||||||
|
|
||||||
for i in 0 ..< activeCount {
|
for i in 0 ..< simultaneousDisplayCount {
|
||||||
let delay: Double = Double.random(in: 0.0 ..< 0.8)
|
let delay: Double = Double.random(in: 0.0 ..< 0.8)
|
||||||
Queue.mainQueue().after(delay) {
|
Queue.mainQueue().after(delay) {
|
||||||
self.spawnPhrase(phraseIds[i], positionIndex: positionIds[i])
|
self.spawnPhrase(phraseIds[i], positionIndex: positionIds[i])
|
||||||
|
@ -838,6 +838,34 @@ public extension TelegramEngine.EngineData.Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct TranslationHidden: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||||
|
public typealias Result = Bool
|
||||||
|
|
||||||
|
fileprivate var id: EnginePeer.Id
|
||||||
|
public var mapKey: EnginePeer.Id {
|
||||||
|
return self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(id: EnginePeer.Id) {
|
||||||
|
self.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
var key: PostboxViewKey {
|
||||||
|
return .cachedPeerData(peerId: self.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extract(view: PostboxView) -> Result {
|
||||||
|
guard let view = view as? CachedPeerDataView else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
if let cachedData = view.cachedPeerData as? CachedChannelData {
|
||||||
|
return cachedData.flags.contains(.translationHidden)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct LegacyGroupParticipants: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
public struct LegacyGroupParticipants: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||||
public typealias Result = EnginePeerCachedInfoItem<[EngineLegacyGroupParticipant]>
|
public typealias Result = EnginePeerCachedInfoItem<[EngineLegacyGroupParticipant]>
|
||||||
|
|
||||||
|
@ -6737,15 +6737,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return peer?.isPremium ?? false
|
return peer?.isPremium ?? false
|
||||||
} |> distinctUntilChanged
|
} |> distinctUntilChanged
|
||||||
|
|
||||||
let isHidden = self.chatDisplayNode.historyNode.cachedPeerDataAndMessages
|
let isHidden = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.TranslationHidden(id: self.context.account.peerId))
|
||||||
|> map { cachedDataAndMessages -> Bool in
|
|> distinctUntilChanged
|
||||||
let (cachedData, _) = cachedDataAndMessages
|
|
||||||
var isHidden = false
|
|
||||||
if let cachedData = cachedData as? CachedChannelData, cachedData.flags.contains(.translationHidden) {
|
|
||||||
isHidden = true
|
|
||||||
}
|
|
||||||
return isHidden
|
|
||||||
} |> distinctUntilChanged
|
|
||||||
self.translationStateDisposable = (combineLatest(
|
self.translationStateDisposable = (combineLatest(
|
||||||
queue: .concurrentDefaultQueue(),
|
queue: .concurrentDefaultQueue(),
|
||||||
isPremium,
|
isPremium,
|
||||||
|
@ -29,6 +29,7 @@ import MultiAnimationRenderer
|
|||||||
import ComponentDisplayAdapters
|
import ComponentDisplayAdapters
|
||||||
import ChatTitleView
|
import ChatTitleView
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
import AvatarVideoNode
|
||||||
|
|
||||||
enum PeerInfoHeaderButtonKey: Hashable {
|
enum PeerInfoHeaderButtonKey: Hashable {
|
||||||
case message
|
case message
|
||||||
@ -309,6 +310,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||||||
|
|
||||||
let avatarNode: AvatarNode
|
let avatarNode: AvatarNode
|
||||||
fileprivate var videoNode: UniversalVideoNode?
|
fileprivate var videoNode: UniversalVideoNode?
|
||||||
|
fileprivate var markupNode: AvatarVideoNode?
|
||||||
fileprivate var iconView: ComponentView<Empty>?
|
fileprivate var iconView: ComponentView<Empty>?
|
||||||
private var videoContent: NativeVideoContent?
|
private var videoContent: NativeVideoContent?
|
||||||
private var videoStartTimestamp: Double?
|
private var videoStartTimestamp: Double?
|
||||||
@ -380,12 +382,23 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if fraction > 0.0 {
|
if fraction > 0.0 {
|
||||||
self.videoNode?.pause()
|
videoNode.pause()
|
||||||
} else {
|
} else {
|
||||||
self.videoNode?.play()
|
videoNode.play()
|
||||||
}
|
}
|
||||||
transition.updateAlpha(node: videoNode, alpha: 1.0 - fraction)
|
transition.updateAlpha(node: videoNode, alpha: 1.0 - fraction)
|
||||||
}
|
}
|
||||||
|
if let markupNode = self.markupNode {
|
||||||
|
if case .immediate = transition, fraction == 1.0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fraction > 0.0 {
|
||||||
|
markupNode.updateVisibility(false)
|
||||||
|
} else {
|
||||||
|
markupNode.updateVisibility(true)
|
||||||
|
}
|
||||||
|
transition.updateAlpha(node: markupNode, alpha: 1.0 - fraction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var removedPhotoResourceIds = Set<String>()
|
var removedPhotoResourceIds = Set<String>()
|
||||||
@ -461,9 +474,11 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isForum = false
|
||||||
let avatarCornerRadius: CGFloat
|
let avatarCornerRadius: CGFloat
|
||||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||||
avatarCornerRadius = floor(avatarSize * 0.25)
|
avatarCornerRadius = floor(avatarSize * 0.25)
|
||||||
|
isForum = true
|
||||||
} else {
|
} else {
|
||||||
avatarCornerRadius = avatarSize / 2.0
|
avatarCornerRadius = avatarSize / 2.0
|
||||||
}
|
}
|
||||||
@ -485,12 +500,14 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||||||
let videoRepresentations: [VideoRepresentationWithReference]
|
let videoRepresentations: [VideoRepresentationWithReference]
|
||||||
let immediateThumbnailData: Data?
|
let immediateThumbnailData: Data?
|
||||||
var videoId: Int64
|
var videoId: Int64
|
||||||
|
let markup: TelegramMediaImage.EmojiMarkup?
|
||||||
switch item {
|
switch item {
|
||||||
case .custom:
|
case .custom:
|
||||||
representations = []
|
representations = []
|
||||||
videoRepresentations = []
|
videoRepresentations = []
|
||||||
immediateThumbnailData = nil
|
immediateThumbnailData = nil
|
||||||
videoId = 0
|
videoId = 0
|
||||||
|
markup = nil
|
||||||
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
|
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
|
||||||
representations = topRepresentations
|
representations = topRepresentations
|
||||||
videoRepresentations = videoRepresentationsValue
|
videoRepresentations = videoRepresentationsValue
|
||||||
@ -499,7 +516,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||||||
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
|
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
|
||||||
videoId = videoId &+ resource.photoId
|
videoId = videoId &+ resource.photoId
|
||||||
}
|
}
|
||||||
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, _):
|
markup = nil
|
||||||
|
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, markupValue):
|
||||||
representations = imageRepresentations
|
representations = imageRepresentations
|
||||||
videoRepresentations = videoRepresentationsValue
|
videoRepresentations = videoRepresentationsValue
|
||||||
immediateThumbnailData = immediateThumbnail
|
immediateThumbnailData = immediateThumbnail
|
||||||
@ -508,11 +526,31 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
videoId = peer.id.id._internalGetInt64Value()
|
videoId = peer.id.id._internalGetInt64Value()
|
||||||
}
|
}
|
||||||
|
markup = markupValue
|
||||||
}
|
}
|
||||||
|
|
||||||
self.containerNode.isGestureEnabled = !isSettings
|
self.containerNode.isGestureEnabled = !isSettings
|
||||||
|
|
||||||
if threadInfo == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
|
if let markup {
|
||||||
|
if let videoNode = self.videoNode {
|
||||||
|
self.videoContent = nil
|
||||||
|
self.videoStartTimestamp = nil
|
||||||
|
self.videoNode = nil
|
||||||
|
|
||||||
|
videoNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
|
let markupNode: AvatarVideoNode
|
||||||
|
if let current = self.markupNode {
|
||||||
|
markupNode = current
|
||||||
|
} else {
|
||||||
|
markupNode = AvatarVideoNode(context: self.context)
|
||||||
|
self.containerNode.addSubnode(markupNode)
|
||||||
|
self.markupNode = markupNode
|
||||||
|
}
|
||||||
|
markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
|
||||||
|
markupNode.updateVisibility(true)
|
||||||
|
} else if threadInfo == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
|
||||||
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
|
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
|
||||||
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, 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, storeAfterDownload: nil)
|
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, 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, storeAfterDownload: nil)
|
||||||
if videoContent.id != self.videoContent?.id {
|
if videoContent.id != self.videoContent?.id {
|
||||||
@ -553,28 +591,51 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
|||||||
self.videoContent = videoContent
|
self.videoContent = videoContent
|
||||||
self.videoNode = videoNode
|
self.videoNode = videoNode
|
||||||
|
|
||||||
let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size))
|
let maskPath: UIBezierPath
|
||||||
|
if isForum {
|
||||||
|
maskPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size), cornerRadius: avatarCornerRadius)
|
||||||
|
} else {
|
||||||
|
maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size))
|
||||||
|
}
|
||||||
let shape = CAShapeLayer()
|
let shape = CAShapeLayer()
|
||||||
shape.path = maskPath.cgPath
|
shape.path = maskPath.cgPath
|
||||||
videoNode.layer.mask = shape
|
videoNode.layer.mask = shape
|
||||||
|
|
||||||
self.containerNode.addSubnode(videoNode)
|
self.containerNode.addSubnode(videoNode)
|
||||||
}
|
}
|
||||||
} else if let videoNode = self.videoNode {
|
} else {
|
||||||
|
if let markupNode = self.markupNode {
|
||||||
|
self.markupNode = nil
|
||||||
|
markupNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
if let videoNode = self.videoNode {
|
||||||
|
self.videoStartTimestamp = nil
|
||||||
|
self.videoContent = nil
|
||||||
|
self.videoNode = nil
|
||||||
|
|
||||||
|
videoNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let markupNode = self.markupNode {
|
||||||
|
self.markupNode = nil
|
||||||
|
markupNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
if let videoNode = self.videoNode {
|
||||||
|
self.videoStartTimestamp = nil
|
||||||
self.videoContent = nil
|
self.videoContent = nil
|
||||||
self.videoNode = nil
|
self.videoNode = nil
|
||||||
|
|
||||||
videoNode.removeFromSupernode()
|
videoNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
} else if let videoNode = self.videoNode {
|
|
||||||
self.videoContent = nil
|
|
||||||
self.videoNode = nil
|
|
||||||
|
|
||||||
videoNode.removeFromSupernode()
|
|
||||||
|
|
||||||
self.containerNode.isGestureEnabled = false
|
self.containerNode.isGestureEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let markupNode = self.markupNode {
|
||||||
|
markupNode.frame = self.avatarNode.frame
|
||||||
|
markupNode.updateLayout(size: self.avatarNode.frame.size, cornerRadius: avatarCornerRadius, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
if let videoNode = self.videoNode {
|
if let videoNode = self.videoNode {
|
||||||
if self.canAttachVideo {
|
if self.canAttachVideo {
|
||||||
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
|
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
|
||||||
@ -730,6 +791,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
let avatarNode: AvatarNode
|
let avatarNode: AvatarNode
|
||||||
fileprivate var videoNode: UniversalVideoNode?
|
fileprivate var videoNode: UniversalVideoNode?
|
||||||
|
fileprivate var markupNode: AvatarVideoNode?
|
||||||
private var videoContent: NativeVideoContent?
|
private var videoContent: NativeVideoContent?
|
||||||
private var videoStartTimestamp: Double?
|
private var videoStartTimestamp: Double?
|
||||||
var item: PeerInfoAvatarListItem?
|
var item: PeerInfoAvatarListItem?
|
||||||
@ -799,8 +861,10 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
|||||||
self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, clipStyle: .none, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
|
self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, clipStyle: .none, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
|
||||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize))
|
self.avatarNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize))
|
||||||
|
|
||||||
|
var isForum = false
|
||||||
let avatarCornerRadius: CGFloat
|
let avatarCornerRadius: CGFloat
|
||||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||||
|
isForum = true
|
||||||
avatarCornerRadius = floor(avatarSize * 0.25)
|
avatarCornerRadius = floor(avatarSize * 0.25)
|
||||||
} else {
|
} else {
|
||||||
avatarCornerRadius = avatarSize / 2.0
|
avatarCornerRadius = avatarSize / 2.0
|
||||||
@ -816,35 +880,63 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
|||||||
let representations: [ImageRepresentationWithReference]
|
let representations: [ImageRepresentationWithReference]
|
||||||
let videoRepresentations: [VideoRepresentationWithReference]
|
let videoRepresentations: [VideoRepresentationWithReference]
|
||||||
let immediateThumbnailData: Data?
|
let immediateThumbnailData: Data?
|
||||||
var id: Int64
|
var videoId: Int64
|
||||||
|
let markup: TelegramMediaImage.EmojiMarkup?
|
||||||
switch item {
|
switch item {
|
||||||
case .custom:
|
case .custom:
|
||||||
representations = []
|
representations = []
|
||||||
videoRepresentations = []
|
videoRepresentations = []
|
||||||
immediateThumbnailData = nil
|
immediateThumbnailData = nil
|
||||||
id = 0
|
videoId = 0
|
||||||
|
markup = nil
|
||||||
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
|
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
|
||||||
representations = topRepresentations
|
representations = topRepresentations
|
||||||
videoRepresentations = videoRepresentationsValue
|
videoRepresentations = videoRepresentationsValue
|
||||||
immediateThumbnailData = immediateThumbnail
|
immediateThumbnailData = immediateThumbnail
|
||||||
id = peer.id.id._internalGetInt64Value()
|
videoId = peer.id.id._internalGetInt64Value()
|
||||||
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
|
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
|
||||||
id = id &+ resource.photoId
|
videoId = videoId &+ resource.photoId
|
||||||
}
|
}
|
||||||
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, _):
|
markup = nil
|
||||||
|
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, markupValue):
|
||||||
representations = imageRepresentations
|
representations = imageRepresentations
|
||||||
videoRepresentations = videoRepresentationsValue
|
videoRepresentations = videoRepresentationsValue
|
||||||
immediateThumbnailData = immediateThumbnail
|
immediateThumbnailData = immediateThumbnail
|
||||||
if case let .cloud(imageId, _, _) = reference {
|
if case let .cloud(imageId, _, _) = reference {
|
||||||
id = imageId
|
videoId = imageId
|
||||||
} else {
|
} else {
|
||||||
id = peer.id.id._internalGetInt64Value()
|
videoId = peer.id.id._internalGetInt64Value()
|
||||||
}
|
}
|
||||||
|
markup = markupValue
|
||||||
}
|
}
|
||||||
|
|
||||||
if threadData == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
|
if let markup {
|
||||||
|
if let videoNode = self.videoNode {
|
||||||
|
self.videoContent = nil
|
||||||
|
self.videoStartTimestamp = nil
|
||||||
|
self.videoNode = nil
|
||||||
|
|
||||||
|
videoNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
|
let markupNode: AvatarVideoNode
|
||||||
|
if let current = self.markupNode {
|
||||||
|
markupNode = current
|
||||||
|
} else {
|
||||||
|
markupNode = AvatarVideoNode(context: self.context)
|
||||||
|
self.insertSubnode(markupNode, aboveSubnode: self.avatarNode)
|
||||||
|
self.markupNode = markupNode
|
||||||
|
}
|
||||||
|
markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
|
||||||
|
markupNode.updateVisibility(true)
|
||||||
|
} else if threadData == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
|
||||||
|
if let markupNode = self.markupNode {
|
||||||
|
self.markupNode = nil
|
||||||
|
markupNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
|
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
|
||||||
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), userLocation: .other, 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, storeAfterDownload: nil)
|
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, 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, storeAfterDownload: nil)
|
||||||
if videoContent.id != self.videoContent?.id {
|
if videoContent.id != self.videoContent?.id {
|
||||||
self.videoNode?.removeFromSupernode()
|
self.videoNode?.removeFromSupernode()
|
||||||
|
|
||||||
@ -855,19 +947,30 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
|||||||
self.videoContent = videoContent
|
self.videoContent = videoContent
|
||||||
self.videoNode = videoNode
|
self.videoNode = videoNode
|
||||||
|
|
||||||
let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size))
|
let maskPath: UIBezierPath
|
||||||
|
if isForum {
|
||||||
|
maskPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size), cornerRadius: avatarCornerRadius)
|
||||||
|
} else {
|
||||||
|
maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size))
|
||||||
|
}
|
||||||
let shape = CAShapeLayer()
|
let shape = CAShapeLayer()
|
||||||
shape.path = maskPath.cgPath
|
shape.path = maskPath.cgPath
|
||||||
videoNode.layer.mask = shape
|
videoNode.layer.mask = shape
|
||||||
|
|
||||||
self.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
|
self.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
|
||||||
}
|
}
|
||||||
} else if let videoNode = self.videoNode {
|
} else {
|
||||||
self.videoStartTimestamp = nil
|
if let markupNode = self.markupNode {
|
||||||
self.videoContent = nil
|
self.markupNode = nil
|
||||||
self.videoNode = nil
|
markupNode.removeFromSupernode()
|
||||||
|
}
|
||||||
videoNode.removeFromSupernode()
|
if let videoNode = self.videoNode {
|
||||||
|
self.videoStartTimestamp = nil
|
||||||
|
self.videoContent = nil
|
||||||
|
self.videoNode = nil
|
||||||
|
|
||||||
|
videoNode.removeFromSupernode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if let videoNode = self.videoNode {
|
} else if let videoNode = self.videoNode {
|
||||||
self.videoStartTimestamp = nil
|
self.videoStartTimestamp = nil
|
||||||
@ -877,6 +980,11 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
|||||||
videoNode.removeFromSupernode()
|
videoNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let markupNode = self.markupNode {
|
||||||
|
markupNode.frame = self.avatarNode.frame
|
||||||
|
markupNode.updateLayout(size: self.avatarNode.frame.size, cornerRadius: avatarCornerRadius, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
if let videoNode = self.videoNode {
|
if let videoNode = self.videoNode {
|
||||||
if self.canAttachVideo {
|
if self.canAttachVideo {
|
||||||
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
|
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
|
||||||
@ -897,6 +1005,8 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
final class PeerInfoAvatarListNode: ASDisplayNode {
|
final class PeerInfoAvatarListNode: ASDisplayNode {
|
||||||
private let isSettings: Bool
|
private let isSettings: Bool
|
||||||
let pinchSourceNode: PinchSourceContainerNode
|
let pinchSourceNode: PinchSourceContainerNode
|
||||||
@ -1018,6 +1128,8 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
|
|||||||
if let currentItemNode = self.listContainerNode.currentItemNode, case .animated = transition {
|
if let currentItemNode = self.listContainerNode.currentItemNode, case .animated = transition {
|
||||||
if let _ = self.avatarContainerNode.videoNode {
|
if let _ = self.avatarContainerNode.videoNode {
|
||||||
|
|
||||||
|
} else if let _ = self.avatarContainerNode.markupNode {
|
||||||
|
|
||||||
} else if let unroundedImage = self.avatarContainerNode.avatarNode.unroundedImage {
|
} else if let unroundedImage = self.avatarContainerNode.avatarNode.unroundedImage {
|
||||||
let avatarCopyView = UIImageView()
|
let avatarCopyView = UIImageView()
|
||||||
avatarCopyView.image = unroundedImage
|
avatarCopyView.image = unroundedImage
|
||||||
|
Loading…
x
Reference in New Issue
Block a user