import Foundation import UIKit import AsyncDisplayKit import Display import GradientBackground import TelegramPresentationData import SyncCore import TelegramCore import AccountContext import SwiftSignalKit import WallpaperResources import Postbox import FastBlur private let motionAmount: CGFloat = 32.0 private func generateBlurredContents(image: UIImage) -> UIImage? { let size = image.size.aspectFitted(CGSize(width: 64.0, height: 64.0)) let context = DrawingContext(size: size, scale: 1.0, opaque: true, clear: false) 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) return context.generateImage() } public final class WallpaperBackgroundNode: ASDisplayNode { public final class BubbleBackgroundNode: ASDisplayNode { public enum BubbleType { case incoming case outgoing case free } private let bubbleType: BubbleType private let contentNode: ASImageNode private var cleanWallpaperNode: ASDisplayNode? private var gradientWallpaperNode: GradientBackgroundNode.CloneNode? private weak var backgroundNode: WallpaperBackgroundNode? private var index: SparseBag.Index? private var currentLayout: (rect: CGRect, containerSize: CGSize)? public override var frame: CGRect { didSet { if oldValue.size != self.bounds.size { 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: WallpaperBackgroundNode, bubbleType: BubbleType) { self.backgroundNode = backgroundNode self.bubbleType = bubbleType self.contentNode = ASImageNode() 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 } 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 } else { self.contentNode.backgroundColor = nil } needsCleanBackground = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill.alpha <= 0.99 || bubbleTheme.chat.message.incoming.bubble.withWallpaper.gradientFill.alpha <= 0.99 case .outgoing: self.contentNode.image = graphics.outgoingBubbleGradientImage if graphics.outgoingBubbleGradientImage == nil { self.contentNode.backgroundColor = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill } else { self.contentNode.backgroundColor = nil } needsCleanBackground = bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.alpha <= 0.99 || bubbleTheme.chat.message.outgoing.bubble.withWallpaper.gradientFill.alpha <= 0.99 case .free: self.contentNode.image = nil self.contentNode.backgroundColor = nil needsCleanBackground = true } var isInvertedGradient = false var hasComplexGradient = false switch wallpaper { case let .file(_, _, _, _, _, _, _, _, settings): hasComplexGradient = settings.colors.count >= 3 if let intensity = settings.intensity, intensity < 0 { isInvertedGradient = true } case let .gradient(_, colors, _): hasComplexGradient = colors.count >= 3 default: break } var needsGradientBackground = false var needsWallpaperBackground = false if isInvertedGradient { switch self.bubbleType { case .free: needsCleanBackground = false case .incoming, .outgoing: break } } if needsCleanBackground { if hasComplexGradient { needsGradientBackground = backgroundNode.gradientBackgroundNode != nil } else { needsWallpaperBackground = true } } if needsWallpaperBackground { if self.cleanWallpaperNode == nil { let cleanWallpaperNode = ASImageNode() 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 = backgroundNode.gradientBackgroundNode { 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 (rect, containerSize) = self.currentLayout { self.update(rect: rect, within: containerSize) } } public func update(rect: CGRect, within containerSize: CGSize, 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) 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 } } public 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 } } public 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) } } public 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) } } } private final class BubbleBackgroundNodeReference { weak var node: BubbleBackgroundNode? init(node: BubbleBackgroundNode) { self.node = node } } private let context: AccountContext private let useSharedAnimationPhase: Bool private let contentNode: ASDisplayNode private var blurredBackgroundContents: UIImage? private var gradientBackgroundNode: GradientBackgroundNode? private let patternImageNode: ASImageNode private var isGeneratingPatternImage: Bool = false private var validLayout: CGSize? 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 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() 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() } } } } public 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: MediaId var isLight: Bool } private static var cachedSharedPattern: (PatternKey, UIImage)? private let _isReady = ValuePromise(false, ignoreRepeated: true) public var isReady: Signal { return self._isReady.get() } public init(context: AccountContext, useSharedAnimationPhase: Bool = false) { self.context = context self.useSharedAnimationPhase = useSharedAnimationPhase self.imageContentMode = .scaleAspectFill self.contentNode = ASDisplayNode() self.contentNode.contentMode = self.imageContentMode self.patternImageNode = ASImageNode() super.init() self.clipsToBounds = true self.contentNode.frame = self.bounds self.addSubnode(self.contentNode) self.addSubnode(self.patternImageNode) } deinit { self.patternImageDisposable.dispose() self.wallpaperDisposable.dispose() self.imageDisposable.dispose() } public func update(wallpaper: TelegramWallpaper) { if self.wallpaper == wallpaper { return } self.wallpaper = wallpaper var gradientColors: [UInt32] = [] var gradientAngle: Int32 = 0 if case let .color(color) = wallpaper { gradientColors = [color] self._isReady.set(true) } else if case let .gradient(_, colors, settings) = wallpaper { gradientColors = colors gradientAngle = settings.rotation ?? 0 self._isReady.set(true) } else if case let .file(_, _, _, _, isPattern, _, _, _, settings) = wallpaper, isPattern { gradientColors = settings.colors gradientAngle = settings.rotation ?? 0 } 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.addSubnode(self.patternImageNode) } 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() self.insertSubnode(self.patternImageNode, aboveSubnode: self.contentNode) } 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) 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) 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) } else { strongSelf.blurredBackgroundContents = nil } strongSelf._isReady.set(true) })) } self.contentNode.isHidden = false } } if let size = self.validLayout { self.updateLayout(size: size, transition: .immediate) self.updateBubbles() } } public func _internalUpdateIsSettingUpWallpaper() { self.isSettingUpWallpaper = true } private func updatePatternPresentation() { guard let wallpaper = self.wallpaper else { return } switch wallpaper { case let .file(_, _, _, _, isPattern, _, _, _, settings) where isPattern: let brightness = UIColor.average(of: settings.colors.map(UIColor.init(rgb:))).hsb.b let patternIsBlack = brightness <= 0.01 let intensity = CGFloat(settings.intensity ?? 50) / 100.0 if intensity < 0 { self.patternImageNode.alpha = 1.0 self.patternImageNode.layer.compositingFilter = nil } else { self.patternImageNode.alpha = intensity if patternIsBlack { self.patternImageNode.layer.compositingFilter = nil } else { self.patternImageNode.layer.compositingFilter = "softLightBlendMode" } } self.patternImageNode.isHidden = false let invertPattern = intensity < 0 if invertPattern { self.backgroundColor = .black let contentAlpha = abs(intensity) self.gradientBackgroundNode?.contentView.alpha = contentAlpha self.contentNode.alpha = contentAlpha if self.patternImageNode.image != nil { self.patternImageNode.backgroundColor = nil } else { self.patternImageNode.backgroundColor = .black } } else { self.backgroundColor = nil self.gradientBackgroundNode?.contentView.alpha = 1.0 self.contentNode.alpha = 1.0 self.patternImageNode.backgroundColor = nil } default: self.patternImageDisposable.set(nil) self.validPatternImage = nil self.patternImageNode.isHidden = true self.patternImageNode.backgroundColor = nil self.backgroundColor = nil self.gradientBackgroundNode?.contentView.alpha = 1.0 self.contentNode.alpha = 1.0 } } private func loadPatternForSizeIfNeeded(size: CGSize, transition: ContainedViewLayoutTransition) { guard let wallpaper = self.wallpaper else { return } var invertPattern: Bool = false var patternIsLight: Bool = false switch wallpaper { case let .file(_, _, _, _, isPattern, _, slug, file, settings) where isPattern: var updated = true let brightness = UIColor.average(of: settings.colors.map(UIColor.init(rgb:))).hsb.b patternIsLight = brightness > 0.3 if let previousWallpaper = self.validPatternImage?.wallpaper { switch previousWallpaper { case let .file(_, _, _, _, _, _, _, previousFile, _): if file.id == previousFile.id { updated = false } default: break } } if updated { self.validPatternGeneratedImage = nil self.validPatternImage = nil if let cachedValidPatternImage = WallpaperBackgroundNode.cachedValidPatternImage, cachedValidPatternImage.generated.wallpaper == wallpaper { self.validPatternImage = ValidPatternImage(wallpaper: cachedValidPatternImage.generated.wallpaper, generate: cachedValidPatternImage.generate) } else { func reference(for resource: MediaResource, media: Media, message: Message?) -> MediaResourceReference { if let message = message { return .media(media: .message(message: MessageReference(message), media: media), resource: resource) } return .wallpaper(wallpaper: .slug(slug), resource: resource) } var convertedRepresentations: [ImageRepresentationWithReference] = [] for representation in file.previewRepresentations { convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: reference(for: representation.resource, media: file, message: nil))) } let dimensions = file.dimensions ?? PixelDimensions(width: 2000, height: 4000) convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.resource, progressiveSizes: [], immediateThumbnailData: nil), reference: reference(for: file.resource, media: file, message: nil))) 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 { strongSelf.validPatternImage = ValidPatternImage(wallpaper: wallpaper, generate: generator) strongSelf.validPatternGeneratedImage = nil if let size = strongSelf.validLayout { strongSelf.loadPatternForSizeIfNeeded(size: size, transition: .immediate) } else { strongSelf._isReady.set(true) } } else { strongSelf._isReady.set(true) } })) } } let intensity = CGFloat(settings.intensity ?? 50) / 100.0 invertPattern = intensity < 0 default: self.updatePatternPresentation() } if let validPatternImage = self.validPatternImage { let patternBackgroundColor: UIColor let patternColor: UIColor if invertPattern { patternColor = .clear patternBackgroundColor = .clear if self.patternImageNode.image == nil { self.patternImageNode.backgroundColor = .black } else { self.patternImageNode.backgroundColor = nil } } else { if patternIsLight { patternColor = .black } else { patternColor = .white } patternBackgroundColor = .clear self.patternImageNode.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 = WallpaperBackgroundNode.cachedValidPatternImage, cachedValidPatternImage.generated == updatedGeneratedImage { self.patternImageNode.image = cachedValidPatternImage.image self.updatePatternPresentation() } else { let patternArguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets(), custom: PatternWallpaperArguments(colors: [patternBackgroundColor], rotation: nil, customPatternColor: patternColor, preview: false), scale: min(2.0, UIScreenScale)) if self.useSharedAnimationPhase || self.patternImageNode.image == nil { if let drawingContext = validPatternImage.generate(patternArguments) { if let image = drawingContext.generateImage() { self.patternImageNode.image = image self.updatePatternPresentation() if self.useSharedAnimationPhase { WallpaperBackgroundNode.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.patternImageNode.image = image strongSelf.updatePatternPresentation() if let image = image, strongSelf.useSharedAnimationPhase { WallpaperBackgroundNode.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(node: self.patternImageNode, frame: CGRect(origin: CGPoint(), size: size)) } public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { let isFirstLayout = self.validLayout == nil self.validLayout = 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) } self.loadPatternForSizeIfNeeded(size: size, transition: transition) if isFirstLayout && !self.frame.isEmpty { self.updateScale() } } public func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool = false) { self.gradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation) } public func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) { if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners { self.bubbleTheme = bubbleTheme self.bubbleCorners = bubbleCorners self.updateBubbles() } } private func updateBubbles() { for reference in self.bubbleBackgroundNodeReferences { reference.node?.updateContents() } } public func hasBubbleBackground(for type: WallpaperBackgroundNode.BubbleBackgroundNode.BubbleType) -> 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.alpha <= 0.99 { return !hasPlainWallpaper } case .outgoing: if graphics.outgoingBubbleGradientImage != nil { return true } if bubbleTheme.chat.message.outgoing.bubble.withWallpaper.fill.alpha <= 0.99 { return !hasPlainWallpaper } case .free: return true } return false } public func makeBubbleBackground(for type: WallpaperBackgroundNode.BubbleBackgroundNode.BubbleType) -> WallpaperBackgroundNode.BubbleBackgroundNode? { if !self.hasBubbleBackground(for: type) { return nil } let node = WallpaperBackgroundNode.BubbleBackgroundNode(backgroundNode: self, bubbleType: type) node.updateContents() return node } }