Swiftgram/submodules/SettingsUI/Sources/Themes/SettingsThemeWallpaperNode.swift

309 lines
15 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore
import Postbox
import SwiftSignalKit
import TelegramPresentationData
import AccountContext
import RadialStatusNode
import WallpaperResources
import GradientBackground
private func whiteColorImage(theme: PresentationTheme, color: UIColor) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
return .single({ arguments in
guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else {
return nil
}
context.withFlippedContext { c in
c.setFillColor(color.cgColor)
c.fill(CGRect(origin: CGPoint(), size: arguments.drawingSize))
let lineWidth: CGFloat = 1.0
c.setLineWidth(lineWidth)
c.setStrokeColor(theme.list.controlSecondaryColor.cgColor)
c.stroke(CGRect(origin: CGPoint(), size: arguments.drawingSize).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0))
}
return context
})
}
private let blackColorImage: UIImage? = {
guard let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, opaque: true, clear: false) else {
return nil
}
context.withContext { c in
c.setFillColor(UIColor.black.cgColor)
c.fill(CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0)))
}
return context.generateImage()
}()
final class SettingsThemeWallpaperNode: ASDisplayNode {
var wallpaper: TelegramWallpaper?
private var arguments: PatternWallpaperArguments?
let buttonNode = HighlightTrackingButtonNode()
let backgroundNode = ASImageNode()
let imageNode = TransformImageNode()
private var gradientNode: GradientBackgroundNode?
private let statusNode: RadialStatusNode
var pressed: (() -> Void)?
private let displayLoading: Bool
private var isSelected: Bool = false
private var isLoaded: Bool = false
private let isLoadedDisposable = MetaDisposable()
init(displayLoading: Bool = false, overlayBackgroundColor: UIColor = UIColor(white: 0.0, alpha: 0.3)) {
self.displayLoading = displayLoading
self.imageNode.contentAnimations = [.subsequentUpdates]
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.2), enableBlur: true)
let progressDiameter: CGFloat = 50.0
self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter)
self.statusNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.imageNode)
self.addSubnode(self.buttonNode)
self.addSubnode(self.statusNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
deinit {
self.isLoadedDisposable.dispose()
}
func setSelected(_ selected: Bool, animated: Bool = false) {
if self.isSelected != selected {
self.isSelected = selected
self.updateStatus(animated: animated)
}
}
private func updateIsLoaded(isLoaded: Bool, animated: Bool) {
if self.isLoaded != isLoaded {
self.isLoaded = isLoaded
self.updateStatus(animated: animated)
}
}
private func updateStatus(animated: Bool) {
if self.isSelected {
if self.isLoaded || !displayLoading {
self.statusNode.transitionToState(.check(.white), animated: animated, completion: {})
} else {
self.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true), animated: animated, completion: {})
}
} else {
self.statusNode.transitionToState(.none, animated: animated, completion: {})
}
}
func setOverlayBackgroundColor(_ color: UIColor) {
self.statusNode.backgroundNodeColor = color
}
func setWallpaper(context: AccountContext, wallpaper: TelegramWallpaper, selected: Bool, size: CGSize, cornerRadius: CGFloat = 0.0, synchronousLoad: Bool = false) {
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
var colors: [UInt32] = []
var intensity: CGFloat = 0.5
if case let .gradient(gradient) = wallpaper {
colors = gradient.colors
} else if case let .file(file) = wallpaper {
colors = file.settings.colors
intensity = CGFloat(file.settings.intensity ?? 50) / 100.0
} else if case let .color(color) = wallpaper {
colors = [color]
}
let isBlack = UIColor.average(of: colors.map(UIColor.init(rgb:))).hsb.b <= 0.01
if colors.count >= 3 {
if let gradientNode = self.gradientNode {
gradientNode.updateColors(colors: colors.map { UIColor(rgb: $0) })
} else {
let gradientNode = createGradientBackgroundNode()
gradientNode.isUserInteractionEnabled = false
self.gradientNode = gradientNode
gradientNode.updateColors(colors: colors.map { UIColor(rgb: $0) })
self.insertSubnode(gradientNode, belowSubnode: self.imageNode)
}
if intensity < 0.0 {
self.imageNode.layer.compositingFilter = nil
} else {
if isBlack {
self.imageNode.layer.compositingFilter = nil
} else {
self.imageNode.layer.compositingFilter = "softLightBlendMode"
}
}
self.backgroundNode.image = nil
} else {
if let gradientNode = self.gradientNode {
self.gradientNode = nil
gradientNode.removeFromSupernode()
}
if intensity < 0.0 {
self.imageNode.layer.compositingFilter = nil
} else {
if isBlack {
self.imageNode.layer.compositingFilter = nil
} else {
self.imageNode.layer.compositingFilter = "softLightBlendMode"
}
}
if colors.count >= 2 {
self.backgroundNode.image = generateGradientImage(size: CGSize(width: 80.0, height: 80.0), colors: colors.map(UIColor.init(rgb:)), locations: [0.0, 1.0], direction: .vertical)
self.backgroundNode.backgroundColor = nil
} else if colors.count >= 1 {
self.backgroundNode.image = nil
self.backgroundNode.backgroundColor = UIColor(rgb: colors[0])
}
}
if let gradientNode = self.gradientNode {
gradientNode.frame = CGRect(origin: CGPoint(), size: size)
gradientNode.updateLayout(size: size, transition: .immediate, extendAnimation: false, backwards: false, completion: {})
}
let progressDiameter: CGFloat = 50.0
self.statusNode.frame = CGRect(x: floorToScreenPixels((size.width - progressDiameter) / 2.0), y: floorToScreenPixels((size.height - progressDiameter) / 2.0), width: progressDiameter, height: progressDiameter)
let corners = ImageCorners(radius: cornerRadius)
if self.wallpaper != wallpaper {
self.wallpaper = wallpaper
switch wallpaper {
case .builtin:
self.imageNode.alpha = 1.0
self.imageNode.setSignal(settingsBuiltinWallpaperImage(account: context.account, thumbnail: true))
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: CGSize(), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
apply()
self.isLoadedDisposable.set(nil)
self.updateIsLoaded(isLoaded: true, animated: false)
case let .image(representations, _):
let convertedRepresentations: [ImageRepresentationWithReference] = representations.map({ ImageRepresentationWithReference(representation: $0, reference: .wallpaper(wallpaper: nil, resource: $0.resource)) })
self.imageNode.alpha = 1.0
self.imageNode.setSignal(wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: convertedRepresentations, thumbnail: true, autoFetchFullSize: true, synchronousLoad: synchronousLoad))
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: largestImageRepresentation(representations)!.dimensions.cgSize.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
apply()
self.isLoadedDisposable.set(nil)
self.updateIsLoaded(isLoaded: true, animated: false)
case let .file(file):
let convertedRepresentations : [ImageRepresentationWithReference] = file.file.previewRepresentations.map {
ImageRepresentationWithReference(representation: $0, reference: .wallpaper(wallpaper: .slug(file.slug), resource: $0.resource))
}
let fullDimensions = file.file.dimensions ?? PixelDimensions(width: 2000, height: 4000)
let convertedFullRepresentations = [ImageRepresentationWithReference(representation: .init(dimensions: fullDimensions, resource: file.file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource))]
let imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>
if wallpaper.isPattern {
var patternIntensity: CGFloat = 0.5
if !file.settings.colors.isEmpty {
if let intensity = file.settings.intensity {
patternIntensity = CGFloat(intensity) / 100.0
}
}
if patternIntensity < 0.0 {
self.imageNode.alpha = 1.0
self.arguments = PatternWallpaperArguments(colors: [.clear], rotation: nil, customPatternColor: UIColor(white: 0.0, alpha: 1.0 + patternIntensity))
} else {
self.imageNode.alpha = CGFloat(file.settings.intensity ?? 50) / 100.0
let isLight = UIColor.average(of: file.settings.colors.map(UIColor.init(rgb:))).hsb.b > 0.3
self.arguments = PatternWallpaperArguments(colors: [.clear], rotation: nil, customPatternColor: isLight ? .black : .white)
}
imageSignal = patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: convertedRepresentations, mode: .thumbnail, autoFetchFullSize: true)
|> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
if let value = value {
return .single(value)
} else {
return .complete()
}
}
let anyStatus = combineLatest(queue: .mainQueue(),
context.account.postbox.mediaBox.resourceStatus(convertedFullRepresentations[0].reference.resource, approximateSynchronousValue: true),
context.sharedContext.accountManager.mediaBox.resourceStatus(convertedFullRepresentations[0].reference.resource, approximateSynchronousValue: true)
)
|> map { a, b -> Bool in
switch a {
case .Local:
return true
default:
break
}
switch b {
case .Local:
return true
default:
break
}
return false
}
|> distinctUntilChanged
self.updateIsLoaded(isLoaded: false, animated: false)
self.isLoadedDisposable.set((anyStatus
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
return
}
strongSelf.updateIsLoaded(isLoaded: value, animated: true)
}))
} else {
self.imageNode.alpha = 1.0
imageSignal = wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: .standalone(media: file.file), representations: convertedRepresentations, thumbnail: true, autoFetchFullSize: true, blurred: file.settings.blur, synchronousLoad: synchronousLoad)
self.updateIsLoaded(isLoaded: true, animated: false)
self.isLoadedDisposable.set(nil)
}
self.imageNode.setSignal(imageSignal, attemptSynchronously: synchronousLoad)
let dimensions = file.file.dimensions ?? PixelDimensions(width: 100, height: 100)
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: dimensions.cgSize.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets(), custom: self.arguments))
apply()
default:
break
}
} else if let wallpaper = self.wallpaper {
switch wallpaper {
case .builtin, .color, .gradient:
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: CGSize(), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
apply()
case let .image(representations, _):
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: largestImageRepresentation(representations)!.dimensions.cgSize.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
apply()
case let .file(file):
let dimensions = file.file.dimensions ?? PixelDimensions(width: 100, height: 100)
let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: dimensions.cgSize.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets(), custom: self.arguments))
apply()
}
}
self.setSelected(selected, animated: false)
}
@objc func buttonPressed() {
self.pressed?()
}
}