Emoji improvements

This commit is contained in:
Ali 2022-07-20 01:32:47 +02:00
parent 97de870643
commit b56132cbc1
14 changed files with 567 additions and 148 deletions

View File

@ -6,23 +6,56 @@ import ComponentDisplayAdapters
public final class BlurredBackgroundComponent: Component { public final class BlurredBackgroundComponent: Component {
public let color: UIColor public let color: UIColor
public let tintContainerView: UIView?
public init( public init(
color: UIColor color: UIColor,
tintContainerView: UIView? = nil
) { ) {
self.color = color self.color = color
self.tintContainerView = tintContainerView
} }
public static func ==(lhs: BlurredBackgroundComponent, rhs: BlurredBackgroundComponent) -> Bool { public static func ==(lhs: BlurredBackgroundComponent, rhs: BlurredBackgroundComponent) -> Bool {
if lhs.color != rhs.color { if lhs.color != rhs.color {
return false return false
} }
if lhs.tintContainerView !== rhs.tintContainerView {
return false
}
return true return true
} }
public final class View: BlurredBackgroundView { public final class View: BlurredBackgroundView {
private var tintMaskView: UIView?
public func update(component: BlurredBackgroundComponent, availableSize: CGSize, transition: Transition) -> CGSize { public func update(component: BlurredBackgroundComponent, availableSize: CGSize, transition: Transition) -> CGSize {
if let tintContainerView = component.tintContainerView {
self.updateColor(color: .clear, forceKeepBlur: true, transition: transition.containedViewLayoutTransition)
let tintMaskView: UIView
if let current = self.tintMaskView {
tintMaskView = current
} else {
tintMaskView = UIView()
self.tintMaskView = tintMaskView
self.addSubview(tintMaskView)
}
tintMaskView.backgroundColor = component.color
transition.setFrame(view: tintMaskView, frame: CGRect(origin: CGPoint(), size: availableSize))
if tintMaskView.mask !== tintContainerView {
tintMaskView.mask = tintContainerView
}
} else {
self.updateColor(color: component.color, transition: transition.containedViewLayoutTransition) self.updateColor(color: component.color, transition: transition.containedViewLayoutTransition)
if let tintMaskView = self.tintMaskView {
self.tintMaskView = nil
tintMaskView.removeFromSuperview()
}
}
self.update(size: availableSize, transition: transition.containedViewLayoutTransition) self.update(size: availableSize, transition: transition.containedViewLayoutTransition)
return availableSize return availableSize
@ -30,7 +63,7 @@ public final class BlurredBackgroundComponent: Component {
} }
public func makeView() -> View { public func makeView() -> View {
return View(color: .clear, enableBlur: true) return View(color: nil, enableBlur: true)
} }
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize { public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {

View File

@ -62,7 +62,7 @@ private func adjustFrameRate(animation: CAAnimation) {
} }
public extension CALayer { public extension CALayer {
func makeAnimation(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, delay: Double = 0.0, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) -> CAAnimation { func makeAnimation(from: AnyObject?, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, delay: Double = 0.0, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) -> CAAnimation {
if timingFunction.hasPrefix(kCAMediaTimingFunctionCustomSpringPrefix) { if timingFunction.hasPrefix(kCAMediaTimingFunctionCustomSpringPrefix) {
let components = timingFunction.components(separatedBy: "_") let components = timingFunction.components(separatedBy: "_")
let damping = Float(components[1]) ?? 100.0 let damping = Float(components[1]) ?? 100.0
@ -158,7 +158,7 @@ public extension CALayer {
} }
} }
func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, delay: Double = 0.0, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { func animate(from: AnyObject?, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, delay: Double = 0.0, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
let animation = self.makeAnimation(from: from, to: to, keyPath: keyPath, timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) let animation = self.makeAnimation(from: from, to: to, keyPath: keyPath, timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
self.add(animation, forKey: additive ? nil : keyPath) self.add(animation, forKey: additive ? nil : keyPath)
} }

View File

@ -633,7 +633,7 @@ public extension ContainedViewLayoutTransition {
} }
case let .animated(duration, curve): case let .animated(duration, curve):
let previousFrame: CGRect let previousFrame: CGRect
if beginWithCurrentState, let presentation = view.layer.presentation() { if beginWithCurrentState, (view.layer.animation(forKey: "position") != nil || view.layer.animation(forKey: "bounds") != nil), let presentation = view.layer.presentation() {
previousFrame = presentation.frame previousFrame = presentation.frame
} else { } else {
previousFrame = view.frame previousFrame = view.frame
@ -662,7 +662,7 @@ public extension ContainedViewLayoutTransition {
} }
case let .animated(duration, curve): case let .animated(duration, curve):
let previousFrame: CGRect let previousFrame: CGRect
if beginWithCurrentState, let presentation = layer.presentation() { if beginWithCurrentState, (layer.animation(forKey: "position") != nil || layer.animation(forKey: "bounds") != nil), let presentation = layer.presentation() {
previousFrame = presentation.frame previousFrame = presentation.frame
} else { } else {
previousFrame = layer.frame previousFrame = layer.frame

View File

@ -287,7 +287,7 @@ public final class NavigationBackgroundNode: ASDisplayNode {
} }
open class BlurredBackgroundView: UIView { open class BlurredBackgroundView: UIView {
private var _color: UIColor private var _color: UIColor?
private var enableBlur: Bool private var enableBlur: Bool
@ -304,8 +304,8 @@ open class BlurredBackgroundView: UIView {
} }
} }
public init(color: UIColor, enableBlur: Bool = true) { public init(color: UIColor?, enableBlur: Bool = true) {
self._color = .clear self._color = nil
self.enableBlur = enableBlur self.enableBlur = enableBlur
self.backgroundView = UIView() self.backgroundView = UIView()
@ -314,15 +314,17 @@ open class BlurredBackgroundView: UIView {
self.addSubview(self.backgroundView) self.addSubview(self.backgroundView)
if let color = color {
self.updateColor(color: color, transition: .immediate) self.updateColor(color: color, transition: .immediate)
} }
}
required public init?(coder: NSCoder) { required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
private func updateBackgroundBlur(forceKeepBlur: Bool) { private func updateBackgroundBlur(forceKeepBlur: Bool) {
if self.enableBlur && !sharedIsReduceTransparencyEnabled && ((self._color.alpha > .ulpOfOne && self._color.alpha < 0.95) || forceKeepBlur) { if let color = self._color, self.enableBlur && !sharedIsReduceTransparencyEnabled && ((color.alpha > .ulpOfOne && color.alpha < 0.95) || forceKeepBlur) {
if self.effectView == nil { if self.effectView == nil {
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
@ -369,16 +371,16 @@ open class BlurredBackgroundView: UIView {
public func updateColor(color: UIColor, enableBlur: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) { public func updateColor(color: UIColor, enableBlur: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) {
let effectiveEnableBlur = enableBlur ?? self.enableBlur let effectiveEnableBlur = enableBlur ?? self.enableBlur
if self._color.isEqual(color) && self.enableBlur == effectiveEnableBlur { if self._color == color && self.enableBlur == effectiveEnableBlur {
return return
} }
self._color = color self._color = color
self.enableBlur = effectiveEnableBlur self.enableBlur = effectiveEnableBlur
if sharedIsReduceTransparencyEnabled { if sharedIsReduceTransparencyEnabled {
transition.updateBackgroundColor(layer: self.backgroundView.layer, color: self._color.withAlphaComponent(1.0)) transition.updateBackgroundColor(layer: self.backgroundView.layer, color: color.withAlphaComponent(1.0))
} else { } else {
transition.updateBackgroundColor(layer: self.backgroundView.layer, color: self._color) transition.updateBackgroundColor(layer: self.backgroundView.layer, color: color)
} }
self.updateBackgroundBlur(forceKeepBlur: forceKeepBlur) self.updateBackgroundBlur(forceKeepBlur: forceKeepBlur)

View File

@ -71,7 +71,7 @@ public func adjustSaturationInContext(context: DrawingContext, saturation: CGFlo
vImageMatrixMultiply_ARGB8888(&buffer, &buffer, &matrix, divisor, nil, nil, vImage_Flags(kvImageDoNotTile)) vImageMatrixMultiply_ARGB8888(&buffer, &buffer, &matrix, divisor, nil, nil, vImage_Flags(kvImageDoNotTile))
} }
private func generateGradient(size: CGSize, colors inputColors: [UIColor], positions: [CGPoint], adjustSaturation: CGFloat = 1.0) -> UIImage { private func generateGradient(size: CGSize, colors inputColors: [UIColor], positions: [CGPoint], adjustSaturation: CGFloat = 1.0) -> (UIImage, String) {
let colors: [UIColor] = inputColors.count == 1 ? [inputColors[0], inputColors[0], inputColors[0]] : inputColors let colors: [UIColor] = inputColors.count == 1 ? [inputColors[0], inputColors[0], inputColors[0]] : inputColors
let width = Int(size.width) let width = Int(size.width)
@ -179,7 +179,23 @@ private func generateGradient(size: CGSize, colors inputColors: [UIColor], posit
adjustSaturationInContext(context: context, saturation: adjustSaturation) adjustSaturationInContext(context: context, saturation: adjustSaturation)
} }
return context.generateImage()! var hashString = ""
hashString.append("\(size.width)x\(size.height)")
for color in colors {
hashString.append("_\(color.argb)")
}
for position in positions {
hashString.append("_\(position.x):\(position.y)")
}
hashString.append("_\(adjustSaturation)")
return (context.generateImage()!, hashString)
}
public protocol GradientBackgroundPatternOverlayLayer: CALayer {
var isAnimating: Bool { get set }
func updateCompositionData(size: CGSize, backgroundImage: UIImage, backgroundImageHash: String)
} }
public final class GradientBackgroundNode: ASDisplayNode { public final class GradientBackgroundNode: ASDisplayNode {
@ -218,12 +234,14 @@ public final class GradientBackgroundNode: ASDisplayNode {
public static func generatePreview(size: CGSize, colors: [UIColor]) -> UIImage { public static func generatePreview(size: CGSize, colors: [UIColor]) -> UIImage {
let positions = gatherPositions(shiftArray(array: GradientBackgroundNode.basePositions, offset: 0)) let positions = gatherPositions(shiftArray(array: GradientBackgroundNode.basePositions, offset: 0))
return generateGradient(size: size, colors: colors, positions: positions) return generateGradient(size: size, colors: colors, positions: positions).0
} }
private var colors: [UIColor] private var colors: [UIColor]
private var phase: Int = 0 private var phase: Int = 0
private var backgroundImageHash: String?
public let contentView: UIImageView public let contentView: UIImageView
private var validPhase: Int? private var validPhase: Int?
private var invalidated: Bool = false private var invalidated: Bool = false
@ -234,7 +252,7 @@ public final class GradientBackgroundNode: ASDisplayNode {
if let current = self._dimmedImage { if let current = self._dimmedImage {
return current return current
} else if let (size, colors, positions) = self.dimmedImageParams { } else if let (size, colors, positions) = self.dimmedImageParams {
self._dimmedImage = generateGradient(size: size, colors: colors, positions: positions, adjustSaturation: self.saturation) self._dimmedImage = generateGradient(size: size, colors: colors, positions: positions, adjustSaturation: self.saturation).0
return self._dimmedImage return self._dimmedImage
} else { } else {
return nil return nil
@ -247,8 +265,12 @@ public final class GradientBackgroundNode: ASDisplayNode {
private let useSharedAnimationPhase: Bool private let useSharedAnimationPhase: Bool
static var sharedPhase: Int = 0 static var sharedPhase: Int = 0
private var isAnimating: Bool = false
private let saturation: CGFloat private let saturation: CGFloat
private var patternOverlayLayer: GradientBackgroundPatternOverlayLayer?
public init(colors: [UIColor]? = nil, useSharedAnimationPhase: Bool = false, adjustSaturation: Bool = true) { public init(colors: [UIColor]? = nil, useSharedAnimationPhase: Bool = false, adjustSaturation: Bool = true) {
self.useSharedAnimationPhase = useSharedAnimationPhase self.useSharedAnimationPhase = useSharedAnimationPhase
self.saturation = adjustSaturation ? 1.7 : 1.0 self.saturation = adjustSaturation ? 1.7 : 1.0
@ -275,6 +297,31 @@ public final class GradientBackgroundNode: ASDisplayNode {
deinit { deinit {
} }
public func setPatternOverlay(layer: GradientBackgroundPatternOverlayLayer?) {
if self.patternOverlayLayer === layer {
return
}
if let patternOverlayLayer = self.patternOverlayLayer {
if patternOverlayLayer.superlayer == self.layer {
patternOverlayLayer.removeFromSuperlayer()
}
self.patternOverlayLayer = nil
}
self.patternOverlayLayer = layer
if let patternOverlayLayer = self.patternOverlayLayer {
self.layer.addSublayer(patternOverlayLayer)
patternOverlayLayer.isAnimating = self.isAnimating
if let image = self.contentView.image, let backgroundImageHash = self.backgroundImageHash, self.contentView.bounds.width > 1.0, self.contentView.bounds.height > 1.0 {
patternOverlayLayer.updateCompositionData(size: self.contentView.bounds.size, backgroundImage: image, backgroundImageHash: backgroundImageHash)
}
}
}
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, extendAnimation: Bool, backwards: Bool, completion: @escaping () -> Void) { public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, extendAnimation: Bool, backwards: Bool, completion: @escaping () -> Void) {
let sizeUpdated = self.validLayout != size let sizeUpdated = self.validLayout != size
self.validLayout = size self.validLayout = size
@ -283,6 +330,9 @@ public final class GradientBackgroundNode: ASDisplayNode {
let positions = gatherPositions(shiftArray(array: GradientBackgroundNode.basePositions, offset: self.phase % 8)) let positions = gatherPositions(shiftArray(array: GradientBackgroundNode.basePositions, offset: self.phase % 8))
let previousImage = self.contentView.image
let previousSize = self.contentView.bounds.size
if let validPhase = self.validPhase { if let validPhase = self.validPhase {
if validPhase != self.phase || self.invalidated { if validPhase != self.phase || self.invalidated {
self.validPhase = self.phase self.validPhase = self.phase
@ -318,7 +368,7 @@ public final class GradientBackgroundNode: ASDisplayNode {
} }
if case let .animated(duration, curve) = transition, duration > 0.001 { if case let .animated(duration, curve) = transition, duration > 0.001 {
var images: [UIImage] = [] var images: [(UIImage, String)] = []
var dimmedImages: [UIImage] = [] var dimmedImages: [UIImage] = []
let needDimmedImages = !self.cloneNodes.isEmpty let needDimmedImages = !self.cloneNodes.isEmpty
@ -350,16 +400,17 @@ public final class GradientBackgroundNode: ASDisplayNode {
images.append(generateGradient(size: imageSize, colors: self.colors, positions: morphedPositions)) images.append(generateGradient(size: imageSize, colors: self.colors, positions: morphedPositions))
if needDimmedImages { if needDimmedImages {
dimmedImages.append(generateGradient(size: imageSize, colors: self.colors, positions: morphedPositions, adjustSaturation: self.saturation)) dimmedImages.append(generateGradient(size: imageSize, colors: self.colors, positions: morphedPositions, adjustSaturation: self.saturation).0)
} }
} }
self.dimmedImageParams = (imageSize, self.colors, gatherPositions(shiftArray(array: GradientBackgroundNode.basePositions, offset: self.phase % 8))) self.dimmedImageParams = (imageSize, self.colors, gatherPositions(shiftArray(array: GradientBackgroundNode.basePositions, offset: self.phase % 8)))
self.contentView.image = images.last self.contentView.image = images[images.count - 1].0
self.backgroundImageHash = images[images.count - 1].1
let animation = CAKeyframeAnimation(keyPath: "contents") let animation = CAKeyframeAnimation(keyPath: "contents")
animation.values = images.map { $0.cgImage! } animation.values = images.map { $0.0.cgImage! }
animation.duration = duration * UIView.animationDurationFactor() animation.duration = duration * UIView.animationDurationFactor()
if backwards || extendAnimation { if backwards || extendAnimation {
animation.calculationMode = .discrete animation.calculationMode = .discrete
@ -372,7 +423,19 @@ public final class GradientBackgroundNode: ASDisplayNode {
animation.beginTime = self.contentView.layer.convertTime(CACurrentMediaTime(), from: nil) + 0.25 animation.beginTime = self.contentView.layer.convertTime(CACurrentMediaTime(), from: nil) + 0.25
} }
animation.completion = { _ in
self.isAnimating = true
if let patternOverlayLayer = self.patternOverlayLayer {
patternOverlayLayer.isAnimating = true
}
animation.completion = { [weak self] value in
if let strongSelf = self, value {
strongSelf.isAnimating = false
if let patternOverlayLayer = strongSelf.patternOverlayLayer {
patternOverlayLayer.isAnimating = false
}
}
completion() completion()
} }
@ -399,10 +462,11 @@ public final class GradientBackgroundNode: ASDisplayNode {
} }
} }
} else { } else {
let image = generateGradient(size: imageSize, colors: self.colors, positions: positions) let (image, imageHash) = generateGradient(size: imageSize, colors: self.colors, positions: positions)
self.contentView.image = image self.contentView.image = image
self.backgroundImageHash = imageHash
let dimmedImage = generateGradient(size: imageSize, colors: self.colors, positions: positions, adjustSaturation: self.saturation) let dimmedImage = generateGradient(size: imageSize, colors: self.colors, positions: positions, adjustSaturation: self.saturation).0
self._dimmedImage = dimmedImage self._dimmedImage = dimmedImage
self.dimmedImageParams = (imageSize, self.colors, positions) self.dimmedImageParams = (imageSize, self.colors, positions)
@ -412,14 +476,30 @@ public final class GradientBackgroundNode: ASDisplayNode {
completion() completion()
} }
} else if sizeUpdated {
let (image, imageHash) = generateGradient(size: imageSize, colors: self.colors, positions: positions)
self.contentView.image = image
self.backgroundImageHash = imageHash
let dimmedImage = generateGradient(size: imageSize, colors: self.colors, positions: positions, adjustSaturation: self.saturation).0
self.dimmedImageParams = (imageSize, self.colors, positions)
for cloneNode in self.cloneNodes {
cloneNode.value?.image = dimmedImage
}
self.validPhase = self.phase
completion()
} else { } else {
completion() completion()
} }
} else if sizeUpdated { } else if sizeUpdated {
let image = generateGradient(size: imageSize, colors: self.colors, positions: positions) let (image, imageHash) = generateGradient(size: imageSize, colors: self.colors, positions: positions)
self.contentView.image = image self.contentView.image = image
self.backgroundImageHash = imageHash
let dimmedImage = generateGradient(size: imageSize, colors: self.colors, positions: positions, adjustSaturation: self.saturation) let dimmedImage = generateGradient(size: imageSize, colors: self.colors, positions: positions, adjustSaturation: self.saturation).0
self.dimmedImageParams = (imageSize, self.colors, positions) self.dimmedImageParams = (imageSize, self.colors, positions)
for cloneNode in self.cloneNodes { for cloneNode in self.cloneNodes {
@ -434,6 +514,12 @@ public final class GradientBackgroundNode: ASDisplayNode {
} }
transition.updateFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size))
if self.contentView.image !== previousImage || self.contentView.bounds.size != previousSize {
if let patternOverlayLayer = self.patternOverlayLayer, let imageHash = self.backgroundImageHash, let image = self.contentView.image, self.contentView.bounds.width > 1.0, self.contentView.bounds.height > 1.0 {
patternOverlayLayer.updateCompositionData(size: size, backgroundImage: image, backgroundImageHash: imageHash)
}
}
} }
public func updateColors(colors: [UIColor]) { public func updateColors(colors: [UIColor]) {

View File

@ -1522,52 +1522,56 @@ public final class EmojiPagerContentComponent: Component {
if let clearIconLayer = groupHeader.clearIconLayer, clearIconLayer.frame.insetBy(dx: -4.0, dy: -4.0).contains(groupHeaderPoint) { if let clearIconLayer = groupHeader.clearIconLayer, clearIconLayer.frame.insetBy(dx: -4.0, dy: -4.0).contains(groupHeaderPoint) {
component.inputInteraction.clearGroup(id) component.inputInteraction.clearGroup(id)
} }
/*for group in component.itemGroups {
if group.groupId == id {
if group.isPremium && !self.expandedPremiumGroups.contains(id) {
if self.expandedPremiumGroups.contains(id) {
self.expandedPremiumGroups.remove(id)
} else {
self.expandedPremiumGroups.insert(id)
}
let previousItemLayout = self.itemLayout
let transition = Transition(animation: .curve(duration: 0.25, curve: .easeInOut))
self.state?.updated(transition: transition)
if let previousItemLayout = previousItemLayout, let itemLayout = self.itemLayout {
let boundsOffset = itemLayout.contentSize.height - previousItemLayout.contentSize.height
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: self.scrollView.contentOffset.y + boundsOffset), animated: false)
transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: -boundsOffset), to: CGPoint(), additive: true)
}
return
} else {
break outer
}
}
}*/
} }
} }
var foundExactItem = false
if let (item, itemKey) = self.item(atPoint: recognizer.location(in: self)), let itemLayer = self.visibleItemLayers[itemKey] { if let (item, itemKey) = self.item(atPoint: recognizer.location(in: self)), let itemLayer = self.visibleItemLayers[itemKey] {
foundExactItem = true
if !itemLayer.displayPlaceholder {
component.inputInteraction.performItemAction(item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer)
}
}
if !foundExactItem {
if let (item, itemKey) = self.item(atPoint: recognizer.location(in: self), extendedHitRange: true), let itemLayer = self.visibleItemLayers[itemKey] {
if !itemLayer.displayPlaceholder { if !itemLayer.displayPlaceholder {
component.inputInteraction.performItemAction(item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer) component.inputInteraction.performItemAction(item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer)
} }
} }
} }
} }
}
private func item(atPoint point: CGPoint) -> (Item, ItemLayer.Key)? { private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (Item, ItemLayer.Key)? {
let localPoint = self.convert(point, to: self.scrollView) let localPoint = self.convert(point, to: self.scrollView)
var closestItem: (key: ItemLayer.Key, distance: CGFloat)?
for (key, itemLayer) in self.visibleItemLayers { for (key, itemLayer) in self.visibleItemLayers {
if extendedHitRange {
let position = CGPoint(x: itemLayer.frame.midX, y: itemLayer.frame.midY)
let distance = CGPoint(x: localPoint.x - position.x, y: localPoint.y - position.y)
let distance2 = distance.x * distance.x + distance.y * distance.y
if let closestItemValue = closestItem {
if closestItemValue.distance > distance2 {
closestItem = (key, distance2)
}
} else {
closestItem = (key, distance2)
}
} else {
if itemLayer.frame.contains(localPoint) { if itemLayer.frame.contains(localPoint) {
return (itemLayer.item, key) return (itemLayer.item, key)
} }
} }
}
if let key = closestItem?.key {
if let itemLayer = self.visibleItemLayers[key] {
return (itemLayer.item, key)
}
}
return nil return nil
} }
@ -1724,7 +1728,7 @@ public final class EmojiPagerContentComponent: Component {
let groupBorderRadius: CGFloat = 16.0 let groupBorderRadius: CGFloat = 16.0
if itemGroup.isPremiumLocked { if itemGroup.isPremiumLocked && !itemGroup.isFeatured {
validGroupBorderIds.insert(itemGroup.groupId) validGroupBorderIds.insert(itemGroup.groupId)
let groupBorderLayer: GroupBorderLayer let groupBorderLayer: GroupBorderLayer
var groupBorderTransition = transition var groupBorderTransition = transition

View File

@ -298,6 +298,7 @@ public final class EntityKeyboardComponent: Component {
if let stickerContent = component.stickerContent { if let stickerContent = component.stickerContent {
var topStickerItems: [EntityKeyboardTopPanelComponent.Item] = [] var topStickerItems: [EntityKeyboardTopPanelComponent.Item] = []
for itemGroup in stickerContent.itemGroups { for itemGroup in stickerContent.itemGroups {
if let id = itemGroup.supergroupId.base as? String { if let id = itemGroup.supergroupId.base as? String {
let iconMapping: [String: String] = [ let iconMapping: [String: String] = [

View File

@ -1063,6 +1063,10 @@ final class EntityKeyboardTopPanelComponent: Component {
private var currentReorderingItemId: AnyHashable? private var currentReorderingItemId: AnyHashable?
private var currentReorderingItemContainerView: UIView? private var currentReorderingItemContainerView: UIView?
private var initialReorderingItemFrame: CGRect? private var initialReorderingItemFrame: CGRect?
private var currentReorderingScrollDisplayLink: ConstantDisplayLinkAnimator?
private lazy var reorderingHapticFeedback: HapticFeedback = {
return HapticFeedback()
}()
private var itemLayout: ItemLayout? private var itemLayout: ItemLayout?
private var items: [Item] = [] private var items: [Item] = []
@ -1164,7 +1168,11 @@ final class EntityKeyboardTopPanelComponent: Component {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let wasReordering = strongSelf.isReordering
strongSelf.updateIsReordering(isActive) strongSelf.updateIsReordering(isActive)
if !isActive, wasReordering {
strongSelf.endReordering()
}
} }
) )
self.reorderGestureRecognizer = reorderGestureRecognizer self.reorderGestureRecognizer = reorderGestureRecognizer
@ -1327,6 +1335,18 @@ final class EntityKeyboardTopPanelComponent: Component {
if let componentView = itemView.componentView { if let componentView = itemView.componentView {
reorderingItemContainerView.addSubview(componentView) reorderingItemContainerView.addSubview(componentView)
} }
self.reorderingHapticFeedback.impact()
if self.currentReorderingScrollDisplayLink == nil {
self.currentReorderingScrollDisplayLink = ConstantDisplayLinkAnimator(update: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.updateReorderingAutoscroll()
})
self.currentReorderingScrollDisplayLink?.isPaused = false
}
} }
private func endReordering() { private func endReordering() {
@ -1348,6 +1368,11 @@ final class EntityKeyboardTopPanelComponent: Component {
reorderingItemContainerView.removeFromSuperview() reorderingItemContainerView.removeFromSuperview()
} }
if let currentReorderingScrollDisplayLink = self.currentReorderingScrollDisplayLink {
self.currentReorderingScrollDisplayLink = nil
currentReorderingScrollDisplayLink.invalidate()
}
self.currentReorderingItemId = nil self.currentReorderingItemId = nil
self.temporaryReorderingOrderIndex = nil self.temporaryReorderingOrderIndex = nil
@ -1361,15 +1386,23 @@ final class EntityKeyboardTopPanelComponent: Component {
} }
reorderingItemContainerView.frame = initialReorderingItemFrame.offsetBy(dx: offset, dy: 0.0) reorderingItemContainerView.frame = initialReorderingItemFrame.offsetBy(dx: offset, dy: 0.0)
let localReorderingItemFrame = reorderingItemContainerView.convert(reorderingItemContainerView.bounds, to: self.scrollView)
for i in 0 ..< self.items.count { for i in 0 ..< self.items.count {
if !self.items[i].isReorderable { if !self.items[i].isReorderable {
continue continue
} }
let containerFrame = itemLayout.containerFrame(at: i) let containerFrame = itemLayout.containerFrame(at: i)
if containerFrame.intersects(reorderingItemContainerView.frame) { if containerFrame.intersects(localReorderingItemFrame) {
let temporaryReorderingOrderIndex: (id: AnyHashable, index: Int) = (currentReorderingItemId, i) let temporaryReorderingOrderIndex: (id: AnyHashable, index: Int) = (currentReorderingItemId, i)
let hadPrevous = self.temporaryReorderingOrderIndex != nil
if self.temporaryReorderingOrderIndex?.id != temporaryReorderingOrderIndex.id || self.temporaryReorderingOrderIndex?.index != temporaryReorderingOrderIndex.index { if self.temporaryReorderingOrderIndex?.id != temporaryReorderingOrderIndex.id || self.temporaryReorderingOrderIndex?.index != temporaryReorderingOrderIndex.index {
self.temporaryReorderingOrderIndex = temporaryReorderingOrderIndex self.temporaryReorderingOrderIndex = temporaryReorderingOrderIndex
if hadPrevous {
self.reorderingHapticFeedback.tap()
}
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring))) self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
} }
break break
@ -1377,6 +1410,34 @@ final class EntityKeyboardTopPanelComponent: Component {
} }
} }
private func updateReorderingAutoscroll() {
guard let reorderingItemContainerView = self.currentReorderingItemContainerView, let initialReorderingItemFrame = self.initialReorderingItemFrame else {
return
}
var bounds = self.scrollView.bounds
let delta: CGFloat = 3.0
if reorderingItemContainerView.frame.minX < 16.0 {
bounds.origin.x -= delta
} else if reorderingItemContainerView.frame.maxX > self.scrollView.bounds.width - 16.0 {
bounds.origin.x += delta
}
if bounds.origin.x + bounds.size.width > self.scrollView.contentSize.width {
bounds.origin.x = self.scrollView.contentSize.width - bounds.size.width
}
if bounds.origin.x < 0.0 {
bounds.origin.x = 0.0
}
if self.scrollView.bounds != bounds {
self.scrollView.bounds = bounds
let offset = reorderingItemContainerView.frame.minX - initialReorderingItemFrame.minX
self.updateReordering(offset: offset)
}
}
private func updateVisibleItems(attemptSynchronousLoads: Bool, transition: Transition) { private func updateVisibleItems(attemptSynchronousLoads: Bool, transition: Transition) {
guard let itemLayout = self.itemLayout else { guard let itemLayout = self.itemLayout else {
return return
@ -1708,6 +1769,16 @@ final class EntityKeyboardTopPanelComponent: Component {
let _ = itemLayout let _ = itemLayout
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
if let component = self.component, let itemLayout = self.itemLayout {
for i in 0 ..< component.items.count {
if component.items[i].id == itemId {
let itemFrame = itemLayout.containerFrame(at: i)
self.scrollView.scrollRectToVisible(itemFrame.insetBy(dx: -2.0, dy: 0.0), animated: true)
break
}
}
}
/*var found = false /*var found = false
for i in 0 ..< self.items.count { for i in 0 ..< self.items.count {
if self.items[i].id == itemId { if self.items[i].id == itemId {

View File

@ -236,7 +236,8 @@ public final class TextNodeWithEntities {
if let current = self.inlineStickerItemLayers[id] { if let current = self.inlineStickerItemLayers[id] {
itemLayer = current itemLayer = current
} else { } else {
itemLayer = InlineStickerItemLayer(context: context, attemptSynchronousLoad: attemptSynchronousLoad, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: floor(itemSize * 1.2), height: floor(itemSize * 1.2))) let pointSize = floor(itemSize * 1.3)
itemLayer = InlineStickerItemLayer(context: context, attemptSynchronousLoad: attemptSynchronousLoad, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize))
self.inlineStickerItemLayers[id] = itemLayer self.inlineStickerItemLayers[id] = itemLayer
self.textNode.layer.addSublayer(itemLayer) self.textNode.layer.addSublayer(itemLayer)
@ -399,7 +400,8 @@ public class ImmediateTextNodeWithEntities: TextNode {
if let current = self.inlineStickerItemLayers[id] { if let current = self.inlineStickerItemLayers[id] {
itemLayer = current itemLayer = current
} else { } else {
itemLayer = InlineStickerItemLayer(context: context, attemptSynchronousLoad: false, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: itemSize, height: itemSize)) let pointSize = floor(itemSize * 1.3)
itemLayer = InlineStickerItemLayer(context: context, attemptSynchronousLoad: false, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize))
self.inlineStickerItemLayers[id] = itemLayer self.inlineStickerItemLayers[id] = itemLayer
self.layer.addSublayer(itemLayer) self.layer.addSublayer(itemLayer)

View File

@ -496,9 +496,10 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
let stickerItems: Signal<EmojiPagerContentComponent, NoError> = combineLatest( let stickerItems: Signal<EmojiPagerContentComponent, NoError> = combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: stickerOrderedItemListCollectionIds, namespaces: stickerNamespaces, aroundIndex: nil, count: 10000000), context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: stickerOrderedItemListCollectionIds, namespaces: stickerNamespaces, aroundIndex: nil, count: 10000000),
hasPremium hasPremium,
context.account.viewTracker.featuredStickerPacks()
) )
|> map { view, hasPremium -> EmojiPagerContentComponent in |> map { view, hasPremium, featuredStickerPacks -> EmojiPagerContentComponent in
struct ItemGroup { struct ItemGroup {
var supergroupId: AnyHashable var supergroupId: AnyHashable
var id: AnyHashable var id: AnyHashable
@ -630,6 +631,11 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
} }
var installedCollectionIds = Set<ItemCollectionId>()
for (id, _, _) in view.collectionInfos {
installedCollectionIds.insert(id)
}
for entry in view.entries { for entry in view.entries {
guard let item = entry.item as? StickerPackItem else { guard let item = entry.item as? StickerPackItem else {
continue continue
@ -656,6 +662,33 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
} }
for featuredStickerPack in featuredStickerPacks {
if installedCollectionIds.contains(featuredStickerPack.info.id) {
continue
}
for item in featuredStickerPack.topItems {
let resultItem = EmojiPagerContentComponent.Item(
file: item.file,
staticEmoji: nil,
subgroupId: nil
)
let supergroupId = featuredStickerPack.info.id
let groupId: AnyHashable = supergroupId
let isPremiumLocked: Bool = item.file.isPremiumSticker && !hasPremium
if isPremiumLocked && isPremiumDisabled {
continue
}
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: featuredStickerPack.info.title, isPremiumLocked: isPremiumLocked, isFeatured: true, displayPremiumBadges: false, items: [resultItem]))
}
}
}
return EmojiPagerContentComponent( return EmojiPagerContentComponent(
id: "stickers", id: "stickers",
context: context, context: context,

View File

@ -135,7 +135,9 @@ func chatHistoryEntriesForView(
} }
if presentationData.largeEmoji, message.media.isEmpty { if presentationData.largeEmoji, message.media.isEmpty {
if stickersEnabled && message.text.count == 1, let _ = associatedData.animatedEmojiStickers[message.text.basicEmoji.0], (message.textEntitiesAttribute?.entities.isEmpty ?? true) { if stickersEnabled && message.text.count == 1, let entities = message.textEntitiesAttribute?.entities, entities.count == 1, case let .CustomEmoji(_, fileId) = entities[0].type, let _ = message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] {
contentTypeHint = .animatedEmoji
} else if stickersEnabled && message.text.count == 1, let _ = associatedData.animatedEmojiStickers[message.text.basicEmoji.0], (message.textEntitiesAttribute?.entities.isEmpty ?? true) {
contentTypeHint = .animatedEmoji contentTypeHint = .animatedEmoji
} else if message.text.count < 10 && messageIsElligibleForLargeEmoji(message) { } else if message.text.count < 10 && messageIsElligibleForLargeEmoji(message) {
contentTypeHint = .largeEmoji contentTypeHint = .largeEmoji

View File

@ -518,9 +518,17 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
} else if self.telegramFile == nil && self.telegramDice == nil { } else if self.telegramFile == nil && self.telegramDice == nil {
let (emoji, fitz) = item.message.text.basicEmoji let (emoji, fitz) = item.message.text.basicEmoji
var emojiFile: TelegramMediaFile?
var emojiFile: TelegramMediaFile?
if let entities = item.message.textEntitiesAttribute?.entities, entities.count == 1, case let .CustomEmoji(_, fileId) = entities[0].type {
if let file = item.message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile {
emojiFile = file
}
}
if emojiFile == nil {
emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.first?.file emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.first?.file
}
if emojiFile == nil { if emojiFile == nil {
emojiFile = item.associatedData.animatedEmojiStickers[emoji.strippedEmoji]?.first?.file emojiFile = item.associatedData.animatedEmojiStickers[emoji.strippedEmoji]?.first?.file
} }
@ -547,11 +555,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
var animationItems: [Int: StickerPackItem]? var animationItems: [Int: StickerPackItem]?
if let emojiFile = emojiFile, emojiFile.isCustomEmoji {
} else {
if let items = item.associatedData.additionalAnimatedEmojiStickers[textEmoji] { if let items = item.associatedData.additionalAnimatedEmojiStickers[textEmoji] {
animationItems = items animationItems = items
} else if let items = item.associatedData.additionalAnimatedEmojiStickers[additionalTextEmoji] { } else if let items = item.associatedData.additionalAnimatedEmojiStickers[additionalTextEmoji] {
animationItems = items animationItems = items
} }
}
if let animationItems = animationItems { if let animationItems = animationItems {
for (_, animationItem) in animationItems { for (_, animationItem) in animationItems {
@ -580,18 +591,20 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
playbackMode = .once playbackMode = .once
} }
} else if let emojiFile = self.emojiFile { } else if let emojiFile = self.emojiFile {
isEmoji = true
file = emojiFile file = emojiFile
//if alreadySeen && emojiFile.resource is LocalFileReferenceMediaResource {
if emojiFile.isCustomEmoji {
playbackMode = .loop
} else {
isEmoji = true
playbackMode = .still(.end) playbackMode = .still(.end)
//} else {
// playbackMode = .once
//}
let (_, fitz) = item.message.text.basicEmoji let (_, fitz) = item.message.text.basicEmoji
if let fitz = fitz { if let fitz = fitz {
fitzModifier = EmojiFitzModifier(emoji: fitz) fitzModifier = EmojiFitzModifier(emoji: fitz)
} }
} }
}
let isPlaying = self.visibilityStatus == true && !self.forceStopAnimations let isPlaying = self.visibilityStatus == true && !self.forceStopAnimations
if !isPlaying { if !isPlaying {
@ -1805,7 +1818,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
return .optionalAction({ return .optionalAction({
item.controllerInteraction.displayDiceTooltip(dice) item.controllerInteraction.displayDiceTooltip(dice)
}) })
} else if let _ = self.emojiFile { } else if let emojiFile = self.emojiFile, !emojiFile.isCustomEmoji {
if let animationNode = self.animationNode as? AnimatedStickerNode, let _ = recognizer { if let animationNode = self.animationNode as? AnimatedStickerNode, let _ = recognizer {
var shouldPlay = false var shouldPlay = false
if !animationNode.isPlaying { if !animationNode.isPlaying {

View File

@ -809,7 +809,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
return UIView() return UIView()
} }
return EmojiTextAttachmentView(context: context, emoji: emoji, file: emoji.file, cache: presentationContext.animationCache, renderer: presentationContext.animationRenderer, placeholderColor: presentationInterfaceState.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12), pointSize: CGSize(width: 24.0, height: 24.0)) let pointSize = floor(24.0 * 1.3)
return EmojiTextAttachmentView(context: context, emoji: emoji, file: emoji.file, cache: presentationContext.animationCache, renderer: presentationContext.animationRenderer, placeholderColor: presentationInterfaceState.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12), pointSize: CGSize(width: pointSize, height: pointSize))
} }
} }
} }

View File

@ -64,6 +64,208 @@ public protocol WallpaperBackgroundNode: ASDisplayNode {
func makeDimmedNode() -> ASDisplayNode? func makeDimmedNode() -> ASDisplayNode?
} }
private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOverlayLayer {
enum SoftlightMode {
case whileAnimating
case always
case never
}
var patternContentImage: UIImage? {
didSet {
if self.patternContentImage !== oldValue {
self.updateComposedImage()
self.updateContents()
}
}
}
var composedContentImage: UIImage? {
didSet {
if self.composedContentImage !== oldValue {
self.updateContents()
}
}
}
var softlightMode: SoftlightMode = .whileAnimating {
didSet {
if self.softlightMode != oldValue {
self.updateFilters()
}
}
}
var isAnimating: Bool = false {
didSet {
if self.isAnimating != oldValue {
self.updateFilters()
}
}
}
private var isUsingSoftlight: Bool = false
var suspendCompositionUpdates: Bool = false
private var needsCompositionUpdate: Bool = false
private func updateFilters() {
let useSoftlight: Bool
switch self.softlightMode {
case .whileAnimating:
useSoftlight = self.isAnimating
case .always:
useSoftlight = true
case .never:
useSoftlight = false
}
if self.isUsingSoftlight != useSoftlight {
self.isUsingSoftlight = useSoftlight
if self.isUsingSoftlight {
self.compositingFilter = "softLightBlendMode"
} else {
self.compositingFilter = nil
}
self.updateContents()
self.updateOpacity()
}
}
private var allowSettingContents: Bool = false
private var currentContents: UIImage?
override var contents: Any? {
get {
return super.contents
} set(value) {
if self.allowSettingContents {
super.contents = value
} else {
assert(false)
}
}
}
private var allowSettingOpacity: Bool = false
var compositionOpacity: Float = 1.0 {
didSet {
if self.compositionOpacity != oldValue {
self.updateOpacity()
self.updateComposedImage()
}
}
}
override var opacity: Float {
get {
return super.opacity
} set(value) {
if self.allowSettingOpacity {
super.opacity = value
} else {
assert(false)
}
}
}
private var compositionData: (size: CGSize, backgroundImage: UIImage, backgroundImageHash: String)?
func updateCompositionData(size: CGSize, backgroundImage: UIImage, backgroundImageHash: String) {
if self.compositionData?.size == size && self.compositionData?.backgroundImage === backgroundImage {
return
}
self.compositionData = (size, backgroundImage, backgroundImageHash)
self.updateComposedImage()
}
func updateCompositionIfNeeded() {
if self.needsCompositionUpdate {
self.needsCompositionUpdate = false
self.updateComposedImage()
}
}
private static var cachedComposedImage: (size: CGSize, patternContentImage: UIImage, backgroundImageHash: String, image: UIImage)?
private func updateComposedImage() {
if self.suspendCompositionUpdates {
self.needsCompositionUpdate = true
return
}
switch self.softlightMode {
case .always:
return
default:
break
}
guard let (size, backgroundImage, backgroundImageHash) = self.compositionData, let patternContentImage = self.patternContentImage else {
return
}
if let cachedComposedImage = EffectImageLayer.cachedComposedImage, cachedComposedImage.size == size, cachedComposedImage.backgroundImageHash == backgroundImageHash, cachedComposedImage.patternContentImage === patternContentImage {
self.composedContentImage = cachedComposedImage.image
return
}
#if DEBUG
let startTime = CFAbsoluteTimeGetCurrent()
#endif
let composedContentImage = generateImage(size, contextGenerator: { size, context in
context.draw(backgroundImage.cgImage!, in: CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.softLight)
context.setAlpha(CGFloat(self.compositionOpacity))
context.draw(patternContentImage.cgImage!, in: CGRect(origin: CGPoint(), size: size))
}, opaque: true, scale: min(UIScreenScale, patternContentImage.scale))
self.composedContentImage = composedContentImage
#if DEBUG
print("Wallpaper composed image updated in \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
#endif
if self.softlightMode == .whileAnimating, let composedContentImage = composedContentImage {
EffectImageLayer.cachedComposedImage = (size, patternContentImage, backgroundImageHash, composedContentImage)
}
}
private func updateContents() {
var contents: UIImage?
if self.isUsingSoftlight {
contents = self.patternContentImage
} else {
contents = self.composedContentImage
}
if self.currentContents !== contents {
self.currentContents = contents
self.allowSettingContents = true
self.contents = contents?.cgImage
self.allowSettingContents = false
}
}
private func updateOpacity() {
if self.isUsingSoftlight {
self.allowSettingOpacity = true
self.opacity = self.compositionOpacity
self.allowSettingOpacity = false
self.isOpaque = false
} else {
self.allowSettingOpacity = true
self.opacity = 1.0
self.allowSettingOpacity = false
self.isOpaque = true
}
}
}
final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode { final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode {
final class BubbleBackgroundNodeImpl: ASDisplayNode, WallpaperBubbleBackgroundNode { final class BubbleBackgroundNodeImpl: ASDisplayNode, WallpaperBubbleBackgroundNode {
private let bubbleType: WallpaperBubbleType private let bubbleType: WallpaperBubbleType
@ -348,7 +550,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
private var gradientBackgroundNode: GradientBackgroundNode? private var gradientBackgroundNode: GradientBackgroundNode?
private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode? private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode?
private let patternImageNode: ASImageNode private let patternImageLayer: EffectImageLayer
private var isGeneratingPatternImage: Bool = false private var isGeneratingPatternImage: Bool = false
private let bakedBackgroundView: UIImageView private let bakedBackgroundView: UIImageView
@ -466,7 +668,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
self.contentNode = ASDisplayNode() self.contentNode = ASDisplayNode()
self.contentNode.contentMode = self.imageContentMode self.contentNode.contentMode = self.imageContentMode
self.patternImageNode = ASImageNode() self.patternImageLayer = EffectImageLayer()
self.bakedBackgroundView = UIImageView() self.bakedBackgroundView = UIImageView()
self.bakedBackgroundView.isHidden = true self.bakedBackgroundView.isHidden = true
@ -476,49 +678,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
self.clipsToBounds = true self.clipsToBounds = true
self.contentNode.frame = self.bounds self.contentNode.frame = self.bounds
self.addSubnode(self.contentNode) self.addSubnode(self.contentNode)
self.addSubnode(self.patternImageNode) self.layer.addSublayer(self.patternImageLayer)
/*let animationList: [(String, CGPoint)] = [
("ptrnCAT_1162_1918", CGPoint(x: 1162 - 256, y: 1918 - 256)),
("ptrnDOG_0440_2284", CGPoint(x: 440 - 256, y: 2284 - 256)),
("ptrnGLOB_0438_1553", CGPoint(x: 438 - 256, y: 1553 - 256)),
("ptrnSLON_0906_1033", CGPoint(x: 906 - 256, y: 1033 - 256))
]
for (animation, relativePosition) in animationList {
let animationNode = DefaultAnimatedStickerNodeImpl()
animationNode.automaticallyLoadFirstFrame = true
animationNode.autoplay = true
//self.inlineAnimationNodes.append((animationNode, relativePosition))
self.patternImageNode.addSubnode(animationNode)
animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animation), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
}
self.layer.addSublayer(self.hierarchyTrackingLayer)
self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
guard let strongSelf = self else {
return
}
for (animationNode, _) in strongSelf.inlineAnimationNodes {
animationNode.visibility = true
}
strongSelf.activateInlineAnimationTimer = SwiftSignalKit.Timer(timeout: 5.0, repeat: true, completion: {
guard let strongSelf = self else {
return
}
strongSelf.inlineAnimationNodes[Int.random(in: 0 ..< strongSelf.inlineAnimationNodes.count)].0.play()
}, queue: .mainQueue())
strongSelf.activateInlineAnimationTimer?.start()
}
self.hierarchyTrackingLayer.didExitHierarchy = { [weak self] in
guard let strongSelf = self else {
return
}
for (animationNode, _) in strongSelf.inlineAnimationNodes {
animationNode.visibility = false
}
strongSelf.activateInlineAnimationTimer?.invalidate()
}*/
} }
deinit { deinit {
@ -556,7 +716,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
let gradientBackgroundNode = createGradientBackgroundNode(colors: mappedColors, useSharedAnimationPhase: self.useSharedAnimationPhase) let gradientBackgroundNode = createGradientBackgroundNode(colors: mappedColors, useSharedAnimationPhase: self.useSharedAnimationPhase)
self.gradientBackgroundNode = gradientBackgroundNode self.gradientBackgroundNode = gradientBackgroundNode
self.insertSubnode(gradientBackgroundNode, aboveSubnode: self.contentNode) self.insertSubnode(gradientBackgroundNode, aboveSubnode: self.contentNode)
gradientBackgroundNode.addSubnode(self.patternImageNode) gradientBackgroundNode.setPatternOverlay(layer: self.patternImageLayer)
} }
self.gradientBackgroundNode?.updateColors(colors: mappedColors) self.gradientBackgroundNode?.updateColors(colors: mappedColors)
@ -569,7 +729,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
if let gradientBackgroundNode = self.gradientBackgroundNode { if let gradientBackgroundNode = self.gradientBackgroundNode {
self.gradientBackgroundNode = nil self.gradientBackgroundNode = nil
gradientBackgroundNode.removeFromSupernode() gradientBackgroundNode.removeFromSupernode()
self.insertSubnode(self.patternImageNode, aboveSubnode: self.contentNode) gradientBackgroundNode.setPatternOverlay(layer: nil)
self.layer.insertSublayer(self.patternImageLayer, above: self.contentNode.layer)
} }
self.motionEnabled = wallpaper.settings?.motion ?? false self.motionEnabled = wallpaper.settings?.motion ?? false
@ -654,40 +815,44 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
let intensity = CGFloat(file.settings.intensity ?? 50) / 100.0 let intensity = CGFloat(file.settings.intensity ?? 50) / 100.0
if intensity < 0 { if intensity < 0 {
self.patternImageNode.alpha = 1.0 self.patternImageLayer.compositionOpacity = 1.0
self.patternImageNode.layer.compositingFilter = nil self.patternImageLayer.softlightMode = .never
} else { } else {
self.patternImageNode.alpha = intensity self.patternImageLayer.compositionOpacity = Float(intensity)
if patternIsBlack { if patternIsBlack {
self.patternImageNode.layer.compositingFilter = nil self.patternImageLayer.softlightMode = .never
} else { } else {
self.patternImageNode.layer.compositingFilter = "softLightBlendMode" if self.useSharedAnimationPhase {
self.patternImageLayer.softlightMode = .whileAnimating
} else {
self.patternImageLayer.softlightMode = .always
}
} }
} }
self.patternImageNode.isHidden = false self.patternImageLayer.isHidden = false
let invertPattern = intensity < 0 let invertPattern = intensity < 0
if invertPattern { if invertPattern {
self.backgroundColor = .black self.backgroundColor = .black
let contentAlpha = abs(intensity) let contentAlpha = abs(intensity)
self.gradientBackgroundNode?.contentView.alpha = contentAlpha self.gradientBackgroundNode?.contentView.alpha = contentAlpha
self.contentNode.alpha = contentAlpha self.contentNode.alpha = contentAlpha
if self.patternImageNode.image != nil { if self.patternImageLayer.contents != nil {
self.patternImageNode.backgroundColor = nil self.patternImageLayer.backgroundColor = nil
} else { } else {
self.patternImageNode.backgroundColor = .black self.patternImageLayer.backgroundColor = UIColor.black.cgColor
} }
} else { } else {
self.backgroundColor = nil self.backgroundColor = nil
self.gradientBackgroundNode?.contentView.alpha = 1.0 self.gradientBackgroundNode?.contentView.alpha = 1.0
self.contentNode.alpha = 1.0 self.contentNode.alpha = 1.0
self.patternImageNode.backgroundColor = nil self.patternImageLayer.backgroundColor = nil
} }
default: default:
self.patternImageDisposable.set(nil) self.patternImageDisposable.set(nil)
self.validPatternImage = nil self.validPatternImage = nil
self.patternImageNode.isHidden = true self.patternImageLayer.isHidden = true
self.patternImageNode.backgroundColor = nil self.patternImageLayer.backgroundColor = nil
self.backgroundColor = nil self.backgroundColor = nil
self.gradientBackgroundNode?.contentView.alpha = 1.0 self.gradientBackgroundNode?.contentView.alpha = 1.0
self.contentNode.alpha = 1.0 self.contentNode.alpha = 1.0
@ -784,10 +949,10 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
if invertPattern { if invertPattern {
patternColor = .clear patternColor = .clear
patternBackgroundColor = .clear patternBackgroundColor = .clear
if self.patternImageNode.image == nil { if self.patternImageLayer.contents == nil {
self.patternImageNode.backgroundColor = .black self.patternImageLayer.backgroundColor = UIColor.black.cgColor
} else { } else {
self.patternImageNode.backgroundColor = nil self.patternImageLayer.backgroundColor = nil
} }
} else { } else {
if patternIsLight { if patternIsLight {
@ -796,7 +961,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
patternColor = .white patternColor = .white
} }
patternBackgroundColor = .clear patternBackgroundColor = .clear
self.patternImageNode.backgroundColor = nil self.patternImageLayer.backgroundColor = nil
} }
let updatedGeneratedImage = ValidPatternGeneratedImage(wallpaper: validPatternImage.wallpaper, size: size, patternColor: patternColor.rgb, backgroundColor: patternBackgroundColor.rgb, invertPattern: invertPattern) let updatedGeneratedImage = ValidPatternGeneratedImage(wallpaper: validPatternImage.wallpaper, size: size, patternColor: patternColor.rgb, backgroundColor: patternBackgroundColor.rgb, invertPattern: invertPattern)
@ -805,15 +970,21 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
self.validPatternGeneratedImage = updatedGeneratedImage self.validPatternGeneratedImage = updatedGeneratedImage
if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated == updatedGeneratedImage { if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated == updatedGeneratedImage {
self.patternImageNode.image = cachedValidPatternImage.image self.patternImageLayer.suspendCompositionUpdates = true
self.updatePatternPresentation() self.updatePatternPresentation()
self.patternImageLayer.patternContentImage = cachedValidPatternImage.image
self.patternImageLayer.suspendCompositionUpdates = false
self.patternImageLayer.updateCompositionIfNeeded()
} else { } 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)) 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 self.useSharedAnimationPhase || self.patternImageLayer.contents == nil {
if let drawingContext = validPatternImage.generate(patternArguments) { if let drawingContext = validPatternImage.generate(patternArguments) {
if let image = drawingContext.generateImage() { if let image = drawingContext.generateImage() {
self.patternImageNode.image = image self.patternImageLayer.suspendCompositionUpdates = true
self.updatePatternPresentation() self.updatePatternPresentation()
self.patternImageLayer.patternContentImage = image
self.patternImageLayer.suspendCompositionUpdates = false
self.patternImageLayer.updateCompositionIfNeeded()
if self.useSharedAnimationPhase { if self.useSharedAnimationPhase {
WallpaperBackgroundNodeImpl.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, image: image) WallpaperBackgroundNodeImpl.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, image: image)
@ -833,7 +1004,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
return return
} }
strongSelf.isGeneratingPatternImage = false strongSelf.isGeneratingPatternImage = false
strongSelf.patternImageNode.image = image strongSelf.patternImageLayer.patternContentImage = image
strongSelf.updatePatternPresentation() strongSelf.updatePatternPresentation()
if let image = image, strongSelf.useSharedAnimationPhase { if let image = image, strongSelf.useSharedAnimationPhase {
@ -856,7 +1027,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
} }
} }
transition.updateFrame(node: self.patternImageNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(layer: self.patternImageLayer, frame: CGRect(origin: CGPoint(), size: size))
} }
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {