mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
572 lines
27 KiB
Swift
572 lines
27 KiB
Swift
import Foundation
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import ComponentFlow
|
|
import ComponentDisplayAdapters
|
|
import AnimationCache
|
|
import MultiAnimationRenderer
|
|
import TelegramCore
|
|
import AccountContext
|
|
import SwiftSignalKit
|
|
import EmojiTextAttachmentView
|
|
import LokiRng
|
|
|
|
private final class PatternContentsTarget: MultiAnimationRenderTarget {
|
|
private let imageUpdated: (Bool) -> Void
|
|
|
|
init(imageUpdated: @escaping (Bool) -> Void) {
|
|
self.imageUpdated = imageUpdated
|
|
|
|
super.init()
|
|
}
|
|
|
|
required init(coder: NSCoder) {
|
|
preconditionFailure()
|
|
}
|
|
|
|
override func transitionToContents(_ contents: AnyObject, didLoop: Bool) {
|
|
let hadContents = self.contents != nil
|
|
self.contents = contents
|
|
self.imageUpdated(hadContents)
|
|
}
|
|
}
|
|
|
|
private func windowFunction(t: CGFloat) -> CGFloat {
|
|
return bezierPoint(0.6, 0.0, 0.4, 1.0, t)
|
|
}
|
|
|
|
private func patternScaleValueAt(fraction: CGFloat, t: CGFloat, reverse: Bool) -> CGFloat {
|
|
let windowSize: CGFloat = 0.8
|
|
|
|
let effectiveT: CGFloat
|
|
let windowStartOffset: CGFloat
|
|
let windowEndOffset: CGFloat
|
|
if reverse {
|
|
effectiveT = 1.0 - t
|
|
windowStartOffset = 1.0
|
|
windowEndOffset = -windowSize
|
|
} else {
|
|
effectiveT = t
|
|
windowStartOffset = -windowSize
|
|
windowEndOffset = 1.0
|
|
}
|
|
|
|
let windowPosition = (1.0 - fraction) * windowStartOffset + fraction * windowEndOffset
|
|
let windowT = max(0.0, min(windowSize, effectiveT - windowPosition)) / windowSize
|
|
let localT = 1.0 - windowFunction(t: windowT)
|
|
|
|
return localT
|
|
}
|
|
|
|
public final class PeerInfoCoverComponent: Component {
|
|
public enum Subject: Equatable {
|
|
case peer(EnginePeer)
|
|
case status(PeerEmojiStatus)
|
|
case custom(UIColor?, UIColor?, UIColor?, Int64?)
|
|
|
|
func colors(context: AccountContext, isDark: Bool) -> (UIColor, UIColor)? {
|
|
switch self {
|
|
case let .peer(peer):
|
|
if let colors = peer._asPeer().profileColor.flatMap({ context.peerNameColors.getProfile($0, dark: isDark) }) {
|
|
let backgroundColor = colors.main
|
|
let secondaryBackgroundColor = colors.secondary ?? colors.main
|
|
return (backgroundColor, secondaryBackgroundColor)
|
|
} else {
|
|
return nil
|
|
}
|
|
case let .status(status):
|
|
if case let .starGift(_, _, _, _, _, innerColor, outerColor, _, _) = status.content {
|
|
return (UIColor(rgb: UInt32(bitPattern: innerColor)), UIColor(rgb: UInt32(bitPattern: outerColor)))
|
|
}
|
|
return nil
|
|
case let .custom(color, secondColor, _, _):
|
|
if let color {
|
|
if let secondColor {
|
|
return (color, secondColor)
|
|
} else {
|
|
return (color, color)
|
|
}
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
var fileId: Int64? {
|
|
switch self {
|
|
case let .peer(peer):
|
|
return peer.profileBackgroundEmojiId
|
|
case let .status(status):
|
|
if case let .starGift(_, _, _, _, patternFileId, _, _, _, _) = status.content {
|
|
return patternFileId
|
|
}
|
|
return nil
|
|
case let .custom(_, _, _, fileId):
|
|
return fileId
|
|
}
|
|
}
|
|
}
|
|
public let context: AccountContext
|
|
public let subject: Subject?
|
|
public let files: [Int64: TelegramMediaFile]
|
|
public let isDark: Bool
|
|
public let avatarCenter: CGPoint
|
|
public let avatarScale: CGFloat
|
|
public let defaultHeight: CGFloat
|
|
public let gradientOnTop: Bool
|
|
public let avatarTransitionFraction: CGFloat
|
|
public let patternTransitionFraction: CGFloat
|
|
|
|
public init(
|
|
context: AccountContext,
|
|
subject: Subject?,
|
|
files: [Int64: TelegramMediaFile],
|
|
isDark: Bool,
|
|
avatarCenter: CGPoint,
|
|
avatarScale: CGFloat,
|
|
defaultHeight: CGFloat,
|
|
gradientOnTop: Bool = false,
|
|
avatarTransitionFraction: CGFloat,
|
|
patternTransitionFraction: CGFloat
|
|
) {
|
|
self.context = context
|
|
self.subject = subject
|
|
self.files = files
|
|
self.isDark = isDark
|
|
self.avatarCenter = avatarCenter
|
|
self.avatarScale = avatarScale
|
|
self.defaultHeight = defaultHeight
|
|
self.gradientOnTop = gradientOnTop
|
|
self.avatarTransitionFraction = avatarTransitionFraction
|
|
self.patternTransitionFraction = patternTransitionFraction
|
|
}
|
|
|
|
public static func ==(lhs: PeerInfoCoverComponent, rhs: PeerInfoCoverComponent) -> Bool {
|
|
if lhs.context !== rhs.context {
|
|
return false
|
|
}
|
|
if lhs.subject != rhs.subject {
|
|
return false
|
|
}
|
|
if lhs.files != rhs.files {
|
|
return false
|
|
}
|
|
if lhs.isDark != rhs.isDark {
|
|
return false
|
|
}
|
|
if lhs.avatarCenter != rhs.avatarCenter {
|
|
return false
|
|
}
|
|
if lhs.avatarScale != rhs.avatarScale {
|
|
return false
|
|
}
|
|
if lhs.defaultHeight != rhs.defaultHeight {
|
|
return false
|
|
}
|
|
if lhs.gradientOnTop != rhs.gradientOnTop {
|
|
return false
|
|
}
|
|
if lhs.avatarTransitionFraction != rhs.avatarTransitionFraction {
|
|
return false
|
|
}
|
|
if lhs.patternTransitionFraction != rhs.patternTransitionFraction {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
public final class View: UIView {
|
|
private let backgroundView: UIView
|
|
private let backgroundGradientLayer: SimpleGradientLayer
|
|
private let avatarBackgroundPatternContentsLayer: SimpleGradientLayer
|
|
private let avatarBackgroundPatternMaskLayer: SimpleLayer
|
|
private let avatarBackgroundGradientLayer: SimpleGradientLayer
|
|
private let backgroundPatternContainer: UIView
|
|
|
|
private var currentSize: CGSize?
|
|
private var component: PeerInfoCoverComponent?
|
|
private var state: EmptyComponentState?
|
|
|
|
private var patternContentsTarget: PatternContentsTarget?
|
|
private var avatarPatternContentLayers: [SimpleLayer] = []
|
|
private var patternFile: TelegramMediaFile?
|
|
private var patternFileDisposable: Disposable?
|
|
private var patternImageDisposable: Disposable?
|
|
|
|
override public init(frame: CGRect) {
|
|
self.backgroundView = UIView()
|
|
self.backgroundGradientLayer = SimpleGradientLayer()
|
|
|
|
self.avatarBackgroundGradientLayer = SimpleGradientLayer()
|
|
self.avatarBackgroundGradientLayer.opacity = 0.0
|
|
let baseAvatarGradientAlpha: CGFloat = 0.4
|
|
let numSteps = 6
|
|
self.avatarBackgroundGradientLayer.colors = (0 ..< numSteps).map { i in
|
|
let step: CGFloat = 1.0 - CGFloat(i) / CGFloat(numSteps - 1)
|
|
return UIColor(white: 1.0, alpha: baseAvatarGradientAlpha * pow(step, 2.0)).cgColor
|
|
}
|
|
self.avatarBackgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
|
|
self.avatarBackgroundGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
|
|
self.avatarBackgroundGradientLayer.type = .radial
|
|
|
|
self.avatarBackgroundPatternContentsLayer = SimpleGradientLayer()
|
|
self.avatarBackgroundPatternContentsLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
|
|
self.avatarBackgroundPatternContentsLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
|
|
self.avatarBackgroundPatternContentsLayer.type = .radial
|
|
|
|
self.avatarBackgroundPatternMaskLayer = SimpleLayer()
|
|
self.backgroundPatternContainer = UIView()
|
|
|
|
super.init(frame: frame)
|
|
|
|
self.clipsToBounds = true
|
|
|
|
self.addSubview(self.backgroundView)
|
|
self.layer.addSublayer(self.backgroundGradientLayer)
|
|
self.layer.addSublayer(self.avatarBackgroundGradientLayer)
|
|
|
|
self.avatarBackgroundPatternContentsLayer.mask = self.avatarBackgroundPatternMaskLayer
|
|
self.layer.addSublayer(self.avatarBackgroundPatternContentsLayer)
|
|
|
|
self.addSubview(self.backgroundPatternContainer)
|
|
|
|
self.layer.allowsGroupOpacity = true
|
|
self.backgroundView.layer.allowsGroupOpacity = true
|
|
}
|
|
|
|
required public init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
self.patternFileDisposable?.dispose()
|
|
self.patternImageDisposable?.dispose()
|
|
}
|
|
|
|
public func willAnimateIn() {
|
|
for layer in self.avatarPatternContentLayers {
|
|
layer.opacity = 0.0
|
|
}
|
|
}
|
|
|
|
public func animateIn() {
|
|
guard let _ = self.currentSize, let component = self.component else {
|
|
return
|
|
}
|
|
|
|
for layer in self.avatarPatternContentLayers {
|
|
layer.opacity = 1.0
|
|
layer.animatePosition(
|
|
from: component.avatarCenter,
|
|
to: layer.position,
|
|
duration: 0.4,
|
|
timingFunction: kCAMediaTimingFunctionSpring
|
|
)
|
|
}
|
|
}
|
|
|
|
public func animateTransition() {
|
|
if let gradientSnapshotLayer = self.backgroundGradientLayer.snapshotContentTree() {
|
|
let backgroundSnapshotLayer = SimpleLayer()
|
|
backgroundSnapshotLayer.allowsGroupOpacity = true
|
|
backgroundSnapshotLayer.backgroundColor = self.backgroundView.backgroundColor?.cgColor
|
|
backgroundSnapshotLayer.frame = self.backgroundView.frame
|
|
self.layer.insertSublayer(backgroundSnapshotLayer, above: self.backgroundGradientLayer)
|
|
|
|
backgroundSnapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
|
backgroundSnapshotLayer.removeFromSuperlayer()
|
|
})
|
|
gradientSnapshotLayer.frame = self.backgroundGradientLayer.convert(self.backgroundGradientLayer.bounds, to: self.backgroundView.layer)
|
|
backgroundSnapshotLayer.addSublayer(gradientSnapshotLayer)
|
|
}
|
|
for layer in self.avatarPatternContentLayers {
|
|
if let _ = layer.contents, let snapshot = layer.snapshotContentTree() {
|
|
layer.superlayer?.addSublayer(snapshot)
|
|
snapshot.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
|
snapshot.removeFromSuperlayer()
|
|
})
|
|
}
|
|
layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
|
}
|
|
let values: [NSNumber] = [1.0, 1.08, 1.0]
|
|
self.avatarBackgroundPatternContentsLayer.animateKeyframes(values: values, duration: 0.25, keyPath: "sublayerTransform.scale")
|
|
}
|
|
|
|
private func loadPatternFromFile() {
|
|
guard let component = self.component else {
|
|
return
|
|
}
|
|
guard let patternContentsTarget = self.patternContentsTarget else {
|
|
return
|
|
}
|
|
guard let patternFile = self.patternFile else {
|
|
return
|
|
}
|
|
|
|
if component.context.animationRenderer.loadFirstFrameSynchronously(target: patternContentsTarget, cache: component.context.animationCache, itemId: patternFile.resource.id.stringRepresentation, size: CGSize(width: 96, height: 96)) {
|
|
self.updatePatternLayerImages(animated: false)
|
|
} else {
|
|
let animated = self.patternContentsTarget?.contents == nil
|
|
self.patternImageDisposable = component.context.animationRenderer.loadFirstFrame(
|
|
target: patternContentsTarget,
|
|
cache: component.context.animationCache,
|
|
itemId: patternFile.resource.id.stringRepresentation,
|
|
size: CGSize(width: 96, height: 96),
|
|
fetch: animationCacheFetchFile(
|
|
postbox: component.context.account.postbox,
|
|
userLocation: .other,
|
|
userContentType: .sticker,
|
|
resource: .media(media: .standalone(media: patternFile), resource: patternFile.resource),
|
|
type: AnimationCacheAnimationType(file: patternFile),
|
|
keyframeOnly: false,
|
|
customColor: .white
|
|
),
|
|
completion: { [weak self] _, _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.updatePatternLayerImages(animated: animated)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
private func updatePatternLayerImages(animated: Bool) {
|
|
let image = self.patternContentsTarget?.contents
|
|
for patternContentLayer in self.avatarPatternContentLayers {
|
|
patternContentLayer.contents = image
|
|
|
|
if image != nil && animated {
|
|
patternContentLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
|
}
|
|
}
|
|
}
|
|
|
|
func update(component: PeerInfoCoverComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
|
let previousComponent = self.component
|
|
self.component = component
|
|
self.currentSize = availableSize
|
|
|
|
if previousComponent?.subject?.fileId != component.subject?.fileId {
|
|
if let fileId = component.subject?.fileId, fileId != 0 {
|
|
if self.patternContentsTarget == nil {
|
|
self.patternContentsTarget = PatternContentsTarget(imageUpdated: { [weak self] hadContents in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.updatePatternLayerImages(animated: !hadContents)
|
|
})
|
|
}
|
|
|
|
self.patternFile = nil
|
|
self.patternFileDisposable?.dispose()
|
|
self.patternFileDisposable = nil
|
|
self.patternImageDisposable?.dispose()
|
|
|
|
if let file = component.files[fileId] {
|
|
self.patternFile = file
|
|
self.loadPatternFromFile()
|
|
} else {
|
|
self.patternFileDisposable = (component.context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
|
|
|> deliverOnMainQueue).startStrict(next: { [weak self] files in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if let file = files[fileId] {
|
|
self.patternFile = file
|
|
self.loadPatternFromFile()
|
|
}
|
|
})
|
|
}
|
|
} else {
|
|
self.patternContentsTarget = nil
|
|
self.patternFileDisposable?.dispose()
|
|
self.patternFileDisposable = nil
|
|
self.patternFile = nil
|
|
self.updatePatternLayerImages(animated: false)
|
|
}
|
|
}
|
|
|
|
self.state = state
|
|
|
|
let backgroundColor: UIColor
|
|
let secondaryBackgroundColor: UIColor
|
|
|
|
if let subject = component.subject, let colors = subject.colors(context: component.context, isDark: component.isDark) {
|
|
backgroundColor = colors.0
|
|
secondaryBackgroundColor = colors.1
|
|
} else {
|
|
backgroundColor = .clear
|
|
secondaryBackgroundColor = .clear
|
|
}
|
|
|
|
let gradientWidth: CGFloat
|
|
let gradientHeight: CGFloat = component.defaultHeight
|
|
if case .custom = component.subject {
|
|
gradientWidth = gradientHeight
|
|
self.backgroundView.backgroundColor = backgroundColor
|
|
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: component.avatarCenter.y / gradientHeight)
|
|
self.backgroundGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
|
|
self.backgroundGradientLayer.type = .radial
|
|
self.backgroundGradientLayer.colors = [secondaryBackgroundColor.cgColor, backgroundColor.cgColor]
|
|
} else if case .status = component.subject {
|
|
gradientWidth = availableSize.width
|
|
self.backgroundView.backgroundColor = secondaryBackgroundColor
|
|
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: component.avatarCenter.y / gradientHeight)
|
|
self.backgroundGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
|
|
self.backgroundGradientLayer.type = .radial
|
|
self.backgroundGradientLayer.colors = [backgroundColor.cgColor, secondaryBackgroundColor.cgColor]
|
|
} else {
|
|
gradientWidth = availableSize.width
|
|
self.backgroundView.backgroundColor = secondaryBackgroundColor
|
|
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
|
|
self.backgroundGradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
|
|
self.backgroundGradientLayer.type = .axial
|
|
self.backgroundGradientLayer.colors = [backgroundColor.cgColor, secondaryBackgroundColor.cgColor]
|
|
}
|
|
self.backgroundGradientLayer.anchorPoint = CGPoint(x: 0.0, y: 1.0)
|
|
|
|
let backgroundGradientFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - gradientWidth) / 2.0), y: component.gradientOnTop ? 0.0 : availableSize.height - gradientHeight), size: CGSize(width: gradientWidth, height: gradientHeight))
|
|
if !transition.animation.isImmediate {
|
|
let previousPosition = self.backgroundGradientLayer.position
|
|
let updatedPosition = CGPoint(x: backgroundGradientFrame.minX, y: backgroundGradientFrame.maxY)
|
|
self.backgroundGradientLayer.bounds = CGRect(origin: CGPoint(), size: backgroundGradientFrame.size)
|
|
self.backgroundGradientLayer.position = updatedPosition
|
|
transition.containedViewLayoutTransition.animatePositionAdditive(layer: self.backgroundGradientLayer, offset: CGPoint(x: previousPosition.x - updatedPosition.x, y: previousPosition.y - updatedPosition.y))
|
|
} else {
|
|
self.backgroundGradientLayer.frame = backgroundGradientFrame
|
|
}
|
|
|
|
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -1000.0 + availableSize.height), size: CGSize(width: availableSize.width, height: 1000.0))
|
|
transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundView, frame: backgroundFrame)
|
|
|
|
/*let avatarBackgroundPatternContainerFrame = CGSize(width: 0.0, height: 0.0).centered(around: component.avatarCenter)
|
|
transition.containedViewLayoutTransition.updateFrameAdditive(view: self.avatarBackgroundPatternContainer, frame: avatarBackgroundPatternContainerFrame)
|
|
transition.containedViewLayoutTransition.updateSublayerTransformScaleAdditive(layer: self.avatarBackgroundPatternContainer.layer, scale: component.avatarScale)*/
|
|
|
|
//transition.setFrame(view: self.avatarBackgroundPatternView, frame: CGSize(width: 200.0, height: 200.0).centered(around: CGPoint()))
|
|
|
|
let avatarPatternFrame = CGSize(width: 380.0, height: floor(component.defaultHeight * 1.0)).centered(around: component.avatarCenter)
|
|
transition.setFrame(layer: self.avatarBackgroundPatternContentsLayer, frame: avatarPatternFrame)
|
|
|
|
if case let .custom(_, _, patternColor, _) = component.subject, let patternColor {
|
|
self.avatarBackgroundPatternContentsLayer.compositingFilter = nil
|
|
self.avatarBackgroundPatternContentsLayer.colors = [
|
|
patternColor.withAlphaComponent(0.6).cgColor,
|
|
patternColor.withAlphaComponent(0.0).cgColor
|
|
]
|
|
} else if case let .status(status) = component.subject, case let .starGift(_, _, _, _, _, _, _, patternColorValue, _) = status.content {
|
|
let patternColor = UIColor(rgb: UInt32(bitPattern: patternColorValue))
|
|
self.avatarBackgroundPatternContentsLayer.compositingFilter = nil
|
|
self.avatarBackgroundPatternContentsLayer.colors = [
|
|
patternColor.withAlphaComponent(0.6).cgColor,
|
|
patternColor.withAlphaComponent(0.0).cgColor
|
|
]
|
|
} else {
|
|
if component.subject?.colors(context: component.context, isDark: component.isDark) != nil {
|
|
self.avatarBackgroundPatternContentsLayer.compositingFilter = "overlayBlendMode"
|
|
self.avatarBackgroundPatternContentsLayer.colors = [
|
|
UIColor(white: 0.0, alpha: 0.6).cgColor,
|
|
UIColor(white: 0.0, alpha: 0.0).cgColor
|
|
]
|
|
|
|
} else {
|
|
self.avatarBackgroundPatternContentsLayer.compositingFilter = nil
|
|
let baseWhite: CGFloat = component.isDark ? 0.5 : 0.3
|
|
self.avatarBackgroundPatternContentsLayer.colors = [
|
|
UIColor(white: baseWhite, alpha: 0.6).cgColor,
|
|
UIColor(white: baseWhite, alpha: 0.0).cgColor
|
|
]
|
|
}
|
|
}
|
|
|
|
switch component.subject {
|
|
case .custom, .status:
|
|
self.avatarBackgroundGradientLayer.isHidden = true
|
|
default:
|
|
self.avatarBackgroundGradientLayer.isHidden = component.subject?.colors(context: component.context, isDark: component.isDark) == nil
|
|
}
|
|
transition.setFrame(layer: self.avatarBackgroundGradientLayer, frame: CGSize(width: 300.0, height: 300.0).centered(around: component.avatarCenter))
|
|
transition.setAlpha(layer: self.avatarBackgroundGradientLayer, alpha: 1.0 - component.avatarTransitionFraction)
|
|
|
|
let backgroundPatternContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height), size: CGSize(width: availableSize.width, height: 0.0))
|
|
transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundPatternContainer, frame: backgroundPatternContainerFrame)
|
|
transition.setAlpha(view: self.backgroundPatternContainer, alpha: component.patternTransitionFraction)
|
|
|
|
var baseDistance: CGFloat = 72.0
|
|
var baseRowDistance: CGFloat = 28.0
|
|
var baseItemSize: CGFloat = 26.0
|
|
if availableSize.width <= 60.0 {
|
|
baseDistance *= 0.35
|
|
baseRowDistance *= 0.3
|
|
baseItemSize *= 0.4
|
|
} else if availableSize.width < 150.0 {
|
|
baseDistance *= 0.6
|
|
baseRowDistance *= 0.6
|
|
baseItemSize *= 0.83
|
|
}
|
|
|
|
var avatarBackgroundPatternLayerCount = 0
|
|
let lokiRng = LokiRng(seed0: 123, seed1: 0, seed2: 0)
|
|
let numRows = 5
|
|
for row in 0 ..< numRows {
|
|
let avatarPatternCount: Int = 7
|
|
let avatarPatternAngleSpan: CGFloat = CGFloat.pi * 2.0 / CGFloat(avatarPatternCount - 1)
|
|
|
|
for i in 0 ..< avatarPatternCount - 1 {
|
|
let baseItemDistance: CGFloat = baseDistance + CGFloat(row) * baseRowDistance
|
|
|
|
let itemDistanceFraction = max(0.0, min(1.0, baseItemDistance / (baseDistance * 2.0)))
|
|
let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction, t: itemDistanceFraction, reverse: false)
|
|
let itemDistance = baseItemDistance * (1.0 - itemScaleFraction) + 20.0 * itemScaleFraction
|
|
|
|
var itemAngle: CGFloat
|
|
itemAngle = -CGFloat.pi * 0.5 + CGFloat(i) * avatarPatternAngleSpan
|
|
if row % 2 != 0 {
|
|
itemAngle += avatarPatternAngleSpan * 0.5
|
|
}
|
|
let itemPosition = CGPoint(x: avatarPatternFrame.width * 0.5 + cos(itemAngle) * itemDistance, y: avatarPatternFrame.height * 0.5 + sin(itemAngle) * itemDistance)
|
|
|
|
var itemScale: CGFloat
|
|
itemScale = 0.7 + CGFloat(lokiRng.next()) * (1.0 - 0.7)
|
|
|
|
let itemSize: CGFloat = floor(baseItemSize * itemScale)
|
|
let itemFrame = CGSize(width: itemSize, height: itemSize).centered(around: itemPosition)
|
|
|
|
let itemLayer: SimpleLayer
|
|
if self.avatarPatternContentLayers.count > avatarBackgroundPatternLayerCount {
|
|
itemLayer = self.avatarPatternContentLayers[avatarBackgroundPatternLayerCount]
|
|
} else {
|
|
itemLayer = SimpleLayer()
|
|
itemLayer.contents = self.patternContentsTarget?.contents
|
|
self.avatarBackgroundPatternMaskLayer.addSublayer(itemLayer)
|
|
self.avatarPatternContentLayers.append(itemLayer)
|
|
}
|
|
|
|
itemLayer.frame = itemFrame
|
|
itemLayer.layerTintColor = UIColor(white: 0.0, alpha: 0.8).cgColor
|
|
transition.setAlpha(layer: itemLayer, alpha: 1.0 - itemScaleFraction)
|
|
|
|
avatarBackgroundPatternLayerCount += 1
|
|
}
|
|
}
|
|
if avatarBackgroundPatternLayerCount > self.avatarPatternContentLayers.count {
|
|
for i in avatarBackgroundPatternLayerCount ..< self.avatarPatternContentLayers.count {
|
|
self.avatarPatternContentLayers[i].removeFromSuperlayer()
|
|
}
|
|
self.avatarPatternContentLayers.removeSubrange(avatarBackgroundPatternLayerCount ..< self.avatarPatternContentLayers.count)
|
|
}
|
|
|
|
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, state: state, environment: environment, transition: transition)
|
|
}
|
|
}
|