mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
1547 lines
68 KiB
Swift
1547 lines
68 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import GradientBackground
|
|
import TelegramPresentationData
|
|
import TelegramCore
|
|
import AccountContext
|
|
import SwiftSignalKit
|
|
import WallpaperResources
|
|
import FastBlur
|
|
import Svg
|
|
import GZip
|
|
import AppBundle
|
|
import AnimatedStickerNode
|
|
import TelegramAnimatedStickerNode
|
|
import HierarchyTrackingLayer
|
|
|
|
private let motionAmount: CGFloat = 32.0
|
|
|
|
private func generateBlurredContents(image: UIImage, dimColor: UIColor?) -> UIImage? {
|
|
let size = image.size.aspectFitted(CGSize(width: 64.0, height: 64.0))
|
|
guard let context = DrawingContext(size: size, scale: 1.0, opaque: true, clear: false) else {
|
|
return nil
|
|
}
|
|
context.withFlippedContext { c in
|
|
c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size))
|
|
}
|
|
|
|
telegramFastBlurMore(Int32(context.size.width), Int32(context.size.height), Int32(context.bytesPerRow), context.bytes)
|
|
telegramFastBlurMore(Int32(context.size.width), Int32(context.size.height), Int32(context.bytesPerRow), context.bytes)
|
|
|
|
adjustSaturationInContext(context: context, saturation: 1.7)
|
|
|
|
if let dimColor {
|
|
context.withFlippedContext { c in
|
|
c.setFillColor(dimColor.cgColor)
|
|
c.fill(CGRect(origin: CGPoint(), size: size))
|
|
}
|
|
}
|
|
|
|
return context.generateImage()
|
|
}
|
|
|
|
public enum WallpaperBubbleType {
|
|
case incoming
|
|
case outgoing
|
|
case free
|
|
}
|
|
|
|
public protocol WallpaperBubbleBackgroundNode: ASDisplayNode {
|
|
var frame: CGRect { get set }
|
|
|
|
var implicitContentUpdate: Bool { get set }
|
|
|
|
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition)
|
|
func update(rect: CGRect, within containerSize: CGSize, delay: Double, transition: ContainedViewLayoutTransition)
|
|
func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition)
|
|
func update(rect: CGRect, within containerSize: CGSize, animator: ControlledTransitionAnimator)
|
|
func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double)
|
|
func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat)
|
|
}
|
|
|
|
public enum WallpaperDisplayMode {
|
|
case aspectFill
|
|
case aspectFit
|
|
case halfAspectFill
|
|
|
|
var argumentsDisplayMode: PatternWallpaperArguments.DisplayMode {
|
|
switch self {
|
|
case .aspectFill:
|
|
return .aspectFill
|
|
case .aspectFit:
|
|
return .aspectFit
|
|
case .halfAspectFill:
|
|
return .halfAspectFill
|
|
}
|
|
}
|
|
}
|
|
|
|
public protocol WallpaperBackgroundNode: ASDisplayNode {
|
|
var isReady: Signal<Bool, NoError> { get }
|
|
var rotation: CGFloat { get set }
|
|
|
|
func update(wallpaper: TelegramWallpaper)
|
|
func _internalUpdateIsSettingUpWallpaper()
|
|
func updateLayout(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition)
|
|
func updateIsLooping(_ isLooping: Bool)
|
|
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool)
|
|
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners)
|
|
func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool
|
|
func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode?
|
|
func makeFreeBackground() -> PortalView?
|
|
|
|
func hasExtraBubbleBackground() -> Bool
|
|
|
|
func makeDimmedNode() -> ASDisplayNode?
|
|
}
|
|
|
|
private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOverlayLayer {
|
|
enum SoftlightMode {
|
|
case whileAnimating
|
|
case always
|
|
case never
|
|
}
|
|
|
|
var fillWithColorUntilLoaded: UIColor? {
|
|
didSet {
|
|
if self.fillWithColorUntilLoaded != oldValue {
|
|
if let fillWithColorUntilLoaded = self.fillWithColorUntilLoaded {
|
|
if self.currentContents == nil {
|
|
self.backgroundColor = fillWithColorUntilLoaded.cgColor
|
|
} else {
|
|
self.backgroundColor = nil
|
|
}
|
|
} else {
|
|
self.backgroundColor = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var patternContentImage: UIImage? {
|
|
didSet {
|
|
if self.patternContentImage !== oldValue {
|
|
self.updateComposedImage()
|
|
self.updateContents()
|
|
}
|
|
}
|
|
}
|
|
|
|
var composedContentImage: UIImage? {
|
|
didSet {
|
|
if self.composedContentImage !== oldValue {
|
|
self.updateContents()
|
|
}
|
|
}
|
|
}
|
|
|
|
var softlightMode: SoftlightMode = .whileAnimating {
|
|
didSet {
|
|
if self.softlightMode != oldValue {
|
|
self.updateFilters()
|
|
}
|
|
}
|
|
}
|
|
|
|
var isAnimating: Bool = false {
|
|
didSet {
|
|
if self.isAnimating != oldValue {
|
|
self.updateFilters()
|
|
}
|
|
}
|
|
}
|
|
|
|
private var isUsingSoftlight: Bool = false
|
|
private var useFilter: Bool = false
|
|
|
|
var suspendCompositionUpdates: Bool = false
|
|
private var needsCompositionUpdate: Bool = false
|
|
|
|
private func updateFilters() {
|
|
let useSoftlight: Bool
|
|
let useFilter: Bool
|
|
switch self.softlightMode {
|
|
case .whileAnimating:
|
|
useSoftlight = self.isAnimating
|
|
useFilter = useSoftlight
|
|
case .always:
|
|
useSoftlight = true
|
|
useFilter = useSoftlight
|
|
case .never:
|
|
useSoftlight = true
|
|
useFilter = false
|
|
}
|
|
if self.isUsingSoftlight != useSoftlight || self.useFilter != useFilter {
|
|
self.isUsingSoftlight = useSoftlight
|
|
self.useFilter = useFilter
|
|
|
|
if self.isUsingSoftlight && self.useFilter {
|
|
self.compositingFilter = "softLightBlendMode"
|
|
} else {
|
|
self.compositingFilter = nil
|
|
}
|
|
|
|
self.updateContents()
|
|
self.updateOpacity()
|
|
}
|
|
}
|
|
|
|
private var allowSettingContents: Bool = false
|
|
private var currentContents: UIImage?
|
|
|
|
override var contents: Any? {
|
|
get {
|
|
return super.contents
|
|
} set(value) {
|
|
if self.allowSettingContents {
|
|
super.contents = value
|
|
} else {
|
|
assert(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var allowSettingOpacity: Bool = false
|
|
var compositionOpacity: Float = 1.0 {
|
|
didSet {
|
|
if self.compositionOpacity != oldValue {
|
|
self.updateOpacity()
|
|
self.updateComposedImage()
|
|
}
|
|
}
|
|
}
|
|
|
|
override var opacity: Float {
|
|
get {
|
|
return super.opacity
|
|
} set(value) {
|
|
if self.allowSettingOpacity {
|
|
super.opacity = value
|
|
} else {
|
|
assert(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var compositionData: (size: CGSize, backgroundImage: UIImage, backgroundImageHash: String)?
|
|
|
|
func updateCompositionData(size: CGSize, backgroundImage: UIImage, backgroundImageHash: String) {
|
|
if self.compositionData?.size == size && self.compositionData?.backgroundImage === backgroundImage {
|
|
return
|
|
}
|
|
self.compositionData = (size, backgroundImage, backgroundImageHash)
|
|
|
|
self.updateComposedImage()
|
|
}
|
|
|
|
func updateCompositionIfNeeded() {
|
|
if self.needsCompositionUpdate {
|
|
self.needsCompositionUpdate = false
|
|
self.updateComposedImage()
|
|
}
|
|
}
|
|
|
|
private static var cachedComposedImage: (size: CGSize, patternContentImage: UIImage, backgroundImageHash: String, image: UIImage)?
|
|
|
|
private func updateComposedImage() {
|
|
switch self.softlightMode {
|
|
case .always, .never:
|
|
return
|
|
default:
|
|
break
|
|
}
|
|
|
|
if self.suspendCompositionUpdates {
|
|
self.needsCompositionUpdate = true
|
|
return
|
|
}
|
|
|
|
guard let (size, backgroundImage, backgroundImageHash) = self.compositionData, let patternContentImage = self.patternContentImage else {
|
|
return
|
|
}
|
|
|
|
if let cachedComposedImage = EffectImageLayer.cachedComposedImage, cachedComposedImage.size == size, cachedComposedImage.backgroundImageHash == backgroundImageHash, cachedComposedImage.patternContentImage === patternContentImage {
|
|
self.composedContentImage = cachedComposedImage.image
|
|
return
|
|
}
|
|
|
|
#if DEBUG
|
|
let startTime = CFAbsoluteTimeGetCurrent()
|
|
#endif
|
|
|
|
let composedContentImage = generateImage(size, contextGenerator: { size, context in
|
|
context.draw(backgroundImage.cgImage!, in: CGRect(origin: CGPoint(), size: size))
|
|
context.setBlendMode(.softLight)
|
|
context.setAlpha(CGFloat(self.compositionOpacity))
|
|
context.draw(patternContentImage.cgImage!, in: CGRect(origin: CGPoint(), size: size))
|
|
}, opaque: true, scale: min(UIScreenScale, patternContentImage.scale))
|
|
self.composedContentImage = composedContentImage
|
|
|
|
#if DEBUG
|
|
print("Wallpaper composed image updated in \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
|
|
#endif
|
|
|
|
if self.softlightMode == .whileAnimating, let composedContentImage = composedContentImage {
|
|
EffectImageLayer.cachedComposedImage = (size, patternContentImage, backgroundImageHash, composedContentImage)
|
|
}
|
|
}
|
|
|
|
private func updateContents() {
|
|
var contents: UIImage?
|
|
|
|
if self.isUsingSoftlight {
|
|
contents = self.patternContentImage
|
|
} else {
|
|
contents = self.composedContentImage
|
|
}
|
|
|
|
if self.currentContents !== contents {
|
|
self.currentContents = contents
|
|
|
|
self.allowSettingContents = true
|
|
self.contents = contents?.cgImage
|
|
self.allowSettingContents = false
|
|
|
|
self.backgroundColor = nil
|
|
}
|
|
}
|
|
|
|
private func updateOpacity() {
|
|
if self.isUsingSoftlight {
|
|
self.allowSettingOpacity = true
|
|
self.opacity = self.compositionOpacity
|
|
self.allowSettingOpacity = false
|
|
self.isOpaque = false
|
|
} else {
|
|
self.allowSettingOpacity = true
|
|
self.opacity = 1.0
|
|
self.allowSettingOpacity = false
|
|
self.isOpaque = true
|
|
}
|
|
}
|
|
}
|
|
|
|
final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode {
|
|
final class BubbleBackgroundNodeImpl: ASDisplayNode, WallpaperBubbleBackgroundNode {
|
|
var implicitContentUpdate: Bool = true
|
|
|
|
private let bubbleType: WallpaperBubbleType
|
|
private let contentNode: ASImageNode
|
|
|
|
private var cleanWallpaperNode: ASDisplayNode?
|
|
private var gradientWallpaperNode: GradientBackgroundNode.CloneNode?
|
|
private var overlayNode: ASDisplayNode?
|
|
private weak var backgroundNode: WallpaperBackgroundNodeImpl?
|
|
private var index: SparseBag<BubbleBackgroundNodeImpl>.Index?
|
|
|
|
private var currentLayout: (rect: CGRect, containerSize: CGSize)?
|
|
|
|
override var frame: CGRect {
|
|
didSet {
|
|
if oldValue.size != self.bounds.size {
|
|
if self.implicitContentUpdate {
|
|
self.contentNode.frame = self.bounds
|
|
if let cleanWallpaperNode = self.cleanWallpaperNode {
|
|
cleanWallpaperNode.frame = self.bounds
|
|
}
|
|
if let gradientWallpaperNode = self.gradientWallpaperNode {
|
|
gradientWallpaperNode.frame = self.bounds
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
init(backgroundNode: WallpaperBackgroundNodeImpl, bubbleType: WallpaperBubbleType) {
|
|
self.backgroundNode = backgroundNode
|
|
self.bubbleType = bubbleType
|
|
|
|
self.contentNode = ASImageNode()
|
|
self.contentNode.displaysAsynchronously = false
|
|
self.contentNode.isUserInteractionEnabled = false
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.contentNode)
|
|
|
|
self.index = backgroundNode.bubbleBackgroundNodeReferences.add(BubbleBackgroundNodeReference(node: self))
|
|
}
|
|
|
|
deinit {
|
|
if let index = self.index, let backgroundNode = self.backgroundNode {
|
|
backgroundNode.bubbleBackgroundNodeReferences.remove(index)
|
|
}
|
|
}
|
|
|
|
func updateContents() {
|
|
guard let backgroundNode = self.backgroundNode else {
|
|
return
|
|
}
|
|
|
|
var overlayColor: UIColor?
|
|
|
|
if let bubbleTheme = backgroundNode.bubbleTheme, let bubbleCorners = backgroundNode.bubbleCorners {
|
|
let wallpaper = backgroundNode.wallpaper ?? bubbleTheme.chat.defaultWallpaper
|
|
|
|
let graphics = PresentationResourcesChat.principalGraphics(theme: bubbleTheme, wallpaper: wallpaper, bubbleCorners: bubbleCorners)
|
|
|
|
var needsCleanBackground = false
|
|
switch self.bubbleType {
|
|
case .incoming:
|
|
self.contentNode.image = graphics.incomingBubbleGradientImage
|
|
if graphics.incomingBubbleGradientImage == nil {
|
|
self.contentNode.backgroundColor = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill[0]
|
|
} else {
|
|
self.contentNode.backgroundColor = nil
|
|
}
|
|
needsCleanBackground = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 })
|
|
case .outgoing:
|
|
if backgroundNode.outgoingBubbleGradientBackgroundNode != nil {
|
|
self.contentNode.image = nil
|
|
self.contentNode.backgroundColor = nil
|
|
} else {
|
|
self.contentNode.image = graphics.outgoingBubbleGradientImage
|
|
if graphics.outgoingBubbleGradientImage == nil {
|
|
self.contentNode.backgroundColor = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill[0]
|
|
} else {
|
|
self.contentNode.backgroundColor = nil
|
|
}
|
|
needsCleanBackground = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 })
|
|
}
|
|
case .free:
|
|
self.contentNode.image = nil
|
|
self.contentNode.backgroundColor = nil
|
|
needsCleanBackground = true
|
|
|
|
//if wallpaper.isBuiltin {
|
|
overlayColor = selectDateFillStaticColor(theme: bubbleTheme, wallpaper: wallpaper)
|
|
//}
|
|
}
|
|
|
|
var isInvertedGradient = false
|
|
var hasComplexGradient = false
|
|
switch wallpaper {
|
|
case let .file(file):
|
|
hasComplexGradient = file.settings.colors.count >= 3
|
|
if let intensity = file.settings.intensity, intensity < 0 {
|
|
isInvertedGradient = true
|
|
}
|
|
case let .gradient(gradient):
|
|
hasComplexGradient = gradient.colors.count >= 3
|
|
default:
|
|
break
|
|
}
|
|
|
|
var needsGradientBackground = false
|
|
var needsWallpaperBackground = false
|
|
|
|
if isInvertedGradient {
|
|
switch self.bubbleType {
|
|
case .free:
|
|
self.contentNode.backgroundColor = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill[0]
|
|
// needsCleanBackground = false
|
|
case .incoming, .outgoing:
|
|
break
|
|
}
|
|
}
|
|
|
|
if needsCleanBackground {
|
|
if hasComplexGradient {
|
|
needsGradientBackground = backgroundNode.gradientBackgroundNode != nil
|
|
} else {
|
|
needsWallpaperBackground = true
|
|
}
|
|
}
|
|
|
|
var gradientBackgroundSource: GradientBackgroundNode? = backgroundNode.gradientBackgroundNode
|
|
|
|
if case .outgoing = self.bubbleType {
|
|
if let outgoingBubbleGradientBackgroundNode = backgroundNode.outgoingBubbleGradientBackgroundNode {
|
|
gradientBackgroundSource = outgoingBubbleGradientBackgroundNode
|
|
needsWallpaperBackground = false
|
|
needsGradientBackground = true
|
|
}
|
|
}
|
|
|
|
if needsWallpaperBackground {
|
|
if self.cleanWallpaperNode == nil {
|
|
let cleanWallpaperNode = ASImageNode()
|
|
cleanWallpaperNode.displaysAsynchronously = false
|
|
self.cleanWallpaperNode = cleanWallpaperNode
|
|
cleanWallpaperNode.frame = self.bounds
|
|
self.insertSubnode(cleanWallpaperNode, at: 0)
|
|
}
|
|
if let blurredBackgroundContents = backgroundNode.blurredBackgroundContents {
|
|
self.cleanWallpaperNode?.contents = blurredBackgroundContents.cgImage
|
|
self.cleanWallpaperNode?.backgroundColor = backgroundNode.contentNode.backgroundColor
|
|
} else {
|
|
self.cleanWallpaperNode?.contents = backgroundNode.contentNode.contents
|
|
self.cleanWallpaperNode?.backgroundColor = backgroundNode.contentNode.backgroundColor
|
|
}
|
|
} else {
|
|
if let cleanWallpaperNode = self.cleanWallpaperNode {
|
|
self.cleanWallpaperNode = nil
|
|
cleanWallpaperNode.removeFromSupernode()
|
|
}
|
|
}
|
|
|
|
if needsGradientBackground, let gradientBackgroundNode = gradientBackgroundSource {
|
|
if self.gradientWallpaperNode == nil {
|
|
let gradientWallpaperNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode)
|
|
gradientWallpaperNode.frame = self.bounds
|
|
self.gradientWallpaperNode = gradientWallpaperNode
|
|
self.insertSubnode(gradientWallpaperNode, at: 0)
|
|
}
|
|
} else {
|
|
if let gradientWallpaperNode = self.gradientWallpaperNode {
|
|
self.gradientWallpaperNode = nil
|
|
gradientWallpaperNode.removeFromSupernode()
|
|
}
|
|
}
|
|
} else {
|
|
self.contentNode.image = nil
|
|
if let cleanWallpaperNode = self.cleanWallpaperNode {
|
|
self.cleanWallpaperNode = nil
|
|
cleanWallpaperNode.removeFromSupernode()
|
|
}
|
|
}
|
|
|
|
if let overlayColor {
|
|
let overlayNode: ASDisplayNode
|
|
if let current = self.overlayNode {
|
|
overlayNode = current
|
|
} else {
|
|
overlayNode = ASDisplayNode()
|
|
self.overlayNode = overlayNode
|
|
self.addSubnode(overlayNode)
|
|
}
|
|
overlayNode.backgroundColor = overlayColor
|
|
} else if let overlayNode = self.overlayNode {
|
|
self.overlayNode = nil
|
|
overlayNode.removeFromSupernode()
|
|
}
|
|
|
|
if let (rect, containerSize) = self.currentLayout {
|
|
self.update(rect: rect, within: containerSize)
|
|
}
|
|
}
|
|
|
|
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) {
|
|
self.update(rect: rect, within: containerSize, delay: 0.0, transition: transition)
|
|
}
|
|
|
|
func update(rect: CGRect, within containerSize: CGSize, delay: Double = 0.0, transition: ContainedViewLayoutTransition = .immediate) {
|
|
self.currentLayout = (rect, containerSize)
|
|
|
|
let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height))
|
|
|
|
transition.updateFrame(layer: self.contentNode.layer, frame: self.bounds, delay: delay)
|
|
transition.animateView(delay: delay) {
|
|
self.contentNode.layer.contentsRect = shiftedContentsRect
|
|
}
|
|
if let cleanWallpaperNode = self.cleanWallpaperNode {
|
|
transition.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds, delay: delay)
|
|
transition.animateView(delay: delay) {
|
|
cleanWallpaperNode.layer.contentsRect = shiftedContentsRect
|
|
}
|
|
}
|
|
if let gradientWallpaperNode = self.gradientWallpaperNode {
|
|
transition.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds, delay: delay)
|
|
transition.animateView(delay: delay) {
|
|
gradientWallpaperNode.layer.contentsRect = shiftedContentsRect
|
|
}
|
|
}
|
|
if let overlayNode = self.overlayNode {
|
|
transition.updateFrame(layer: overlayNode.layer, frame: self.bounds, delay: delay)
|
|
}
|
|
}
|
|
|
|
func update(rect: CGRect, within containerSize: CGSize, animator: ControlledTransitionAnimator) {
|
|
self.currentLayout = (rect, containerSize)
|
|
|
|
let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height))
|
|
|
|
animator.updateFrame(layer: self.contentNode.layer, frame: self.bounds, completion: nil)
|
|
animator.updateContentsRect(layer: self.contentNode.layer, contentsRect: shiftedContentsRect, completion: nil)
|
|
if let cleanWallpaperNode = self.cleanWallpaperNode {
|
|
animator.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds, completion: nil)
|
|
animator.updateContentsRect(layer: cleanWallpaperNode.layer, contentsRect: shiftedContentsRect, completion: nil)
|
|
}
|
|
if let gradientWallpaperNode = self.gradientWallpaperNode {
|
|
animator.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds, completion: nil)
|
|
animator.updateContentsRect(layer: gradientWallpaperNode.layer, contentsRect: shiftedContentsRect, completion: nil)
|
|
}
|
|
if let overlayNode = self.overlayNode {
|
|
animator.updateFrame(layer: overlayNode.layer, frame: self.bounds, completion: nil)
|
|
}
|
|
}
|
|
|
|
func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) {
|
|
self.currentLayout = (rect, containerSize)
|
|
|
|
let shiftedContentsRect = CGRect(origin: CGPoint(x: rect.minX / containerSize.width, y: rect.minY / containerSize.height), size: CGSize(width: rect.width / containerSize.width, height: rect.height / containerSize.height))
|
|
|
|
transition.updateFrame(layer: self.contentNode.layer, frame: self.bounds)
|
|
self.contentNode.layer.contentsRect = shiftedContentsRect
|
|
if let cleanWallpaperNode = self.cleanWallpaperNode {
|
|
transition.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds)
|
|
cleanWallpaperNode.layer.contentsRect = shiftedContentsRect
|
|
}
|
|
if let gradientWallpaperNode = self.gradientWallpaperNode {
|
|
transition.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds)
|
|
gradientWallpaperNode.layer.contentsRect = shiftedContentsRect
|
|
}
|
|
if let overlayNode = self.overlayNode {
|
|
transition.updateFrame(layer: overlayNode.layer, frame: self.bounds)
|
|
}
|
|
}
|
|
|
|
func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) {
|
|
guard let (_, containerSize) = self.currentLayout else {
|
|
return
|
|
}
|
|
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: animationCurve)
|
|
|
|
let scaledOffset = CGPoint(x: value.x / containerSize.width, y: value.y / containerSize.height)
|
|
transition.animateContentsRectPositionAdditive(layer: self.contentNode.layer, offset: scaledOffset)
|
|
|
|
if let cleanWallpaperNode = self.cleanWallpaperNode {
|
|
transition.animateContentsRectPositionAdditive(layer: cleanWallpaperNode.layer, offset: scaledOffset)
|
|
}
|
|
if let gradientWallpaperNode = self.gradientWallpaperNode {
|
|
transition.animateContentsRectPositionAdditive(layer: gradientWallpaperNode.layer, offset: scaledOffset)
|
|
}
|
|
}
|
|
|
|
func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) {
|
|
guard let (_, containerSize) = self.currentLayout else {
|
|
return
|
|
}
|
|
|
|
let scaledOffset = CGPoint(x: 0.0, y: -value / containerSize.height)
|
|
|
|
self.contentNode.layer.animateSpring(from: NSValue(cgPoint: scaledOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.position", duration: duration, initialVelocity: 0.0, damping: damping, additive: true)
|
|
if let cleanWallpaperNode = self.cleanWallpaperNode {
|
|
cleanWallpaperNode.layer.animateSpring(from: NSValue(cgPoint: scaledOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.position", duration: duration, initialVelocity: 0.0, damping: damping, additive: true)
|
|
}
|
|
if let gradientWallpaperNode = self.gradientWallpaperNode {
|
|
gradientWallpaperNode.layer.animateSpring(from: NSValue(cgPoint: scaledOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.position", duration: duration, initialVelocity: 0.0, damping: damping, additive: true)
|
|
}
|
|
}
|
|
}
|
|
|
|
final class BubbleBackgroundPortalNodeImpl: ASDisplayNode, WallpaperBubbleBackgroundNode {
|
|
private let portalView: PortalView
|
|
|
|
var implicitContentUpdate: Bool = true
|
|
|
|
init(portalView: PortalView) {
|
|
self.portalView = portalView
|
|
|
|
super.init()
|
|
|
|
self.view.addSubview(portalView.view)
|
|
self.clipsToBounds = true
|
|
}
|
|
|
|
deinit {
|
|
}
|
|
|
|
func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) {
|
|
if self.portalView.view.bounds.size != rect.size {
|
|
transition.updateFrame(view: self.portalView.view, frame: CGRect(origin: CGPoint(), size: rect.size))
|
|
}
|
|
}
|
|
|
|
func update(rect: CGRect, within containerSize: CGSize, delay: Double = 0.0, transition: ContainedViewLayoutTransition = .immediate) {
|
|
if self.portalView.view.bounds.size != rect.size {
|
|
transition.updateFrame(view: self.portalView.view, frame: CGRect(origin: CGPoint(), size: rect.size), delay: delay)
|
|
}
|
|
}
|
|
|
|
func update(rect: CGRect, within containerSize: CGSize, animator: ControlledTransitionAnimator) {
|
|
if self.portalView.view.bounds.size != rect.size {
|
|
animator.updateFrame(layer: self.portalView.view.layer, frame: CGRect(origin: CGPoint(), size: rect.size), completion: nil)
|
|
}
|
|
}
|
|
|
|
func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) {
|
|
if self.portalView.view.bounds.size != rect.size {
|
|
transition.updateFrame(layer: self.portalView.view.layer, frame: CGRect(origin: CGPoint(), size: rect.size))
|
|
}
|
|
}
|
|
|
|
func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) {
|
|
}
|
|
|
|
func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) {
|
|
}
|
|
}
|
|
|
|
private final class BubbleBackgroundNodeReference {
|
|
weak var node: BubbleBackgroundNodeImpl?
|
|
|
|
init(node: BubbleBackgroundNodeImpl) {
|
|
self.node = node
|
|
}
|
|
}
|
|
|
|
private let context: AccountContext
|
|
private let useSharedAnimationPhase: Bool
|
|
|
|
private let contentNode: ASDisplayNode
|
|
|
|
private var blurredBackgroundContents: UIImage?
|
|
|
|
private var freeBackgroundPortalSourceView: PortalSourceView?
|
|
private var freeBackgroundNode: WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl? {
|
|
didSet {
|
|
if self.freeBackgroundNode !== oldValue {
|
|
if let oldValue {
|
|
oldValue.view.removeFromSuperview()
|
|
}
|
|
if let freeBackgroundNode = self.freeBackgroundNode, let freeBackgroundPortalSourceView = self.freeBackgroundPortalSourceView {
|
|
freeBackgroundPortalSourceView.addSubview(freeBackgroundNode.view)
|
|
freeBackgroundNode.frame = CGRect(origin: CGPoint(), size: freeBackgroundPortalSourceView.bounds.size)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var incomingBackgroundPortalSourceView: PortalSourceView?
|
|
private var incomingBackgroundNode: WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl? {
|
|
didSet {
|
|
if self.incomingBackgroundNode !== oldValue {
|
|
if let oldValue {
|
|
oldValue.view.removeFromSuperview()
|
|
}
|
|
if let incomingBackgroundNode = self.incomingBackgroundNode, let incomingBackgroundPortalSourceView = self.incomingBackgroundPortalSourceView {
|
|
incomingBackgroundPortalSourceView.addSubview(incomingBackgroundNode.view)
|
|
incomingBackgroundNode.frame = CGRect(origin: CGPoint(), size: incomingBackgroundPortalSourceView.bounds.size)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var outgoingBackgroundPortalSourceView: PortalSourceView?
|
|
private var outgoingBackgroundNode: WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl? {
|
|
didSet {
|
|
if self.outgoingBackgroundNode !== oldValue {
|
|
if let oldValue {
|
|
oldValue.view.removeFromSuperview()
|
|
}
|
|
if let outgoingBackgroundNode = self.outgoingBackgroundNode, let outgoingBackgroundPortalSourceView = self.outgoingBackgroundPortalSourceView {
|
|
outgoingBackgroundPortalSourceView.addSubview(outgoingBackgroundNode.view)
|
|
outgoingBackgroundNode.frame = CGRect(origin: CGPoint(), size: outgoingBackgroundPortalSourceView.bounds.size)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var gradientBackgroundNode: GradientBackgroundNode?
|
|
private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode?
|
|
private let patternImageLayer: EffectImageLayer
|
|
private let dimLayer: SimpleLayer
|
|
private var isGeneratingPatternImage: Bool = false
|
|
|
|
private let bakedBackgroundView: UIImageView
|
|
|
|
private var validLayout: (CGSize, WallpaperDisplayMode)?
|
|
private var wallpaper: TelegramWallpaper?
|
|
private var isSettingUpWallpaper: Bool = false
|
|
|
|
private struct CachedValidPatternImage {
|
|
let generate: (TransformImageArguments) -> DrawingContext?
|
|
let generated: ValidPatternGeneratedImage
|
|
let image: UIImage
|
|
}
|
|
|
|
private static var cachedValidPatternImage: CachedValidPatternImage?
|
|
|
|
private struct ValidPatternImage {
|
|
let wallpaper: TelegramWallpaper
|
|
let invertPattern: Bool
|
|
let generate: (TransformImageArguments) -> DrawingContext?
|
|
}
|
|
private var validPatternImage: ValidPatternImage?
|
|
|
|
private struct ValidPatternGeneratedImage: Equatable {
|
|
let wallpaper: TelegramWallpaper
|
|
let size: CGSize
|
|
let patternColor: UInt32
|
|
let backgroundColor: UInt32
|
|
let invertPattern: Bool
|
|
}
|
|
private var validPatternGeneratedImage: ValidPatternGeneratedImage?
|
|
|
|
private let patternImageDisposable = MetaDisposable()
|
|
|
|
private var bubbleTheme: PresentationTheme?
|
|
private var bubbleCorners: PresentationChatBubbleCorners?
|
|
private var bubbleBackgroundNodeReferences = SparseBag<BubbleBackgroundNodeReference>()
|
|
|
|
private let wallpaperDisposable = MetaDisposable()
|
|
|
|
private let imageDisposable = MetaDisposable()
|
|
|
|
private var motionEnabled: Bool = false {
|
|
didSet {
|
|
if oldValue != self.motionEnabled {
|
|
if self.motionEnabled {
|
|
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
|
|
horizontal.minimumRelativeValue = motionAmount
|
|
horizontal.maximumRelativeValue = -motionAmount
|
|
|
|
let vertical = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
|
|
vertical.minimumRelativeValue = motionAmount
|
|
vertical.maximumRelativeValue = -motionAmount
|
|
|
|
let group = UIMotionEffectGroup()
|
|
group.motionEffects = [horizontal, vertical]
|
|
self.contentNode.view.addMotionEffect(group)
|
|
} else {
|
|
for effect in self.contentNode.view.motionEffects {
|
|
self.contentNode.view.removeMotionEffect(effect)
|
|
}
|
|
}
|
|
if !self.frame.isEmpty {
|
|
self.updateScale()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var rotation: CGFloat = 0.0 {
|
|
didSet {
|
|
var fromValue: CGFloat = 0.0
|
|
if let value = (self.layer.value(forKeyPath: "transform.rotation.z") as? NSNumber)?.floatValue {
|
|
fromValue = CGFloat(value)
|
|
}
|
|
self.contentNode.layer.transform = CATransform3DMakeRotation(self.rotation, 0.0, 0.0, 1.0)
|
|
self.contentNode.layer.animateRotation(from: fromValue, to: self.rotation, duration: 0.3)
|
|
}
|
|
}
|
|
|
|
private var imageContentMode: UIView.ContentMode {
|
|
didSet {
|
|
self.contentNode.contentMode = self.imageContentMode
|
|
}
|
|
}
|
|
|
|
private func updateScale() {
|
|
if self.motionEnabled {
|
|
let scale = (self.frame.width + motionAmount * 2.0) / self.frame.width
|
|
self.contentNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
|
|
} else {
|
|
self.contentNode.transform = CATransform3DIdentity
|
|
}
|
|
}
|
|
|
|
private struct PatternKey: Equatable {
|
|
var mediaId: EngineMedia.Id
|
|
var isLight: Bool
|
|
}
|
|
private static var cachedSharedPattern: (PatternKey, UIImage)?
|
|
|
|
private let _isReady = ValuePromise<Bool>(false, ignoreRepeated: true)
|
|
var isReady: Signal<Bool, NoError> {
|
|
return self._isReady.get()
|
|
}
|
|
|
|
init(context: AccountContext, useSharedAnimationPhase: Bool) {
|
|
self.context = context
|
|
self.useSharedAnimationPhase = useSharedAnimationPhase
|
|
self.imageContentMode = .scaleAspectFill
|
|
|
|
self.contentNode = ASDisplayNode()
|
|
self.contentNode.contentMode = self.imageContentMode
|
|
|
|
self.patternImageLayer = EffectImageLayer()
|
|
|
|
self.bakedBackgroundView = UIImageView()
|
|
self.bakedBackgroundView.isHidden = true
|
|
|
|
self.dimLayer = SimpleLayer()
|
|
self.dimLayer.opacity = 0.0
|
|
self.dimLayer.backgroundColor = UIColor.black.cgColor
|
|
|
|
super.init()
|
|
|
|
if #available(iOS 12.0, *) {
|
|
let freeBackgroundPortalSourceView = PortalSourceView()
|
|
self.freeBackgroundPortalSourceView = freeBackgroundPortalSourceView
|
|
freeBackgroundPortalSourceView.alpha = 0.0
|
|
self.view.addSubview(freeBackgroundPortalSourceView)
|
|
|
|
let incomingBackgroundPortalSourceView = PortalSourceView()
|
|
self.incomingBackgroundPortalSourceView = incomingBackgroundPortalSourceView
|
|
incomingBackgroundPortalSourceView.alpha = 0.0
|
|
self.view.addSubview(incomingBackgroundPortalSourceView)
|
|
|
|
let outgoingBackgroundPortalSourceView = PortalSourceView()
|
|
self.outgoingBackgroundPortalSourceView = outgoingBackgroundPortalSourceView
|
|
outgoingBackgroundPortalSourceView.alpha = 0.0
|
|
self.view.addSubview(outgoingBackgroundPortalSourceView)
|
|
}
|
|
|
|
self.clipsToBounds = true
|
|
self.contentNode.frame = self.bounds
|
|
self.addSubnode(self.contentNode)
|
|
self.layer.addSublayer(self.patternImageLayer)
|
|
|
|
self.layer.addSublayer(self.dimLayer)
|
|
}
|
|
|
|
deinit {
|
|
self.patternImageDisposable.dispose()
|
|
self.wallpaperDisposable.dispose()
|
|
self.imageDisposable.dispose()
|
|
}
|
|
|
|
private func updateDimming() {
|
|
guard let wallpaper = self.wallpaper, let theme = self.bubbleTheme else {
|
|
return
|
|
}
|
|
var dimAlpha: Float = 0.0
|
|
if theme.overallDarkAppearance == true {
|
|
var intensity: Int32?
|
|
switch wallpaper {
|
|
case let .image(_, settings):
|
|
intensity = settings.intensity
|
|
case let .file(file):
|
|
if !file.isPattern {
|
|
intensity = file.settings.intensity
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
if let intensity, intensity < 100 {
|
|
dimAlpha = 1.0 - max(0.0, min(1.0, Float(intensity) / 100.0))
|
|
}
|
|
}
|
|
self.dimLayer.opacity = dimAlpha
|
|
}
|
|
|
|
func update(wallpaper: TelegramWallpaper) {
|
|
if self.wallpaper == wallpaper {
|
|
return
|
|
}
|
|
self.wallpaper = wallpaper
|
|
|
|
var gradientColors: [UInt32] = []
|
|
var gradientAngle: Int32 = 0
|
|
|
|
let wallpaperDimColor: UIColor? = nil
|
|
|
|
if case let .color(color) = wallpaper {
|
|
gradientColors = [color]
|
|
self._isReady.set(true)
|
|
} else if case let .gradient(gradient) = wallpaper {
|
|
gradientColors = gradient.colors
|
|
gradientAngle = gradient.settings.rotation ?? 0
|
|
self._isReady.set(true)
|
|
} else if case let .file(file) = wallpaper, file.isPattern {
|
|
gradientColors = file.settings.colors
|
|
gradientAngle = file.settings.rotation ?? 0
|
|
}
|
|
|
|
var scheduleLoopingEvent = false
|
|
if gradientColors.count >= 3 {
|
|
let mappedColors = gradientColors.map { color -> UIColor in
|
|
return UIColor(rgb: color)
|
|
}
|
|
if self.gradientBackgroundNode == nil {
|
|
let gradientBackgroundNode = createGradientBackgroundNode(colors: mappedColors, useSharedAnimationPhase: self.useSharedAnimationPhase)
|
|
self.gradientBackgroundNode = gradientBackgroundNode
|
|
self.insertSubnode(gradientBackgroundNode, aboveSubnode: self.contentNode)
|
|
gradientBackgroundNode.setPatternOverlay(layer: self.patternImageLayer)
|
|
|
|
if self.isLooping {
|
|
scheduleLoopingEvent = true
|
|
}
|
|
}
|
|
self.gradientBackgroundNode?.updateColors(colors: mappedColors)
|
|
|
|
self.contentNode.backgroundColor = nil
|
|
self.contentNode.contents = nil
|
|
self.blurredBackgroundContents = nil
|
|
self.motionEnabled = false
|
|
self.wallpaperDisposable.set(nil)
|
|
} else {
|
|
if let gradientBackgroundNode = self.gradientBackgroundNode {
|
|
self.gradientBackgroundNode = nil
|
|
gradientBackgroundNode.removeFromSupernode()
|
|
gradientBackgroundNode.setPatternOverlay(layer: nil)
|
|
self.layer.insertSublayer(self.patternImageLayer, above: self.contentNode.layer)
|
|
}
|
|
|
|
self.motionEnabled = wallpaper.settings?.motion ?? false
|
|
|
|
if gradientColors.count >= 2 {
|
|
self.contentNode.backgroundColor = nil
|
|
let image = generateImage(CGSize(width: 100.0, height: 200.0), rotatedContext: { size, context in
|
|
let gradientColors = [UIColor(rgb: gradientColors[0]).cgColor, UIColor(rgb: gradientColors[1]).cgColor] as CFArray
|
|
|
|
var locations: [CGFloat] = [0.0, 1.0]
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
|
|
|
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
|
context.rotate(by: CGFloat(gradientAngle) * CGFloat.pi / 180.0)
|
|
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
|
|
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
|
|
})
|
|
self.contentNode.contents = image?.cgImage
|
|
self.blurredBackgroundContents = image
|
|
self.wallpaperDisposable.set(nil)
|
|
} else if gradientColors.count >= 1 {
|
|
self.contentNode.backgroundColor = UIColor(rgb: gradientColors[0])
|
|
self.contentNode.contents = nil
|
|
self.blurredBackgroundContents = nil
|
|
self.wallpaperDisposable.set(nil)
|
|
} else {
|
|
self.contentNode.backgroundColor = .white
|
|
if let image = chatControllerBackgroundImage(theme: nil, wallpaper: wallpaper, mediaBox: self.context.sharedContext.accountManager.mediaBox, knockoutMode: false) {
|
|
self.contentNode.contents = image.cgImage
|
|
self.blurredBackgroundContents = generateBlurredContents(image: image, dimColor: wallpaperDimColor)
|
|
self.wallpaperDisposable.set(nil)
|
|
Queue.mainQueue().justDispatch {
|
|
self._isReady.set(true)
|
|
}
|
|
} else if let image = chatControllerBackgroundImage(theme: nil, wallpaper: wallpaper, mediaBox: self.context.account.postbox.mediaBox, knockoutMode: false) {
|
|
self.contentNode.contents = image.cgImage
|
|
self.blurredBackgroundContents = generateBlurredContents(image: image, dimColor: wallpaperDimColor)
|
|
self.wallpaperDisposable.set(nil)
|
|
Queue.mainQueue().justDispatch {
|
|
self._isReady.set(true)
|
|
}
|
|
} else {
|
|
self.wallpaperDisposable.set((chatControllerBackgroundImageSignal(wallpaper: wallpaper, mediaBox: self.context.sharedContext.accountManager.mediaBox, accountMediaBox: self.context.account.postbox.mediaBox)
|
|
|> deliverOnMainQueue).start(next: { [weak self] image in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.contentNode.contents = image?.0?.cgImage
|
|
if let image = image?.0 {
|
|
strongSelf.blurredBackgroundContents = generateBlurredContents(image: image, dimColor: wallpaperDimColor)
|
|
} else {
|
|
strongSelf.blurredBackgroundContents = nil
|
|
}
|
|
strongSelf.updateBubbles()
|
|
strongSelf._isReady.set(true)
|
|
}))
|
|
}
|
|
self.contentNode.isHidden = false
|
|
}
|
|
}
|
|
|
|
if self.hasBubbleBackground(for: .free) {
|
|
self.freeBackgroundNode = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: .free)
|
|
} else {
|
|
self.freeBackgroundNode = nil
|
|
}
|
|
|
|
if self.hasBubbleBackground(for: .incoming) {
|
|
self.incomingBackgroundNode = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: .incoming)
|
|
} else {
|
|
self.incomingBackgroundNode = nil
|
|
}
|
|
|
|
if self.hasBubbleBackground(for: .outgoing) {
|
|
self.outgoingBackgroundNode = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: .outgoing)
|
|
} else {
|
|
self.outgoingBackgroundNode = nil
|
|
}
|
|
|
|
if let (size, displayMode) = self.validLayout {
|
|
self.updateLayout(size: size, displayMode: displayMode, transition: .immediate)
|
|
self.updateBubbles()
|
|
|
|
if scheduleLoopingEvent {
|
|
self.animateEvent(transition: .animated(duration: 0.7, curve: .linear), extendAnimation: false)
|
|
}
|
|
}
|
|
|
|
self.updateDimming()
|
|
}
|
|
|
|
func _internalUpdateIsSettingUpWallpaper() {
|
|
self.isSettingUpWallpaper = true
|
|
}
|
|
|
|
private func updatePatternPresentation() {
|
|
guard let wallpaper = self.wallpaper else {
|
|
return
|
|
}
|
|
|
|
switch wallpaper {
|
|
case let .file(file) where file.isPattern:
|
|
let brightness = UIColor.average(of: file.settings.colors.map(UIColor.init(rgb:))).hsb.b
|
|
let patternIsBlack = brightness <= 0.01
|
|
|
|
let intensity = CGFloat(file.settings.intensity ?? 50) / 100.0
|
|
if intensity < 0 {
|
|
self.patternImageLayer.compositionOpacity = 1.0
|
|
self.patternImageLayer.softlightMode = .never
|
|
} else {
|
|
self.patternImageLayer.compositionOpacity = Float(intensity)
|
|
if patternIsBlack {
|
|
self.patternImageLayer.softlightMode = .never
|
|
} else {
|
|
if self.useSharedAnimationPhase && file.settings.colors.count > 2 {
|
|
self.patternImageLayer.softlightMode = .whileAnimating
|
|
} else {
|
|
self.patternImageLayer.softlightMode = .always
|
|
}
|
|
}
|
|
}
|
|
|
|
self.patternImageLayer.isHidden = false
|
|
let invertPattern = intensity < 0
|
|
|
|
self.patternImageLayer.fillWithColorUntilLoaded = invertPattern ? .black : nil
|
|
|
|
if invertPattern {
|
|
self.backgroundColor = .black
|
|
let contentAlpha = abs(intensity)
|
|
self.gradientBackgroundNode?.contentView.alpha = contentAlpha
|
|
self.contentNode.alpha = contentAlpha
|
|
} else {
|
|
self.backgroundColor = nil
|
|
self.gradientBackgroundNode?.contentView.alpha = 1.0
|
|
self.contentNode.alpha = 1.0
|
|
self.patternImageLayer.backgroundColor = nil
|
|
}
|
|
default:
|
|
self.patternImageDisposable.set(nil)
|
|
self.validPatternImage = nil
|
|
self.patternImageLayer.isHidden = true
|
|
self.patternImageLayer.fillWithColorUntilLoaded = nil
|
|
self.patternImageLayer.backgroundColor = nil
|
|
self.backgroundColor = nil
|
|
self.gradientBackgroundNode?.contentView.alpha = 1.0
|
|
self.contentNode.alpha = 1.0
|
|
}
|
|
}
|
|
|
|
private func loadPatternForSizeIfNeeded(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition) {
|
|
guard let wallpaper = self.wallpaper else {
|
|
return
|
|
}
|
|
|
|
var invertPattern: Bool = false
|
|
var patternIsLight: Bool = false
|
|
|
|
switch wallpaper {
|
|
case let .file(file) where file.isPattern:
|
|
var updated = true
|
|
let brightness = UIColor.average(of: file.settings.colors.map(UIColor.init(rgb:))).hsb.b
|
|
patternIsLight = brightness > 0.3
|
|
|
|
let intensity = CGFloat(file.settings.intensity ?? 50) / 100.0
|
|
invertPattern = intensity < 0
|
|
|
|
if let previousWallpaper = self.validPatternImage?.wallpaper {
|
|
switch previousWallpaper {
|
|
case let .file(previousFile):
|
|
if file.file.id == previousFile.file.id && self.validPatternImage?.invertPattern == invertPattern {
|
|
updated = false
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
if updated {
|
|
self.validPatternGeneratedImage = nil
|
|
self.validPatternImage = nil
|
|
|
|
if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated.wallpaper == wallpaper && cachedValidPatternImage.generated.invertPattern == invertPattern {
|
|
self.validPatternImage = ValidPatternImage(wallpaper: cachedValidPatternImage.generated.wallpaper, invertPattern: invertPattern, generate: cachedValidPatternImage.generate)
|
|
} else {
|
|
func reference(for resource: EngineMediaResource, media: EngineMedia) -> MediaResourceReference {
|
|
return .wallpaper(wallpaper: .slug(file.slug), resource: resource._asResource())
|
|
}
|
|
|
|
var convertedRepresentations: [ImageRepresentationWithReference] = []
|
|
for representation in file.file.previewRepresentations {
|
|
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: reference(for: EngineMediaResource(representation.resource), media: EngineMedia(file.file))))
|
|
}
|
|
let dimensions = file.file.dimensions ?? PixelDimensions(width: 2000, height: 4000)
|
|
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), reference: reference(for: EngineMediaResource(file.file.resource), media: EngineMedia(file.file))))
|
|
|
|
let signal = patternWallpaperImage(account: self.context.account, accountManager: self.context.sharedContext.accountManager, representations: convertedRepresentations, mode: .screen, autoFetchFullSize: true)
|
|
self.patternImageDisposable.set((signal
|
|
|> deliverOnMainQueue).start(next: { [weak self] generator in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
|
|
if let generator = generator {
|
|
/*generator = { arguments in
|
|
let scale = arguments.scale ?? UIScreenScale
|
|
let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true)
|
|
|
|
context.withFlippedContext { c in
|
|
if let path = getAppBundle().path(forResource: "PATTERN_static", ofType: "svg"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
|
|
if let image = drawSvgImage(data, CGSize(width: arguments.drawingSize.width * scale, height: arguments.drawingSize.height * scale), .clear, .black, false) {
|
|
c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: arguments.drawingSize))
|
|
}
|
|
}
|
|
}
|
|
|
|
return context
|
|
}*/
|
|
|
|
strongSelf.validPatternImage = ValidPatternImage(wallpaper: wallpaper, invertPattern: invertPattern, generate: generator)
|
|
strongSelf.validPatternGeneratedImage = nil
|
|
if let (size, displayMode) = strongSelf.validLayout {
|
|
strongSelf.loadPatternForSizeIfNeeded(size: size, displayMode: displayMode, transition: .immediate)
|
|
} else {
|
|
strongSelf._isReady.set(true)
|
|
}
|
|
} else {
|
|
strongSelf._isReady.set(true)
|
|
}
|
|
}))
|
|
}
|
|
}
|
|
default:
|
|
self.updatePatternPresentation()
|
|
}
|
|
|
|
if let validPatternImage = self.validPatternImage {
|
|
let patternBackgroundColor: UIColor
|
|
let patternColor: UIColor
|
|
if invertPattern {
|
|
patternColor = .clear
|
|
patternBackgroundColor = .clear
|
|
} else {
|
|
if patternIsLight {
|
|
patternColor = .black
|
|
} else {
|
|
patternColor = .white
|
|
}
|
|
patternBackgroundColor = .clear
|
|
self.patternImageLayer.backgroundColor = nil
|
|
}
|
|
|
|
let updatedGeneratedImage = ValidPatternGeneratedImage(wallpaper: validPatternImage.wallpaper, size: size, patternColor: patternColor.rgb, backgroundColor: patternBackgroundColor.rgb, invertPattern: invertPattern)
|
|
|
|
if self.validPatternGeneratedImage != updatedGeneratedImage {
|
|
self.validPatternGeneratedImage = updatedGeneratedImage
|
|
|
|
if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated == updatedGeneratedImage {
|
|
self.patternImageLayer.suspendCompositionUpdates = true
|
|
self.updatePatternPresentation()
|
|
self.patternImageLayer.patternContentImage = cachedValidPatternImage.image
|
|
self.patternImageLayer.suspendCompositionUpdates = false
|
|
self.patternImageLayer.updateCompositionIfNeeded()
|
|
} else {
|
|
let patternArguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets(), custom: PatternWallpaperArguments(colors: [patternBackgroundColor], rotation: nil, customPatternColor: patternColor, preview: false, displayMode: displayMode.argumentsDisplayMode), scale: min(2.0, UIScreenScale))
|
|
if self.useSharedAnimationPhase || self.patternImageLayer.contents == nil {
|
|
if let drawingContext = validPatternImage.generate(patternArguments) {
|
|
if let image = drawingContext.generateImage() {
|
|
self.patternImageLayer.suspendCompositionUpdates = true
|
|
self.updatePatternPresentation()
|
|
self.patternImageLayer.patternContentImage = image
|
|
self.patternImageLayer.suspendCompositionUpdates = false
|
|
self.patternImageLayer.updateCompositionIfNeeded()
|
|
|
|
if self.useSharedAnimationPhase {
|
|
WallpaperBackgroundNodeImpl.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, image: image)
|
|
}
|
|
} else {
|
|
self.updatePatternPresentation()
|
|
}
|
|
} else {
|
|
self.updatePatternPresentation()
|
|
}
|
|
} else {
|
|
self.isGeneratingPatternImage = true
|
|
DispatchQueue.global(qos: .userInteractive).async { [weak self] in
|
|
let image = validPatternImage.generate(patternArguments)?.generateImage()
|
|
Queue.mainQueue().async {
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.isGeneratingPatternImage = false
|
|
strongSelf.patternImageLayer.patternContentImage = image
|
|
strongSelf.updatePatternPresentation()
|
|
|
|
if let image = image, strongSelf.useSharedAnimationPhase {
|
|
WallpaperBackgroundNodeImpl.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, image: image)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self._isReady.set(true)
|
|
} else {
|
|
if !self.isGeneratingPatternImage {
|
|
self.updatePatternPresentation()
|
|
}
|
|
}
|
|
} else {
|
|
if !self.isGeneratingPatternImage {
|
|
self.updatePatternPresentation()
|
|
}
|
|
}
|
|
|
|
transition.updateFrame(layer: self.patternImageLayer, frame: CGRect(origin: CGPoint(), size: size))
|
|
}
|
|
|
|
func updateLayout(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition) {
|
|
let isFirstLayout = self.validLayout == nil
|
|
self.validLayout = (size, displayMode)
|
|
|
|
if let freeBackgroundPortalSourceView = self.freeBackgroundPortalSourceView {
|
|
transition.updateFrame(view: freeBackgroundPortalSourceView, frame: CGRect(origin: CGPoint(), size: size))
|
|
}
|
|
|
|
if let incomingBackgroundPortalSourceView = self.incomingBackgroundPortalSourceView {
|
|
transition.updateFrame(view: incomingBackgroundPortalSourceView, frame: CGRect(origin: CGPoint(), size: size))
|
|
}
|
|
|
|
if let outgoingBackgroundPortalSourceView = self.outgoingBackgroundPortalSourceView {
|
|
transition.updateFrame(view: outgoingBackgroundPortalSourceView, frame: CGRect(origin: CGPoint(), size: size))
|
|
}
|
|
|
|
transition.updatePosition(node: self.contentNode, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
|
transition.updateBounds(node: self.contentNode, bounds: CGRect(origin: CGPoint(), size: size))
|
|
|
|
if let gradientBackgroundNode = self.gradientBackgroundNode {
|
|
transition.updateFrame(node: gradientBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
|
gradientBackgroundNode.updateLayout(size: size, transition: transition, extendAnimation: false, backwards: false, completion: {})
|
|
}
|
|
|
|
if let outgoingBubbleGradientBackgroundNode = self.outgoingBubbleGradientBackgroundNode {
|
|
transition.updateFrame(node: outgoingBubbleGradientBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
|
outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: transition, extendAnimation: false, backwards: false, completion: {})
|
|
}
|
|
|
|
if let freeBackgroundNode = self.freeBackgroundNode {
|
|
transition.updateFrame(node: freeBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
|
freeBackgroundNode.update(rect: CGRect(origin: CGPoint(), size: size), within: size, transition: transition)
|
|
}
|
|
|
|
if let incomingBackgroundNode = self.incomingBackgroundNode {
|
|
transition.updateFrame(node: incomingBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
|
incomingBackgroundNode.update(rect: CGRect(origin: CGPoint(), size: size), within: size, transition: transition)
|
|
}
|
|
|
|
if let outgoingBackgroundNode = self.outgoingBackgroundNode {
|
|
transition.updateFrame(node: outgoingBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
|
outgoingBackgroundNode.update(rect: CGRect(origin: CGPoint(), size: size), within: size, transition: transition)
|
|
}
|
|
|
|
transition.updateFrame(layer: self.dimLayer, frame: CGRect(origin: CGPoint(), size: size))
|
|
|
|
self.loadPatternForSizeIfNeeded(size: size, displayMode: displayMode, transition: transition)
|
|
|
|
if isFirstLayout && !self.frame.isEmpty {
|
|
self.updateScale()
|
|
}
|
|
}
|
|
|
|
private var isAnimating = false
|
|
private var isLooping = false
|
|
|
|
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) {
|
|
guard !(self.isLooping && self.isAnimating) else {
|
|
return
|
|
}
|
|
self.isAnimating = true
|
|
self.gradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation, backwards: false, completion: { [weak self] in
|
|
if let strongSelf = self {
|
|
strongSelf.isAnimating = false
|
|
if strongSelf.isLooping && strongSelf.validLayout != nil {
|
|
strongSelf.animateEvent(transition: transition, extendAnimation: extendAnimation)
|
|
}
|
|
}
|
|
})
|
|
self.outgoingBubbleGradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation, backwards: false, completion: {})
|
|
}
|
|
|
|
func updateIsLooping(_ isLooping: Bool) {
|
|
let wasLooping = self.isLooping
|
|
self.isLooping = isLooping
|
|
|
|
if isLooping && !wasLooping {
|
|
self.animateEvent(transition: .animated(duration: 0.7, curve: .linear), extendAnimation: false)
|
|
}
|
|
}
|
|
|
|
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) {
|
|
if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners {
|
|
self.bubbleTheme = bubbleTheme
|
|
self.bubbleCorners = bubbleCorners
|
|
|
|
if bubbleTheme.chat.message.outgoing.bubble.withoutWallpaper.fill.count >= 3 && bubbleTheme.chat.animateMessageColors {
|
|
if self.outgoingBubbleGradientBackgroundNode == nil {
|
|
let outgoingBubbleGradientBackgroundNode = GradientBackgroundNode(adjustSaturation: false)
|
|
if let (size, _) = self.validLayout {
|
|
outgoingBubbleGradientBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
|
outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: .immediate, extendAnimation: false, backwards: false, completion: {})
|
|
}
|
|
self.outgoingBubbleGradientBackgroundNode = outgoingBubbleGradientBackgroundNode
|
|
}
|
|
self.outgoingBubbleGradientBackgroundNode?.updateColors(colors: bubbleTheme.chat.message.outgoing.bubble.withoutWallpaper.fill)
|
|
} else if let _ = self.outgoingBubbleGradientBackgroundNode {
|
|
self.outgoingBubbleGradientBackgroundNode = nil
|
|
}
|
|
|
|
if self.hasBubbleBackground(for: .free) {
|
|
self.freeBackgroundNode = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: .free)
|
|
} else {
|
|
self.freeBackgroundNode = nil
|
|
}
|
|
|
|
if self.hasBubbleBackground(for: .incoming) {
|
|
self.incomingBackgroundNode = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: .incoming)
|
|
} else {
|
|
self.incomingBackgroundNode = nil
|
|
}
|
|
|
|
if self.hasBubbleBackground(for: .outgoing) {
|
|
self.outgoingBackgroundNode = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: .outgoing)
|
|
} else {
|
|
self.outgoingBackgroundNode = nil
|
|
}
|
|
|
|
self.updateBubbles()
|
|
self.updateDimming()
|
|
}
|
|
}
|
|
|
|
private func updateBubbles() {
|
|
for reference in self.bubbleBackgroundNodeReferences {
|
|
reference.node?.updateContents()
|
|
}
|
|
}
|
|
|
|
func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool {
|
|
guard let bubbleTheme = self.bubbleTheme, let bubbleCorners = self.bubbleCorners else {
|
|
return false
|
|
}
|
|
if self.wallpaper == nil && !self.isSettingUpWallpaper {
|
|
return false
|
|
}
|
|
|
|
var hasPlainWallpaper = false
|
|
let graphicsWallpaper: TelegramWallpaper
|
|
if let wallpaper = self.wallpaper {
|
|
switch wallpaper {
|
|
case .color:
|
|
hasPlainWallpaper = true
|
|
default:
|
|
break
|
|
}
|
|
graphicsWallpaper = wallpaper
|
|
} else {
|
|
graphicsWallpaper = bubbleTheme.chat.defaultWallpaper
|
|
}
|
|
|
|
let graphics = PresentationResourcesChat.principalGraphics(theme: bubbleTheme, wallpaper: graphicsWallpaper, bubbleCorners: bubbleCorners)
|
|
switch type {
|
|
case .incoming:
|
|
if graphics.incomingBubbleGradientImage != nil {
|
|
return true
|
|
}
|
|
if bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) {
|
|
return !hasPlainWallpaper
|
|
}
|
|
case .outgoing:
|
|
if graphics.outgoingBubbleGradientImage != nil {
|
|
return true
|
|
}
|
|
if bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.contains(where: { $0.alpha <= 0.99 }) {
|
|
return !hasPlainWallpaper
|
|
}
|
|
case .free:
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? {
|
|
if !self.hasBubbleBackground(for: type) {
|
|
return nil
|
|
}
|
|
|
|
#if true
|
|
var sourceView: PortalSourceView?
|
|
switch type {
|
|
case .free:
|
|
sourceView = self.freeBackgroundPortalSourceView
|
|
case .incoming:
|
|
sourceView = self.incomingBackgroundPortalSourceView
|
|
case .outgoing:
|
|
sourceView = self.outgoingBackgroundPortalSourceView
|
|
}
|
|
|
|
if let sourceView, let portalView = PortalView(matchPosition: true) {
|
|
sourceView.addPortal(view: portalView)
|
|
let node = WallpaperBackgroundNodeImpl.BubbleBackgroundPortalNodeImpl(portalView: portalView)
|
|
return node
|
|
} else {
|
|
let node = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type)
|
|
return node
|
|
}
|
|
#else
|
|
let node = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type)
|
|
node.updateContents()
|
|
return node
|
|
#endif
|
|
}
|
|
|
|
func makeFreeBackground() -> PortalView? {
|
|
if !self.hasBubbleBackground(for: .free) {
|
|
return nil
|
|
}
|
|
|
|
if let sourceView = self.freeBackgroundPortalSourceView, let portalView = PortalView(matchPosition: true) {
|
|
sourceView.addPortal(view: portalView)
|
|
return portalView
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func hasExtraBubbleBackground() -> Bool {
|
|
var isInvertedGradient = false
|
|
switch self.wallpaper {
|
|
case let .file(file):
|
|
if let intensity = file.settings.intensity, intensity < 0 {
|
|
isInvertedGradient = true
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
return isInvertedGradient
|
|
}
|
|
|
|
func makeDimmedNode() -> ASDisplayNode? {
|
|
if let gradientBackgroundNode = self.gradientBackgroundNode {
|
|
return GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private protocol WallpaperComponentView: AnyObject {
|
|
var view: UIView { get }
|
|
|
|
func update(size: CGSize, transition: ContainedViewLayoutTransition)
|
|
}
|
|
|
|
public func createWallpaperBackgroundNode(context: AccountContext, forChatDisplay: Bool, useSharedAnimationPhase: Bool = false) -> WallpaperBackgroundNode {
|
|
return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase)
|
|
}
|