From 11409151df01526f83a4d07d95c8c19fe64ed59c Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 7 Mar 2023 15:56:12 +0400 Subject: [PATCH] Background portal views --- .../Sources/DebugController.swift | 16 +- .../Sources/MediaPickerSelectedListNode.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 2 +- .../TelegramUI/Sources/ChatQrCodeScreen.swift | 4 +- .../Sources/ExperimentalUISettings.swift | 6 - .../MetalWallpaperBackgroundNode.swift | 312 ----- .../Sources/WallpaperBackgroundNode.swift | 1010 +++-------------- 7 files changed, 155 insertions(+), 1197 deletions(-) diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 8727b115ef..7f69a91711 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -90,7 +90,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { case experimentalCompatibility(Bool) case enableDebugDataDisplay(Bool) case acceleratedStickers(Bool) - case experimentalBackground(Bool) case inlineForums(Bool) case localTranscription(Bool) case enableReactionOverrides(Bool) @@ -117,7 +116,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue - case .clearTips, .resetTranslationStates, .resetNotifications, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .enableQuickReactionSwitch, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineForums, .localTranscription, . enableReactionOverrides, .restorePurchases: + case .clearTips, .resetTranslationStates, .resetNotifications, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .enableQuickReactionSwitch, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .inlineForums, .localTranscription, . enableReactionOverrides, .restorePurchases: return DebugControllerSection.experiments.rawValue case .preferredVideoCodec: return DebugControllerSection.videoExperiments.rawValue @@ -202,8 +201,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 34 case .acceleratedStickers: return 35 - case .experimentalBackground: - return 36 case .inlineForums: return 37 case .localTranscription: @@ -1181,16 +1178,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).start() }) - case let .experimentalBackground(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Background Experiment", value: value, sectionId: self.section, style: .blocks, updated: { value in - let _ = arguments.sharedContext.accountManager.transaction ({ transaction in - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in - var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings - settings.experimentalBackground = value - return PreferencesEntry(settings) - }) - }).start() - }) case let .inlineForums(value): return ItemListSwitchItem(presentationData: presentationData, title: "Inline Forums", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in @@ -1371,7 +1358,6 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility)) entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay)) entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers)) - entries.append(.experimentalBackground(experimentalSettings.experimentalBackground)) entries.append(.inlineForums(experimentalSettings.inlineForums)) entries.append(.localTranscription(experimentalSettings.localTranscription)) if case .internal = sharedContext.applicationBindings.appBuildType { diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index 770d27ca53..6f3f918bbd 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -523,7 +523,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI self.context = context self.persistentItems = persistentItems - self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground) + self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false) self.wallpaperBackgroundNode.backgroundColor = .black self.scrollNode = ASScrollNode() diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 9b592a150d..82e4c23393 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -572,7 +572,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G default: break } - self.chatBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: useSharedAnimationPhase, useExperimentalImplementation: self.context.sharedContext.immediateExperimentalUISettings.experimentalBackground) + self.chatBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: useSharedAnimationPhase) self.wallpaperReady.set(self.chatBackgroundNode.isReady) var locationBroadcastPanelSource: LocationBroadcastPanelSource diff --git a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift index e6f5a2760b..8b2f0923a4 100644 --- a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift @@ -1531,7 +1531,7 @@ private class QrContentNode: ASDisplayNode, ContentNode { self.containerNode = ASDisplayNode() - self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground) + self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false) self.codeBackgroundNode = ASDisplayNode() self.codeBackgroundNode.backgroundColor = .white @@ -2022,7 +2022,7 @@ private class MessageContentNode: ASDisplayNode, ContentNode { self.containerNode = ASDisplayNode() - self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground) + self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false) self.backgroundNode = ASDisplayNode() self.backgroundImageNode = ASImageNode() diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index 7e61c20f76..209784f2f0 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -40,7 +40,6 @@ public struct ExperimentalUISettings: Codable, Equatable { public var experimentalCompatibility: Bool public var enableDebugDataDisplay: Bool public var acceleratedStickers: Bool - public var experimentalBackground: Bool public var inlineStickers: Bool public var localTranscription: Bool public var enableReactionOverrides: Bool @@ -68,7 +67,6 @@ public struct ExperimentalUISettings: Codable, Equatable { experimentalCompatibility: false, enableDebugDataDisplay: false, acceleratedStickers: false, - experimentalBackground: false, inlineStickers: false, localTranscription: false, enableReactionOverrides: false, @@ -97,7 +95,6 @@ public struct ExperimentalUISettings: Codable, Equatable { experimentalCompatibility: Bool, enableDebugDataDisplay: Bool, acceleratedStickers: Bool, - experimentalBackground: Bool, inlineStickers: Bool, localTranscription: Bool, enableReactionOverrides: Bool, @@ -123,7 +120,6 @@ public struct ExperimentalUISettings: Codable, Equatable { self.experimentalCompatibility = experimentalCompatibility self.enableDebugDataDisplay = enableDebugDataDisplay self.acceleratedStickers = acceleratedStickers - self.experimentalBackground = experimentalBackground self.inlineStickers = inlineStickers self.localTranscription = localTranscription self.enableReactionOverrides = enableReactionOverrides @@ -153,7 +149,6 @@ public struct ExperimentalUISettings: Codable, Equatable { self.experimentalCompatibility = (try container.decodeIfPresent(Int32.self, forKey: "experimentalCompatibility") ?? 0) != 0 self.enableDebugDataDisplay = (try container.decodeIfPresent(Int32.self, forKey: "enableDebugDataDisplay") ?? 0) != 0 self.acceleratedStickers = (try container.decodeIfPresent(Int32.self, forKey: "acceleratedStickers") ?? 0) != 0 - self.experimentalBackground = (try container.decodeIfPresent(Int32.self, forKey: "experimentalBackground") ?? 0) != 0 self.inlineStickers = (try container.decodeIfPresent(Int32.self, forKey: "inlineStickers") ?? 0) != 0 self.localTranscription = (try container.decodeIfPresent(Int32.self, forKey: "localTranscription") ?? 0) != 0 self.enableReactionOverrides = try container.decodeIfPresent(Bool.self, forKey: "enableReactionOverrides") ?? false @@ -183,7 +178,6 @@ public struct ExperimentalUISettings: Codable, Equatable { try container.encode((self.experimentalCompatibility ? 1 : 0) as Int32, forKey: "experimentalCompatibility") try container.encode((self.enableDebugDataDisplay ? 1 : 0) as Int32, forKey: "enableDebugDataDisplay") try container.encode((self.acceleratedStickers ? 1 : 0) as Int32, forKey: "acceleratedStickers") - try container.encode((self.experimentalBackground ? 1 : 0) as Int32, forKey: "experimentalBackground") try container.encode((self.inlineStickers ? 1 : 0) as Int32, forKey: "inlineStickers") try container.encode((self.localTranscription ? 1 : 0) as Int32, forKey: "localTranscription") try container.encode(self.enableReactionOverrides, forKey: "enableReactionOverrides") diff --git a/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift index ae77acc904..8b13789179 100644 --- a/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift @@ -1,313 +1 @@ -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 -import MetalKit -import HierarchyTrackingLayer -import simd -private final class NullActionClass: NSObject, CAAction { - static let shared = NullActionClass() - - @objc public func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { - } -} - -@available(iOS 13.0, *) -open class SimpleMetalLayer: CAMetalLayer { - override open func action(forKey event: String) -> CAAction? { - return nullAction - } - - override public init() { - super.init() - } - - override public init(layer: Any) { - super.init(layer: layer) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -private func makePipelineState(device: MTLDevice, library: MTLLibrary, vertexProgram: String, fragmentProgram: String) -> MTLRenderPipelineState? { - guard let loadedVertexProgram = library.makeFunction(name: vertexProgram) else { - return nil - } - guard let loadedFragmentProgram = library.makeFunction(name: fragmentProgram) else { - return nil - } - - let pipelineStateDescriptor = MTLRenderPipelineDescriptor() - pipelineStateDescriptor.vertexFunction = loadedVertexProgram - pipelineStateDescriptor.fragmentFunction = loadedFragmentProgram - pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm - guard let pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineStateDescriptor) else { - return nil - } - - return pipelineState -} - - -@available(iOS 13.0, *) -final class MetalWallpaperBackgroundNode: ASDisplayNode, WallpaperBackgroundNode { - private let device: MTLDevice - private let metalLayer: SimpleMetalLayer - private let commandQueue: MTLCommandQueue - private let renderPipelineState: MTLRenderPipelineState - - private let hierarchyTrackingLayer = HierarchyTrackingLayer() - - var isReady: Signal { - return .single(true) - } - - var rotation: CGFloat = 0.0 - - private var animationPhase: Int = 0 - - private var animationThread: Thread? - private var displayLink: CADisplayLink? - - override init() { - self.device = MTLCreateSystemDefaultDevice()! - self.metalLayer = SimpleMetalLayer() - self.metalLayer.maximumDrawableCount = 3 - self.metalLayer.presentsWithTransaction = true - self.metalLayer.contentsScale = UIScreenScale - self.commandQueue = self.device.makeCommandQueue()! - - let mainBundle = Bundle(for: MetalWallpaperBackgroundNode.self) - - guard let path = mainBundle.path(forResource: "WallpaperBackgroundNodeBundle", ofType: "bundle") else { - preconditionFailure() - } - guard let bundle = Bundle(path: path) else { - preconditionFailure() - } - guard let defaultLibrary = try? self.device.makeDefaultLibrary(bundle: bundle) else { - preconditionFailure() - } - - guard let renderPipelineState = makePipelineState(device: self.device, library: defaultLibrary, vertexProgram: "wallpaperVertex", fragmentProgram: "wallpaperFragment") else { - preconditionFailure() - } - self.renderPipelineState = renderPipelineState - - super.init() - - self.metalLayer.device = self.device - self.metalLayer.pixelFormat = .bgra8Unorm - self.metalLayer.framebufferOnly = true - self.metalLayer.allowsNextDrawableTimeout = true - self.metalLayer.isOpaque = true - - self.layer.addSublayer(self.metalLayer) - self.layer.addSublayer(self.hierarchyTrackingLayer) - - self.hierarchyTrackingLayer.opacity = 0.0 - self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in - self?.updateIsVisible(true) - } - self.hierarchyTrackingLayer.didExitHierarchy = { [weak self] in - self?.updateIsVisible(false) - } - } - - func update(wallpaper: TelegramWallpaper) { - - } - - func _internalUpdateIsSettingUpWallpaper() { - - } - - func updateLayout(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition) { - if self.metalLayer.drawableSize != size { - self.metalLayer.drawableSize = size - - transition.updateFrame(layer: self.metalLayer, frame: CGRect(origin: CGPoint(), size: size)) - - self.redraw() - } - } - - private func updateIsVisible(_ isVisible: Bool) { - if isVisible { - if self.displayLink == nil { - let displayLink = CADisplayLink(target: DisplayLinkTarget { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.redraw() - }, selector: #selector(DisplayLinkTarget.event)) - self.displayLink = displayLink - if #available(iOS 15.0, iOSApplicationExtension 15.0, *) { - if "".isEmpty { - displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 60.0, preferred: 60.0) - } else { - displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: Float(UIScreen.main.maximumFramesPerSecond), maximum: Float(UIScreen.main.maximumFramesPerSecond), preferred: Float(UIScreen.main.maximumFramesPerSecond)) - } - } - displayLink.isPaused = false - - if !"".isEmpty { - self.animationThread = Thread(block: { - displayLink.add(to: .current, forMode: .common) - - while true { - if Thread.current.isCancelled { - break - } - RunLoop.current.run(until: .init(timeIntervalSinceNow: 1.0)) - } - }) - self.animationThread?.name = "MetalWallpaperBackgroundNode" - self.animationThread?.qualityOfService = .userInteractive - self.animationThread?.start() - } else { - displayLink.add(to: .current, forMode: .common) - } - } - } else { - if let displayLink = self.displayLink { - self.displayLink = nil - - displayLink.invalidate() - } - if let animationThread = self.animationThread { - self.animationThread = nil - animationThread.cancel() - } - } - } - - private var previousDrawTime: Double? - - private func redraw() { - let timestamp = CACurrentMediaTime() - if let previousDrawTime = self.previousDrawTime { - let _ = previousDrawTime - //print("frame time \((timestamp - previousDrawTime) * 1000.0)") - } - self.previousDrawTime = timestamp - - self.animationPhase += 1 - let animationOffset = Float(self.animationPhase % 200) / 200.0 - let _ = animationOffset - - guard let commandBuffer = self.commandQueue.makeCommandBuffer() else { - return - } - guard let drawable = self.metalLayer.nextDrawable() else { - return - } - - let drawTime = CACurrentMediaTime() - timestamp - if drawTime > 9.0 / 1000.0 { - print("get time \(drawTime * 1000.0)") - } - - let renderPassDescriptor = MTLRenderPassDescriptor() - renderPassDescriptor.colorAttachments[0].texture = drawable.texture - renderPassDescriptor.colorAttachments[0].loadAction = .clear - renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor( - red: 0.0, - green: 0.0, - blue: 0.0, - alpha: 1.0 - ) - - guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { - return - } - - var vertices: [Float] = [ - -1.0, -1.0, - 1.0, -1.0, - -1.0, 1.0, - 1.0, 1.0 - ] - - renderEncoder.setRenderPipelineState(self.renderPipelineState) - - renderEncoder.setVertexBytes(&vertices, length: 4 * vertices.count, index: 0) - - var resolution = simd_uint2(UInt32(drawable.texture.width), UInt32(drawable.texture.height)) - renderEncoder.setFragmentBytes(&resolution, length: MemoryLayout.size * 2, index: 0) - - var time = Float(timestamp) * 0.25 - renderEncoder.setFragmentBytes(&time, length: 4, index: 1) - - renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: 1) - - renderEncoder.endEncoding() - - if self.metalLayer.presentsWithTransaction { - if Thread.isMainThread { - commandBuffer.commit() - commandBuffer.waitUntilScheduled() - drawable.present() - } else { - CATransaction.begin() - commandBuffer.commit() - commandBuffer.waitUntilScheduled() - drawable.present() - CATransaction.commit() - } - } else { - commandBuffer.addScheduledHandler { _ in - drawable.present() - } - commandBuffer.commit() - } - } - - func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) { - - } - - func updateIsLooping(_ isLooping: Bool) { - - } - - func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) { - - } - - func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool { - return false - } - - func hasExtraBubbleBackground() -> Bool { - return false - } - - func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? { - return nil - } - - func makeFreeBackground() -> PortalView? { - return nil - } - - func makeDimmedNode() -> ASDisplayNode? { - return nil - } -} diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 0b1d24c63d..a782d333a0 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -590,6 +590,53 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } } } + + 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) + } + + 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? @@ -603,11 +650,42 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode private let useSharedAnimationPhase: Bool private let contentNode: ASDisplayNode + private var blurredBackgroundContents: UIImage? private var blurredBackgroundPortalSourceView: PortalSourceView? private var blurredBackgroundDimmedNode: GradientBackgroundNode.CloneNode? private var blurredBackgroundDimmedOverlayView: UIView? private var blurredBackgroundContentView: UIImageView? + + 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? @@ -746,6 +824,16 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode let blurredBackgroundContentView = UIImageView() self.blurredBackgroundContentView = blurredBackgroundContentView blurredBackgroundPortalSourceView.addSubview(blurredBackgroundContentView) + + let incomingBackgroundPortalSourceView = PortalSourceView() + self.incomingBackgroundPortalSourceView = incomingBackgroundPortalSourceView + incomingBackgroundPortalSourceView.alpha = 0.0001 + self.view.addSubview(incomingBackgroundPortalSourceView) + + let outgoingBackgroundPortalSourceView = PortalSourceView() + self.outgoingBackgroundPortalSourceView = outgoingBackgroundPortalSourceView + outgoingBackgroundPortalSourceView.alpha = 0.0001 + self.view.addSubview(outgoingBackgroundPortalSourceView) } self.clipsToBounds = true @@ -900,6 +988,18 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.contentNode.isHidden = false } } + + 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) @@ -1154,6 +1254,14 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode if let blurredBackgroundDimmedOverlayView = self.blurredBackgroundDimmedOverlayView { transition.updateFrame(view: blurredBackgroundDimmedOverlayView, 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)) @@ -1167,6 +1275,16 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode transition.updateFrame(node: outgoingBubbleGradientBackgroundNode, frame: CGRect(origin: CGPoint(), size: size)) outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: transition, extendAnimation: false, backwards: false, completion: {}) } + + 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) + } self.loadPatternForSizeIfNeeded(size: size, displayMode: displayMode, transition: transition) @@ -1232,6 +1350,18 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode if let wallpaper = self.wallpaper { self.blurredBackgroundDimmedOverlayView?.backgroundColor = selectDateFillStaticColor(theme: bubbleTheme, wallpaper: wallpaper) } + + 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() } @@ -1292,9 +1422,25 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode if !self.hasBubbleBackground(for: type) { return nil } - let node = WallpaperBackgroundNodeImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type) - node.updateContents() - return node + + var sourceView: PortalSourceView? + switch type { + case .free: + sourceView = self.blurredBackgroundPortalSourceView + 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 + } } func makeFreeBackground() -> PortalView? { @@ -1336,862 +1482,6 @@ private protocol WallpaperComponentView: AnyObject { func update(size: CGSize, transition: ContainedViewLayoutTransition) } -final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroundNode { - final class SharedStorage { - } - - final class BubbleBackgroundNodeImpl: ASDisplayNode, WallpaperBubbleBackgroundNode { - var implicitContentUpdate = true - - private let bubbleType: WallpaperBubbleType - private let contentNode: ASImageNode - - private var cleanWallpaperNode: ASDisplayNode? - private var gradientWallpaperNode: GradientBackgroundNode.CloneNode? - private weak var backgroundNode: WallpaperBackgroundNodeMergedImpl? - private var index: SparseBag.Index? - - private var currentLayout: (rect: CGRect, containerSize: CGSize)? - - 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: WallpaperBackgroundNodeMergedImpl, 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 - } - - 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 - } - - 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: - needsCleanBackground = false - case .incoming, .outgoing: - break - } - } - - if needsCleanBackground { - if hasComplexGradient { - needsGradientBackground = backgroundNode.gradient != nil - } else { - needsWallpaperBackground = true - } - } - - var gradientBackgroundSource: GradientBackgroundNode? = backgroundNode.gradient?.gradientBackground - - 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.backgroundColor - } else { - self.cleanWallpaperNode?.contents = nil - self.cleanWallpaperNode?.backgroundColor = backgroundNode.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 (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, 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) - transition.animateView { - self.contentNode.layer.contentsRect = shiftedContentsRect - } - if let cleanWallpaperNode = self.cleanWallpaperNode { - transition.updateFrame(layer: cleanWallpaperNode.layer, frame: self.bounds) - transition.animateView { - cleanWallpaperNode.layer.contentsRect = shiftedContentsRect - } - } - if let gradientWallpaperNode = self.gradientWallpaperNode { - transition.updateFrame(layer: gradientWallpaperNode.layer, frame: self.bounds) - transition.animateView { - gradientWallpaperNode.layer.contentsRect = shiftedContentsRect - } - } - } - - 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) - } - } - - 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 - } - } - - 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) - } - } - } - - private final class BubbleBackgroundNodeReference { - weak var node: BubbleBackgroundNodeImpl? - - init(node: BubbleBackgroundNodeImpl) { - self.node = node - } - } - - private final class WallpaperGradiendComponentView: WallpaperComponentView { - struct Spec: Equatable { - var colors: [UInt32] - } - - let spec: Spec - let gradientBackground: GradientBackgroundNode - - var view: UIView { - return self.gradientBackground.view - } - - init(spec: Spec, updated: @escaping () -> Void) { - self.spec = spec - - self.gradientBackground = GradientBackgroundNode(colors: spec.colors.map(UIColor.init(rgb:)), useSharedAnimationPhase: true, adjustSaturation: false) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(size: CGSize, transition: ContainedViewLayoutTransition) { - self.gradientBackground.frame = CGRect(origin: CGPoint(), size: size) - self.gradientBackground.updateLayout(size: size, transition: transition, extendAnimation: false, backwards: false, completion: {}) - } - } - - private final class WallpaperColorComponentView: WallpaperComponentView { - struct Spec: Equatable { - var color: UInt32 - } - - let spec: Spec - let backgroundView: UIView - - var view: UIView { - return self.backgroundView - } - - init(spec: Spec, updated: @escaping () -> Void) { - self.spec = spec - - self.backgroundView = UIView() - self.backgroundView.backgroundColor = UIColor(rgb: spec.color) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(size: CGSize, transition: ContainedViewLayoutTransition) { - self.backgroundView.frame = CGRect(origin: CGPoint(), size: size) - } - } - - private final class WallpaperImageComponentView: WallpaperComponentView { - enum Spec: Equatable { - case image( - representation: TelegramMediaImageRepresentation, - isPattern: Bool, - intensity: CGFloat - ) - case builtin - } - - let spec: Spec - let updated: () -> Void - let imageView: UIImageView - var fetchDisposable: Disposable? - var dataDisposable: Disposable? - - var imageData: Data? - - private var validSize: CGSize? - - var view: UIView { - return self.imageView - } - - init(context: AccountContext, spec: Spec, updated: @escaping () -> Void) { - self.spec = spec - self.updated = updated - - self.imageView = UIImageView() - self.imageView.contentMode = .scaleAspectFill - - switch spec { - case let .image(representation, _, _): - self.fetchDisposable = (fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: MediaResourceReference.standalone(resource: representation.resource)) - |> deliverOnMainQueue).start() - self.dataDisposable = (context.account.postbox.mediaBox.resourceData(representation.resource) - |> deliverOnMainQueue).start(next: { [weak self] dataValue in - guard let strongSelf = self else { - return - } - - if dataValue.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: dataValue.path)) { - strongSelf.imageData = data - if let size = strongSelf.validSize { - strongSelf.updateImage(size: size, data: data) - } - } - }) - case .builtin: - if let filePath = getAppBundle().path(forResource: "ChatWallpaperBuiltin0", ofType: "jpg"), let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) { - self.imageData = data - if let size = self.validSize { - self.updateImage(size: size, data: data) - } - } - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.fetchDisposable?.dispose() - self.dataDisposable?.dispose() - } - - func update(size: CGSize, transition: ContainedViewLayoutTransition) { - let sizeUpdated = self.validSize != size - self.validSize = size - - self.imageView.frame = CGRect(origin: CGPoint(), size: size) - - if sizeUpdated || self.imageView.image == nil { - if let imageData = self.imageData { - self.updateImage(size: size, data: imageData) - } - } - } - - private func updateImage(size: CGSize, data: Data) { - let scale: CGFloat - if UIScreenScale >= 2.9 { - scale = 2.5 - } else { - scale = UIScreenScale - } - - switch self.spec { - case let .image(_, isPattern, intensity): - if isPattern { - let patternBackgroundColor: UIColor - let patternForegroundColor: UIColor - if intensity < 0.0 { - patternBackgroundColor = .clear - patternForegroundColor = .black - } else { - patternBackgroundColor = .clear - patternForegroundColor = .black - } - - if let unpackedData = TGGUnzipData(data, 2 * 1024 * 1024), let patternImage = drawSvgImage(unpackedData, CGSize(width: floor(size.width * scale), height: floor(size.height * scale)), patternBackgroundColor, patternForegroundColor, false) { - if intensity < 0.0 { - self.imageView.image = generateImage(patternImage.size, scale: patternImage.scale, rotatedContext: { size, context in - context.setFillColor(UIColor.black.cgColor) - context.fill(CGRect(origin: CGPoint(), size: size)) - - if let cgImage = patternImage.cgImage { - context.setBlendMode(.destinationOut) - context.translateBy(x: size.width / 2.0, y: size.height / 2.0) - context.scaleBy(x: 1.0, y: -1.0) - context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) - context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size)) - } - }) - self.imageView.alpha = 1.0 - self.imageView.layer.compositingFilter = nil - self.imageView.backgroundColor = UIColor(white: 0.0, alpha: 1.0 - abs(intensity)) - } else { - self.imageView.image = patternImage - self.imageView.alpha = abs(intensity) - self.imageView.layer.compositingFilter = "softLightBlendMode" - self.imageView.backgroundColor = nil - } - } - - self.updated() - } else if let image = UIImage(data: data) { - self.imageView.image = image - self.imageView.layer.compositingFilter = nil - self.imageView.alpha = 1.0 - - self.updated() - } - case .builtin: - if let image = UIImage(data: data) { - self.imageView.image = image - self.imageView.layer.compositingFilter = nil - self.imageView.alpha = 1.0 - - self.updated() - } - } - } - } - - private let context: AccountContext - private let storage: SharedStorage - - private let staticView: UIImageView - private let dynamicView: UIView - private var color: WallpaperColorComponentView? - private var gradient: WallpaperGradiendComponentView? - private var image: WallpaperImageComponentView? - - private var blurredBackgroundContents: UIImage? - - private var isSettingUpWallpaper: Bool = false - - private var wallpaper: TelegramWallpaper? - private var validLayout: CGSize? - - private let _isReady = ValuePromise(false, ignoreRepeated: true) - var isReady: Signal { - return self._isReady.get() - } - - var rotation: CGFloat = 0.0 { - didSet { - } - } - - private var isAnimating: Bool = false - - private var bubbleTheme: PresentationTheme? - private var bubbleCorners: PresentationChatBubbleCorners? - private var bubbleBackgroundNodeReferences = SparseBag() - private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode? - - init(context: AccountContext, storage: SharedStorage?) { - self.context = context - self.storage = storage ?? SharedStorage() - - self.staticView = UIImageView() - self.dynamicView = UIView() - - super.init() - - self.view.addSubview(self.staticView) - } - - func update(wallpaper: TelegramWallpaper) { - self.wallpaper = wallpaper - - var colorSpec: WallpaperColorComponentView.Spec? - var gradientSpec: WallpaperGradiendComponentView.Spec? - var imageSpec: WallpaperImageComponentView.Spec? - - switch wallpaper { - case .builtin: - imageSpec = WallpaperImageComponentView.Spec.builtin - case let .color(color): - colorSpec = WallpaperColorComponentView.Spec(color: color) - case let .gradient(gradient): - if gradient.colors.count >= 3 { - gradientSpec = WallpaperGradiendComponentView.Spec(colors: gradient.colors) - } - case let .image(representations, settings): - if let representation = representations.last { - imageSpec = WallpaperImageComponentView.Spec.image(representation: representation, isPattern: false, intensity: 1.0) - } - let _ = settings - case let .file(file): - if file.settings.colors.count >= 3 { - gradientSpec = WallpaperGradiendComponentView.Spec(colors: file.settings.colors) - } - if let dimensions = file.file.dimensions { - let representation = TelegramMediaImageRepresentation(dimensions: dimensions, resource: file.file.resource, progressiveSizes: [], immediateThumbnailData: file.file.immediateThumbnailData, hasVideo: false, isPersonal: false) - imageSpec = WallpaperImageComponentView.Spec.image(representation: representation, isPattern: file.isPattern, intensity: CGFloat(file.settings.intensity ?? 100) / 100.0) - } - } - - if self.color?.spec != colorSpec { - if let color = self.color { - self.color = nil - color.view.removeFromSuperview() - } - if let colorSpec = colorSpec { - let color = WallpaperColorComponentView(spec: colorSpec, updated: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.componentsUpdated() - }) - self.color = color - if let size = self.validLayout { - color.update(size: size, transition: .immediate) - } - self.dynamicView.insertSubview(color.view, at: 0) - - self.componentsUpdated() - } - } - - if self.gradient?.spec != gradientSpec { - if let gradient = self.gradient { - self.gradient = nil - gradient.view.removeFromSuperview() - } - if let gradientSpec = gradientSpec { - let gradient = WallpaperGradiendComponentView(spec: gradientSpec, updated: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.componentsUpdated() - }) - self.gradient = gradient - if let size = self.validLayout { - gradient.update(size: size, transition: .immediate) - } - self.dynamicView.insertSubview(gradient.view, at: 0) - } - } - - if self.image?.spec != imageSpec { - if let image = self.image { - self.image = nil - image.view.removeFromSuperview() - } - if let imageSpec = imageSpec { - let image = WallpaperImageComponentView(context: self.context, spec: imageSpec, updated: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.componentsUpdated() - }) - self.image = image - if let size = self.validLayout { - image.update(size: size, transition: .immediate) - } - if let gradient = self.gradient { - self.dynamicView.insertSubview(image.view, aboveSubview: gradient.view) - } else { - self.dynamicView.insertSubview(image.view, at: 0) - } - } - } - } - - private func componentsUpdated() { - if self.isAnimating { - if self.dynamicView.superview == nil { - self.view.addSubview(self.dynamicView) - self.staticView.isHidden = true - } - self._isReady.set(true) - } else { - self.staticView.isHidden = false - self.dynamicView.removeFromSuperview() - - if let size = self.validLayout { - if let color = self.color { - self.staticView.image = nil - self.staticView.backgroundColor = color.backgroundView.backgroundColor - } else { - let gradientImage = self.gradient?.gradientBackground.contentView.image - let gradientFrame = self.gradient?.gradientBackground.frame - - let imageImage = self.image?.imageView.image - let imageBackgroundColor = self.image?.imageView.backgroundColor - let imageFrame = self.image?.imageView.frame - let imageAlpha = self.image?.imageView.alpha - let imageFilter = self.image?.imageView.layer.compositingFilter as? String - - self.staticView.image = generateImage(size, opaque: true, scale: nil, rotatedContext: { size, context in - UIGraphicsPushContext(context) - - if let gradientImage = gradientImage, let gradientFrame = gradientFrame { - gradientImage.draw(in: gradientFrame) - } - - if let imageImage = imageImage, let imageFrame = imageFrame, let imageAlpha = imageAlpha { - if imageFilter == "softLightBlendMode" { - context.setBlendMode(.softLight) - } - - if let imageBackgroundColor = imageBackgroundColor { - context.setFillColor(imageBackgroundColor.cgColor) - context.fill(imageFrame) - } - - context.setAlpha(imageAlpha) - - context.translateBy(x: imageFrame.midX, y: imageFrame.midY) - context.scaleBy(x: 1.0, y: -1.0) - context.translateBy(x: -imageFrame.midX, y: -imageFrame.midY) - if let cgImage = imageImage.cgImage { - let drawingSize = imageImage.size.aspectFilled(imageFrame.size) - context.draw(cgImage, in: CGRect(origin: CGPoint(x: imageFrame.minX + (imageFrame.width - drawingSize.width) / 2.0, y: imageFrame.minX + (imageFrame.height - drawingSize.height) / 2.0), size: drawingSize)) - } - context.translateBy(x: imageFrame.midX, y: imageFrame.midY) - context.scaleBy(x: 1.0, y: -1.0) - context.translateBy(x: -imageFrame.midX, y: -imageFrame.midY) - - context.setBlendMode(.normal) - context.setAlpha(1.0) - } - - UIGraphicsPopContext() - }) - } - - self._isReady.set(true) - } - } - } - - func _internalUpdateIsSettingUpWallpaper() { - self.isSettingUpWallpaper = true - } - - func updateLayout(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition) { - self.validLayout = size - - self.staticView.frame = CGRect(origin: CGPoint(), size: size) - - if let gradient = self.gradient { - gradient.update(size: size, transition: transition) - } - if let image = self.image { - image.update(size: size, transition: transition) - } - } - - private var isLooping = false - func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) { - if let gradient = self.gradient { - guard !(self.isLooping && self.isAnimating) else { - return - } - self.isAnimating = true - self.componentsUpdated() - gradient.gradientBackground.animateEvent(transition: transition, extendAnimation: extendAnimation, backwards: false, completion: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.isAnimating = false - if strongSelf.isLooping { - strongSelf.animateEvent(transition: transition, extendAnimation: extendAnimation) - } else { - strongSelf.componentsUpdated() - } - }) - } else { - self.isAnimating = false - } - } - - func updateIsLooping(_ isLooping: Bool) { - let wasLooping = self.isLooping - self.isLooping = isLooping - - if isLooping && !wasLooping { - self.animateEvent(transition: .animated(duration: 0.4, 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 - } - - self.updateBubbles() - } - } - - 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 - } - let node = WallpaperBackgroundNodeMergedImpl.BubbleBackgroundNodeImpl(backgroundNode: self, bubbleType: type) - node.updateContents() - return node - } - - func makeFreeBackground() -> PortalView? { - return nil - } - - func hasExtraBubbleBackground() -> Bool { - return false - } - - func makeDimmedNode() -> ASDisplayNode? { - return nil - } -} - -private let sharedStorage = WallpaperBackgroundNodeMergedImpl.SharedStorage() - -public func createWallpaperBackgroundNode(context: AccountContext, forChatDisplay: Bool, useSharedAnimationPhase: Bool = false, useExperimentalImplementation: Bool = false) -> WallpaperBackgroundNode { - if forChatDisplay && useExperimentalImplementation { - #if DEBUG - if #available(iOS 13.0, iOSApplicationExtension 13.0, *) { - return MetalWallpaperBackgroundNode() - } - #else - return WallpaperBackgroundNodeMergedImpl(context: context, storage: useSharedAnimationPhase ? sharedStorage : nil) - #endif - } - +public func createWallpaperBackgroundNode(context: AccountContext, forChatDisplay: Bool, useSharedAnimationPhase: Bool = false) -> WallpaperBackgroundNode { return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase) }