mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
679 lines
32 KiB
Swift
679 lines
32 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import ComponentFlow
|
|
import TelegramPresentationData
|
|
import PhotoResources
|
|
import AvatarNode
|
|
import AccountContext
|
|
import InvisibleInkDustNode
|
|
|
|
final class StarsParticlesView: UIView {
|
|
private struct Particle {
|
|
var trackIndex: Int
|
|
var position: CGPoint
|
|
var scale: CGFloat
|
|
var alpha: CGFloat
|
|
var direction: CGPoint
|
|
var velocity: CGFloat
|
|
var color: UIColor
|
|
var currentTime: CGFloat
|
|
var lifeTime: CGFloat
|
|
|
|
init(
|
|
trackIndex: Int,
|
|
position: CGPoint,
|
|
scale: CGFloat,
|
|
alpha: CGFloat,
|
|
direction: CGPoint,
|
|
velocity: CGFloat,
|
|
color: UIColor,
|
|
currentTime: CGFloat,
|
|
lifeTime: CGFloat
|
|
) {
|
|
self.trackIndex = trackIndex
|
|
self.position = position
|
|
self.scale = scale
|
|
self.alpha = alpha
|
|
self.direction = direction
|
|
self.velocity = velocity
|
|
self.color = color
|
|
self.currentTime = currentTime
|
|
self.lifeTime = lifeTime
|
|
}
|
|
|
|
mutating func update(deltaTime: CGFloat) {
|
|
var position = self.position
|
|
position.x += self.direction.x * self.velocity * deltaTime
|
|
position.y += self.direction.y * self.velocity * deltaTime
|
|
self.position = position
|
|
self.currentTime += deltaTime
|
|
}
|
|
}
|
|
|
|
private final class ParticleSet {
|
|
private let size: CGSize
|
|
private let large: Bool
|
|
private(set) var particles: [Particle] = []
|
|
|
|
init(size: CGSize, large: Bool, preAdvance: Bool) {
|
|
self.size = size
|
|
self.large = large
|
|
|
|
self.generateParticles(preAdvance: preAdvance)
|
|
}
|
|
|
|
private func generateParticles(preAdvance: Bool) {
|
|
let maxDirections = self.large ? 8 : 80
|
|
|
|
if self.particles.count < maxDirections {
|
|
var allTrackIndices: [Int] = Array(repeating: 0, count: maxDirections)
|
|
for i in 0 ..< maxDirections {
|
|
allTrackIndices[i] = i
|
|
}
|
|
var takenIndexCount = 0
|
|
for particle in self.particles {
|
|
allTrackIndices[particle.trackIndex] = -1
|
|
takenIndexCount += 1
|
|
}
|
|
var availableTrackIndices: [Int] = []
|
|
availableTrackIndices.reserveCapacity(maxDirections - takenIndexCount)
|
|
for index in allTrackIndices {
|
|
if index != -1 {
|
|
availableTrackIndices.append(index)
|
|
}
|
|
}
|
|
|
|
if !availableTrackIndices.isEmpty {
|
|
availableTrackIndices.shuffle()
|
|
|
|
for takeIndex in availableTrackIndices {
|
|
let directionIndex = takeIndex
|
|
var angle = (CGFloat(directionIndex % maxDirections) / CGFloat(maxDirections)) * CGFloat.pi * 2.0
|
|
var alpha = 1.0
|
|
var lifeTimeMultiplier = 1.0
|
|
|
|
var isUpOrDownSemisphere = false
|
|
if angle > CGFloat.pi / 7.0 && angle < CGFloat.pi - CGFloat.pi / 7.0 {
|
|
isUpOrDownSemisphere = true
|
|
} else if !"".isEmpty, angle > CGFloat.pi + CGFloat.pi / 7.0 && angle < 2.0 * CGFloat.pi - CGFloat.pi / 7.0 {
|
|
isUpOrDownSemisphere = true
|
|
}
|
|
|
|
if isUpOrDownSemisphere {
|
|
if CGFloat.random(in: 0.0 ... 1.0) < 0.2 {
|
|
lifeTimeMultiplier = 0.3
|
|
} else {
|
|
angle += CGFloat.random(in: 0.0 ... 1.0) > 0.5 ? CGFloat.pi / 1.6 : -CGFloat.pi / 1.6
|
|
angle += CGFloat.random(in: -0.2 ... 0.2)
|
|
lifeTimeMultiplier = 0.5
|
|
}
|
|
if self.large {
|
|
alpha = 0.0
|
|
}
|
|
}
|
|
if self.large {
|
|
angle += CGFloat.random(in: -0.5 ... 0.5)
|
|
}
|
|
|
|
let direction = CGPoint(x: cos(angle), y: sin(angle))
|
|
let velocity = self.large ? CGFloat.random(in: 15.0 ..< 20.0) : CGFloat.random(in: 20.0 ..< 35.0)
|
|
let scale = self.large ? CGFloat.random(in: 0.65 ... 0.9) : CGFloat.random(in: 0.65 ... 1.0) * 0.75
|
|
let lifeTime = (self.large ? CGFloat.random(in: 2.0 ... 3.5) : CGFloat.random(in: 0.7 ... 3.0))
|
|
|
|
var position = CGPoint(x: self.size.width / 2.0, y: self.size.height / 2.0)
|
|
var initialOffset: CGFloat = 0.5
|
|
if preAdvance {
|
|
initialOffset = CGFloat.random(in: 0.5 ... 1.0)
|
|
} else {
|
|
let p = CGFloat.random(in: 0.0 ... 1.0)
|
|
if p < 0.5 {
|
|
initialOffset = CGFloat.random(in: 0.65 ... 1.0)
|
|
} else {
|
|
initialOffset = 0.5
|
|
}
|
|
}
|
|
position.x += direction.x * initialOffset * 105.0
|
|
position.y += direction.y * initialOffset * 105.0
|
|
|
|
let largeColors: [UInt32] = [0xff9145, 0xfec007, 0xed9303]
|
|
let smallColors: [UInt32] = [0xfecc14, 0xf7ab04, 0xff9145, 0xfdda21]
|
|
|
|
let particle = Particle(
|
|
trackIndex: directionIndex,
|
|
position: position,
|
|
scale: scale,
|
|
alpha: alpha,
|
|
direction: direction,
|
|
velocity: velocity,
|
|
color: UIColor(rgb: (self.large ? largeColors : smallColors).randomElement()!),
|
|
currentTime: 0.0,
|
|
lifeTime: lifeTime * lifeTimeMultiplier
|
|
)
|
|
self.particles.append(particle)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func update(deltaTime: CGFloat) {
|
|
for i in (0 ..< self.particles.count).reversed() {
|
|
self.particles[i].update(deltaTime: deltaTime)
|
|
if self.particles[i].currentTime > self.particles[i].lifeTime {
|
|
self.particles.remove(at: i)
|
|
}
|
|
}
|
|
|
|
self.generateParticles(preAdvance: false)
|
|
}
|
|
}
|
|
|
|
private var displayLink: SharedDisplayLinkDriver.Link?
|
|
|
|
private var particleSet: ParticleSet?
|
|
private let particleImage: UIImage
|
|
private var particleLayers: [SimpleLayer] = []
|
|
|
|
private var size: CGSize?
|
|
private let large: Bool
|
|
|
|
init(size: CGSize, large: Bool) {
|
|
if large {
|
|
self.particleImage = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/PremiumIcon"), color: .white)!.withRenderingMode(.alwaysTemplate)
|
|
} else {
|
|
self.particleImage = generateTintedImage(image: UIImage(bundleImageName: "Premium/Stars/Particle"), color: .white)!.withRenderingMode(.alwaysTemplate)
|
|
}
|
|
|
|
self.large = large
|
|
|
|
super.init(frame: .zero)
|
|
|
|
self.particleSet = ParticleSet(size: size, large: large, preAdvance: true)
|
|
|
|
self.displayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] delta in
|
|
self?.update(deltaTime: CGFloat(delta))
|
|
})
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
preconditionFailure()
|
|
}
|
|
|
|
fileprivate func update(size: CGSize) {
|
|
self.size = size
|
|
}
|
|
|
|
private func update(deltaTime: CGFloat) {
|
|
guard let particleSet = self.particleSet else {
|
|
return
|
|
}
|
|
particleSet.update(deltaTime: deltaTime)
|
|
|
|
for i in 0 ..< particleSet.particles.count {
|
|
let particle = particleSet.particles[i]
|
|
|
|
let particleLayer: SimpleLayer
|
|
if i < self.particleLayers.count {
|
|
particleLayer = self.particleLayers[i]
|
|
particleLayer.isHidden = false
|
|
} else {
|
|
particleLayer = SimpleLayer()
|
|
particleLayer.contents = self.particleImage.cgImage
|
|
particleLayer.bounds = CGRect(origin: CGPoint(), size: particleImage.size)
|
|
self.particleLayers.append(particleLayer)
|
|
self.layer.addSublayer(particleLayer)
|
|
}
|
|
|
|
particleLayer.layerTintColor = particle.color.cgColor
|
|
|
|
particleLayer.position = particle.position
|
|
particleLayer.opacity = Float(particle.alpha)
|
|
|
|
let particleScale = min(1.0, particle.currentTime / 0.3) * min(1.0, (particle.lifeTime - particle.currentTime) / 0.2) * particle.scale
|
|
particleLayer.transform = CATransform3DMakeScale(particleScale, particleScale, 1.0)
|
|
}
|
|
if particleSet.particles.count < self.particleLayers.count {
|
|
for i in particleSet.particles.count ..< self.particleLayers.count {
|
|
self.particleLayers[i].isHidden = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public final class StarsImageComponent: Component {
|
|
public enum Subject: Equatable {
|
|
case none
|
|
case photo(TelegramMediaWebFile)
|
|
case media([Media])
|
|
case extendedMedia([TelegramExtendedMedia])
|
|
case transactionPeer(StarsContext.State.Transaction.Peer)
|
|
|
|
public static func == (lhs: StarsImageComponent.Subject, rhs: StarsImageComponent.Subject) -> Bool {
|
|
switch lhs {
|
|
case .none:
|
|
if case .none = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .photo(lhsPhoto):
|
|
if case let .photo(rhsPhoto) = rhs, lhsPhoto == rhsPhoto {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .media(lhsMedia):
|
|
if case let .media(rhsMedia) = rhs, areMediaArraysEqual(lhsMedia, rhsMedia) {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .extendedMedia(lhsExtendedMedia):
|
|
if case let .extendedMedia(rhsExtendedMedia) = rhs, lhsExtendedMedia == rhsExtendedMedia {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .transactionPeer(lhsPeer):
|
|
if case let .transactionPeer(rhsPeer) = rhs, lhsPeer == rhsPeer {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public let context: AccountContext
|
|
public let subject: Subject
|
|
public let theme: PresentationTheme
|
|
public let diameter: CGFloat
|
|
public let backgroundColor: UIColor
|
|
|
|
public init(
|
|
context: AccountContext,
|
|
subject: Subject,
|
|
theme: PresentationTheme,
|
|
diameter: CGFloat,
|
|
backgroundColor: UIColor
|
|
) {
|
|
self.context = context
|
|
self.subject = subject
|
|
self.theme = theme
|
|
self.diameter = diameter
|
|
self.backgroundColor = backgroundColor
|
|
}
|
|
|
|
public static func ==(lhs: StarsImageComponent, rhs: StarsImageComponent) -> Bool {
|
|
if lhs.context !== rhs.context {
|
|
return false
|
|
}
|
|
if lhs.subject != rhs.subject {
|
|
return false
|
|
}
|
|
if lhs.theme !== rhs.theme {
|
|
return false
|
|
}
|
|
if lhs.diameter != rhs.diameter {
|
|
return false
|
|
}
|
|
if lhs.backgroundColor != rhs.backgroundColor {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public final class View: UIView {
|
|
private var component: StarsImageComponent?
|
|
|
|
private var smallParticlesView: StarsParticlesView?
|
|
private var largeParticlesView: StarsParticlesView?
|
|
|
|
private var imageNode: TransformImageNode?
|
|
private var imageFrameNode: UIView?
|
|
private var secondImageNode: TransformImageNode?
|
|
private var avatarNode: ImageNode?
|
|
private var iconBackgroundView: UIImageView?
|
|
private var iconView: UIImageView?
|
|
private var dustNode: MediaDustNode?
|
|
|
|
private var countView = ComponentView<Empty>()
|
|
|
|
private let fetchDisposable = MetaDisposable()
|
|
|
|
public override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
preconditionFailure()
|
|
}
|
|
deinit {
|
|
self.fetchDisposable.dispose()
|
|
}
|
|
|
|
func update(component: StarsImageComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
|
self.component = component
|
|
|
|
let smallParticlesView: StarsParticlesView
|
|
if let current = self.smallParticlesView {
|
|
smallParticlesView = current
|
|
} else {
|
|
smallParticlesView = StarsParticlesView(size: availableSize, large: false)
|
|
|
|
self.addSubview(smallParticlesView)
|
|
self.smallParticlesView = smallParticlesView
|
|
}
|
|
smallParticlesView.update(size: availableSize)
|
|
smallParticlesView.frame = CGRect(origin: .zero, size: availableSize)
|
|
|
|
let largeParticlesView: StarsParticlesView
|
|
if let current = self.largeParticlesView {
|
|
largeParticlesView = current
|
|
} else {
|
|
largeParticlesView = StarsParticlesView(size: availableSize, large: true)
|
|
|
|
self.addSubview(largeParticlesView)
|
|
self.largeParticlesView = largeParticlesView
|
|
}
|
|
largeParticlesView.update(size: availableSize)
|
|
largeParticlesView.frame = CGRect(origin: .zero, size: availableSize)
|
|
|
|
var imageSize = CGSize(width: component.diameter, height: component.diameter)
|
|
if case let .media(media) = component.subject, media.count > 1 {
|
|
imageSize = CGSize(width: component.diameter - 6.0, height: component.diameter - 6.0)
|
|
} else if case let .extendedMedia(media) = component.subject, media.count > 1 {
|
|
imageSize = CGSize(width: component.diameter - 6.0, height: component.diameter - 6.0)
|
|
}
|
|
|
|
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - imageSize.height) / 2.0)), size: imageSize)
|
|
|
|
switch component.subject {
|
|
case .none:
|
|
break
|
|
case let .photo(photo):
|
|
let imageNode: TransformImageNode
|
|
if let current = self.imageNode {
|
|
imageNode = current
|
|
} else {
|
|
imageNode = TransformImageNode()
|
|
imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
|
|
self.addSubview(imageNode.view)
|
|
self.imageNode = imageNode
|
|
|
|
imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo))
|
|
self.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: component.context.account, userLocation: .other, image: photo).startStrict())
|
|
}
|
|
|
|
imageNode.frame = imageFrame
|
|
imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: imageSize.width / 2.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))()
|
|
case let .media(media):
|
|
let imageNode: TransformImageNode
|
|
var dimensions = imageSize
|
|
if let current = self.imageNode {
|
|
imageNode = current
|
|
} else {
|
|
imageNode = TransformImageNode()
|
|
imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
|
|
self.addSubview(imageNode.view)
|
|
self.imageNode = imageNode
|
|
|
|
if let image = media.first as? TelegramMediaImage {
|
|
if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions {
|
|
dimensions = imageDimensions.cgSize.aspectFilled(imageSize)
|
|
}
|
|
imageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false))
|
|
} else if let file = media.first as? TelegramMediaFile {
|
|
if let videoDimensions = file.dimensions {
|
|
dimensions = videoDimensions.cgSize.aspectFilled(imageSize)
|
|
}
|
|
imageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), useLargeThumbnail: true, autoFetchFullSizeThumbnail: true))
|
|
}
|
|
}
|
|
imageNode.frame = imageFrame
|
|
imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: dimensions, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))()
|
|
|
|
if media.count > 1 {
|
|
let secondImageNode: TransformImageNode
|
|
let imageFrameNode: UIView
|
|
if let current = self.secondImageNode, let currentFrame = self.imageFrameNode {
|
|
secondImageNode = current
|
|
imageFrameNode = currentFrame
|
|
} else {
|
|
secondImageNode = TransformImageNode()
|
|
secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
|
|
self.insertSubview(secondImageNode.view, belowSubview: imageNode.view)
|
|
self.secondImageNode = secondImageNode
|
|
|
|
imageFrameNode = UIView()
|
|
imageFrameNode.layer.cornerRadius = 17.0
|
|
self.insertSubview(imageFrameNode, belowSubview: imageNode.view)
|
|
self.imageFrameNode = imageFrameNode
|
|
|
|
if let image = media[1] as? TelegramMediaImage {
|
|
if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions {
|
|
dimensions = imageDimensions.cgSize.aspectFilled(imageSize)
|
|
}
|
|
secondImageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false))
|
|
}
|
|
}
|
|
imageFrameNode.backgroundColor = component.backgroundColor
|
|
secondImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))()
|
|
secondImageNode.frame = imageFrame.offsetBy(dx: 6.0, dy: -6.0)
|
|
imageFrameNode.frame = imageFrame.insetBy(dx: -2.0, dy: -2.0)
|
|
|
|
let countSize = self.countView.update(
|
|
transition: .immediate,
|
|
component: AnyComponent(
|
|
Text(text: "\(media.count)", font: Font.with(size: 30.0, design: .round, weight: .medium), color: .white)
|
|
),
|
|
environment: {},
|
|
containerSize: imageFrame.size
|
|
)
|
|
let countFrame = CGRect(origin: CGPoint(x: imageFrame.minX + floorToScreenPixels((imageFrame.width - countSize.width) / 2.0), y: imageFrame.minY + floorToScreenPixels((imageFrame.height - countSize.height) / 2.0)), size: countSize)
|
|
if let countView = self.countView.view {
|
|
if countView.superview == nil {
|
|
self.addSubview(countView)
|
|
}
|
|
countView.frame = countFrame
|
|
}
|
|
}
|
|
case let .extendedMedia(extendedMedia):
|
|
let imageNode: TransformImageNode
|
|
let dustNode: MediaDustNode
|
|
if let current = self.imageNode, let currentDust = self.dustNode {
|
|
imageNode = current
|
|
dustNode = currentDust
|
|
} else {
|
|
imageNode = TransformImageNode()
|
|
imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
|
|
self.addSubview(imageNode.view)
|
|
self.imageNode = imageNode
|
|
|
|
let media: TelegramMediaImage
|
|
switch extendedMedia.first {
|
|
case let .preview(_, immediateThumbnailData, _):
|
|
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
|
|
media = thumbnailMedia
|
|
default:
|
|
fatalError()
|
|
}
|
|
|
|
imageNode.setSignal(chatSecretPhoto(account: component.context.account, userLocation: .other, photoReference: .standalone(media: media), ignoreFullSize: true, synchronousLoad: true))
|
|
|
|
dustNode = MediaDustNode(enableAnimations: true)
|
|
self.addSubview(dustNode.view)
|
|
self.dustNode = dustNode
|
|
}
|
|
|
|
if extendedMedia.count > 1 {
|
|
let secondImageNode: TransformImageNode
|
|
let imageFrameNode: UIView
|
|
if let current = self.secondImageNode, let currentFrame = self.imageFrameNode {
|
|
secondImageNode = current
|
|
imageFrameNode = currentFrame
|
|
} else {
|
|
secondImageNode = TransformImageNode()
|
|
secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
|
|
self.insertSubview(secondImageNode.view, belowSubview: imageNode.view)
|
|
self.secondImageNode = secondImageNode
|
|
|
|
imageFrameNode = UIView()
|
|
imageFrameNode.layer.cornerRadius = 17.0
|
|
self.insertSubview(imageFrameNode, belowSubview: imageNode.view)
|
|
self.imageFrameNode = imageFrameNode
|
|
|
|
let media: TelegramMediaImage
|
|
switch extendedMedia[1] {
|
|
case let .preview(_, immediateThumbnailData, _):
|
|
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
|
|
media = thumbnailMedia
|
|
default:
|
|
fatalError()
|
|
}
|
|
|
|
secondImageNode.setSignal(chatSecretPhoto(account: component.context.account, userLocation: .other, photoReference: .standalone(media: media), ignoreFullSize: true, synchronousLoad: true))
|
|
}
|
|
imageFrameNode.backgroundColor = component.backgroundColor
|
|
secondImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))()
|
|
secondImageNode.frame = imageFrame.offsetBy(dx: 6.0, dy: -6.0)
|
|
imageFrameNode.frame = imageFrame.insetBy(dx: -2.0, dy: -2.0)
|
|
}
|
|
|
|
imageNode.frame = imageFrame
|
|
imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))()
|
|
|
|
dustNode.frame = imageFrame
|
|
dustNode.update(size: imageFrame.size, color: .white, transition: .immediate)
|
|
|
|
if extendedMedia.count > 1 {
|
|
let countSize = self.countView.update(
|
|
transition: .immediate,
|
|
component: AnyComponent(
|
|
Text(text: "\(extendedMedia.count)", font: Font.with(size: 30.0, design: .round, weight: .medium), color: .white)
|
|
),
|
|
environment: {},
|
|
containerSize: imageFrame.size
|
|
)
|
|
let countFrame = CGRect(origin: CGPoint(x: imageFrame.minX + floorToScreenPixels((imageFrame.width - countSize.width) / 2.0), y: imageFrame.minY + floorToScreenPixels((imageFrame.height - countSize.height) / 2.0)), size: countSize)
|
|
if let countView = self.countView.view {
|
|
if countView.superview == nil {
|
|
self.addSubview(countView)
|
|
}
|
|
countView.frame = countFrame
|
|
}
|
|
}
|
|
case let .transactionPeer(peer):
|
|
if case let .peer(peer) = peer {
|
|
let avatarNode: ImageNode
|
|
if let current = self.avatarNode {
|
|
avatarNode = current
|
|
} else {
|
|
avatarNode = ImageNode()
|
|
avatarNode.displaysAsynchronously = false
|
|
self.addSubview(avatarNode.view)
|
|
self.avatarNode = avatarNode
|
|
|
|
avatarNode.setSignal(peerAvatarCompleteImage(account: component.context.account, peer: peer, size: imageSize, font: avatarPlaceholderFont(size: 43.0), fullSize: true))
|
|
}
|
|
avatarNode.frame = imageFrame
|
|
} else {
|
|
let iconBackgroundView: UIImageView
|
|
let iconView: UIImageView
|
|
if let currentBackground = self.iconBackgroundView, let current = self.iconView {
|
|
iconBackgroundView = currentBackground
|
|
iconView = current
|
|
} else {
|
|
iconBackgroundView = UIImageView()
|
|
iconView = UIImageView()
|
|
|
|
self.addSubview(iconBackgroundView)
|
|
self.addSubview(iconView)
|
|
|
|
self.iconBackgroundView = iconBackgroundView
|
|
self.iconView = iconView
|
|
}
|
|
|
|
var iconInset: CGFloat = 9.0
|
|
var iconOffset: CGFloat = 0.0
|
|
switch peer {
|
|
case .appStore:
|
|
iconBackgroundView.image = generateGradientFilledCircleImage(
|
|
diameter: imageSize.width,
|
|
colors: [
|
|
UIColor(rgb: 0x2a9ef1).cgColor,
|
|
UIColor(rgb: 0x72d5fd).cgColor
|
|
],
|
|
direction: .mirroredDiagonal
|
|
)
|
|
iconView.image = UIImage(bundleImageName: "Premium/Stars/Apple")
|
|
case .playMarket:
|
|
iconBackgroundView.image = generateGradientFilledCircleImage(
|
|
diameter: imageSize.width,
|
|
colors: [
|
|
UIColor(rgb: 0x54cb68).cgColor,
|
|
UIColor(rgb: 0xa0de7e).cgColor
|
|
],
|
|
direction: .mirroredDiagonal
|
|
)
|
|
iconView.image = UIImage(bundleImageName: "Premium/Stars/Google")
|
|
case .fragment:
|
|
iconBackgroundView.image = generateFilledCircleImage(
|
|
diameter: imageSize.width,
|
|
color: UIColor(rgb: 0x1b1f24)
|
|
)
|
|
iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
|
|
iconOffset = 5.0
|
|
case .ads:
|
|
iconBackgroundView.image = generateFilledCircleImage(
|
|
diameter: imageSize.width,
|
|
color: UIColor(rgb: 0x1b1f24)
|
|
)
|
|
iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
|
|
iconOffset = 5.0
|
|
case .premiumBot:
|
|
iconInset = 15.0
|
|
iconBackgroundView.image = generateGradientFilledCircleImage(
|
|
diameter: imageSize.width,
|
|
colors: [
|
|
UIColor(rgb: 0x6b93ff).cgColor,
|
|
UIColor(rgb: 0x6b93ff).cgColor,
|
|
UIColor(rgb: 0x8d77ff).cgColor,
|
|
UIColor(rgb: 0xb56eec).cgColor,
|
|
UIColor(rgb: 0xb56eec).cgColor
|
|
],
|
|
direction: .mirroredDiagonal
|
|
)
|
|
iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
|
|
case .peer, .unsupported:
|
|
iconInset = 15.0
|
|
iconBackgroundView.image = generateGradientFilledCircleImage(
|
|
diameter: imageSize.width,
|
|
colors: [
|
|
UIColor(rgb: 0xb1b1b1).cgColor,
|
|
UIColor(rgb: 0xcdcdcd).cgColor
|
|
],
|
|
direction: .mirroredDiagonal
|
|
)
|
|
iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
|
|
}
|
|
iconBackgroundView.frame = imageFrame
|
|
iconView.frame = imageFrame.insetBy(dx: iconInset, dy: iconInset).offsetBy(dx: 0.0, dy: iconOffset)
|
|
}
|
|
}
|
|
return availableSize
|
|
}
|
|
}
|
|
|
|
public func makeView() -> View {
|
|
return View(frame: CGRect())
|
|
}
|
|
|
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
|
}
|
|
}
|