Static spoiler for media

This commit is contained in:
Ilya Laktyushin
2023-03-03 17:20:16 +04:00
parent 9fddaf9f96
commit a76f5843d4
6 changed files with 215 additions and 165 deletions

View File

@@ -6,25 +6,11 @@ import AsyncDisplayKit
import Display import Display
import AppBundle import AppBundle
import LegacyComponents import LegacyComponents
import GameplayKit
private struct ArbitraryRandomNumberGenerator : RandomNumberGenerator { struct ArbitraryRandomNumberGenerator : RandomNumberGenerator {
init(seed: Int) { srand48(seed) } init(seed: Int) { srand48(seed) }
func next() -> UInt64 { return UInt64(drand48() * Double(UInt64.max)) } func next() -> UInt64 { return UInt64(drand48() * Double(UInt64.max)) }
} }
// mutating func next() -> UInt64 {
// // GKRandom produces values in [INT32_MIN, INT32_MAX] range; hence we need two numbers to produce 64-bit value.
// let next1 = UInt64(bitPattern: Int64(gkrandom.nextInt()))
// let next2 = UInt64(bitPattern: Int64(gkrandom.nextInt()))
// return next1 ^ (next2 << 32)
// }
//
// init(seed: UInt64) {
// self.gkrandom = GKMersenneTwisterRandomSource(seed: seed)
// }
//
// private let gkrandom: GKRandom
//}
func createEmitterBehavior(type: String) -> NSObject { func createEmitterBehavior(type: String) -> NSObject {
let selector = ["behaviorWith", "Type:"].joined(separator: "") let selector = ["behaviorWith", "Type:"].joined(separator: "")
@@ -323,7 +309,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
} }
print("combining \(CACurrentMediaTime() - start)") print("combining \(CACurrentMediaTime() - start)")
Queue.concurrentDefaultQueue().async {
var generator = ArbitraryRandomNumberGenerator(seed: 1) var generator = ArbitraryRandomNumberGenerator(seed: 1)
let image = generateImage(size, rotatedContext: { size, context in let image = generateImage(size, rotatedContext: { size, context in
let bounds = CGRect(origin: .zero, size: size) let bounds = CGRect(origin: .zero, size: size)
@@ -340,8 +326,11 @@ public class InvisibleInkDustNode: ASDisplayNode {
} }
} }
}) })
self.staticNode?.frame = CGRect(origin: CGPoint(), size: size) Queue.mainQueue().async {
self.staticNode?.image = image self.staticNode?.image = image
}
}
self.staticNode?.frame = CGRect(origin: CGPoint(), size: size)
print("total draw \(CACurrentMediaTime() - start)") print("total draw \(CACurrentMediaTime() - start)")
} }

View File

@@ -92,6 +92,7 @@ public class MediaDustLayer: CALayer {
public class MediaDustNode: ASDisplayNode { public class MediaDustNode: ASDisplayNode {
private var currentParams: (size: CGSize, color: UIColor)? private var currentParams: (size: CGSize, color: UIColor)?
private var animColor: CGColor? private var animColor: CGColor?
private let enableAnimations: Bool
private var emitterNode: ASDisplayNode private var emitterNode: ASDisplayNode
private var emitter: CAEmitterCell? private var emitter: CAEmitterCell?
@@ -101,13 +102,18 @@ public class MediaDustNode: ASDisplayNode {
private let emitterSpotNode: ASImageNode private let emitterSpotNode: ASImageNode
private let emitterMaskFillNode: ASDisplayNode private let emitterMaskFillNode: ASDisplayNode
private var staticNode: ASImageNode?
private var staticParams: CGSize?
public var isRevealed = false public var isRevealed = false
private var isExploding = false private var isExploding = false
public var revealed: () -> Void = {} public var revealed: () -> Void = {}
public var tapped: () -> Void = {} public var tapped: () -> Void = {}
public override init() { public init(enableAnimations: Bool) {
self.enableAnimations = enableAnimations
self.emitterNode = ASDisplayNode() self.emitterNode = ASDisplayNode()
self.emitterNode.isUserInteractionEnabled = false self.emitterNode.isUserInteractionEnabled = false
self.emitterNode.clipsToBounds = true self.emitterNode.clipsToBounds = true
@@ -132,6 +138,7 @@ public class MediaDustNode: ASDisplayNode {
public override func didLoad() { public override func didLoad() {
super.didLoad() super.didLoad()
if self.enableAnimations {
let emitter = CAEmitterCell() let emitter = CAEmitterCell()
emitter.color = UIColor(rgb: 0xffffff, alpha: 0.0).cgColor emitter.color = UIColor(rgb: 0xffffff, alpha: 0.0).cgColor
emitter.contents = UIImage(bundleImageName: "Components/TextSpeckle")?.cgImage emitter.contents = UIImage(bundleImageName: "Components/TextSpeckle")?.cgImage
@@ -191,6 +198,11 @@ public class MediaDustNode: ASDisplayNode {
self.emitterLayer = emitterLayer self.emitterLayer = emitterLayer
self.emitterNode.layer.addSublayer(emitterLayer) self.emitterNode.layer.addSublayer(emitterLayer)
} else {
let staticNode = ASImageNode()
self.staticNode = staticNode
self.addSubnode(staticNode)
}
self.updateEmitter() self.updateEmitter()
@@ -207,6 +219,8 @@ public class MediaDustNode: ASDisplayNode {
self.tapped() self.tapped()
self.isRevealed = true self.isRevealed = true
if self.enableAnimations {
self.isExploding = true self.isExploding = true
let position = gestureRecognizer.location(in: self.view) let position = gestureRecognizer.location(in: self.view)
@@ -251,6 +265,12 @@ public class MediaDustNode: ASDisplayNode {
self.emitterSpotNode.layer.removeAllAnimations() self.emitterSpotNode.layer.removeAllAnimations()
self.emitterMaskFillNode.layer.removeAllAnimations() self.emitterMaskFillNode.layer.removeAllAnimations()
} }
} else {
self.supernode?.alpha = 0.0
self.supernode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, completion: { [weak self] _ in
self?.revealed()
})
}
} }
private var didSetupAnimations = false private var didSetupAnimations = false
@@ -327,7 +347,7 @@ public class MediaDustNode: ASDisplayNode {
guard let (size, _) = self.currentParams else { guard let (size, _) = self.currentParams else {
return return
} }
if self.enableAnimations {
self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size) self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size)
self.emitterLayer?.emitterSize = size self.emitterLayer?.emitterSize = size
self.emitterLayer?.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0) self.emitterLayer?.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
@@ -340,6 +360,37 @@ public class MediaDustNode: ASDisplayNode {
Queue.mainQueue().async { Queue.mainQueue().async {
self.emitter?.birthRate = min(100000.0, square * 0.02) self.emitter?.birthRate = min(100000.0, square * 0.02)
} }
} else {
if let staticParams = self.staticParams, staticParams == size && self.staticNode?.image != nil {
return
}
self.staticParams = size
let start = CACurrentMediaTime()
Queue.concurrentDefaultQueue().async {
var generator = ArbitraryRandomNumberGenerator(seed: 1)
let image = generateImage(size, rotatedContext: { size, context in
let bounds = CGRect(origin: .zero, size: size)
context.clear(bounds)
context.setFillColor(UIColor.white.cgColor)
let rect = CGRect(origin: .zero, size: size)
let rate = Int(rect.width * rect.height * 0.04)
for _ in 0 ..< rate {
let location = CGPoint(x: .random(in: rect.minX ..< rect.maxX, using: &generator), y: .random(in: rect.minY ..< rect.maxY, using: &generator))
context.fillEllipse(in: CGRect(origin: location, size: CGSize(width: 1.0, height: 1.0)))
}
})
Queue.mainQueue().async {
self.staticNode?.image = image
}
}
self.staticNode?.frame = CGRect(origin: CGPoint(), size: size)
print("total draw \(CACurrentMediaTime() - start)")
}
} }
public func update(size: CGSize, color: UIColor, transition: ContainedViewLayoutTransition) { public func update(size: CGSize, color: UIColor, transition: ContainedViewLayoutTransition) {
@@ -351,6 +402,8 @@ public class MediaDustNode: ASDisplayNode {
self.emitterMaskNode.frame = bounds self.emitterMaskNode.frame = bounds
self.emitterMaskFillNode.frame = bounds self.emitterMaskFillNode.frame = bounds
self.staticNode?.frame = bounds
if self.isNodeLoaded { if self.isNodeLoaded {
self.updateEmitter() self.updateEmitter()
self.setupRandomAnimations() self.setupRandomAnimations()

View File

@@ -25,24 +25,26 @@ final class MediaPickerGridItem: GridItem {
let content: MediaPickerGridItemContent let content: MediaPickerGridItemContent
let interaction: MediaPickerInteraction let interaction: MediaPickerInteraction
let theme: PresentationTheme let theme: PresentationTheme
let enableAnimations: Bool
let section: GridSection? = nil let section: GridSection? = nil
init(content: MediaPickerGridItemContent, interaction: MediaPickerInteraction, theme: PresentationTheme) { init(content: MediaPickerGridItemContent, interaction: MediaPickerInteraction, theme: PresentationTheme, enableAnimations: Bool) {
self.content = content self.content = content
self.interaction = interaction self.interaction = interaction
self.theme = theme self.theme = theme
self.enableAnimations = enableAnimations
} }
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
switch self.content { switch self.content {
case let .asset(fetchResult, index): case let .asset(fetchResult, index):
let node = MediaPickerGridItemNode() let node = MediaPickerGridItemNode()
node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme) node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme, enableAnimations: self.enableAnimations)
return node return node
case let .media(media, index): case let .media(media, index):
let node = MediaPickerGridItemNode() let node = MediaPickerGridItemNode()
node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme) node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme, enableAnimations: self.enableAnimations)
return node return node
} }
} }
@@ -54,13 +56,13 @@ final class MediaPickerGridItem: GridItem {
assertionFailure() assertionFailure()
return return
} }
node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme) node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme, enableAnimations: self.enableAnimations)
case let .media(media, index): case let .media(media, index):
guard let node = node as? MediaPickerGridItemNode else { guard let node = node as? MediaPickerGridItemNode else {
assertionFailure() assertionFailure()
return return
} }
node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme) node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme, enableAnimations: self.enableAnimations)
} }
} }
} }
@@ -81,6 +83,8 @@ private let maskImage = generateImage(CGSize(width: 1.0, height: 24.0), opaque:
final class MediaPickerGridItemNode: GridItemNode { final class MediaPickerGridItemNode: GridItemNode {
var currentMediaState: (TGMediaSelectableItem, Int)? var currentMediaState: (TGMediaSelectableItem, Int)?
var currentState: (PHFetchResult<PHAsset>, Int)? var currentState: (PHFetchResult<PHAsset>, Int)?
var enableAnimations: Bool = true
private let imageNode: ImageNode private let imageNode: ImageNode
private var checkNode: InteractiveCheckNode? private var checkNode: InteractiveCheckNode?
private let gradientNode: ASImageNode private let gradientNode: ASImageNode
@@ -214,9 +218,10 @@ final class MediaPickerGridItemNode: GridItemNode {
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:)))) self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
} }
func setup(interaction: MediaPickerInteraction, media: MediaPickerScreen.Subject.Media, index: Int, theme: PresentationTheme) { func setup(interaction: MediaPickerInteraction, media: MediaPickerScreen.Subject.Media, index: Int, theme: PresentationTheme, enableAnimations: Bool) {
self.interaction = interaction self.interaction = interaction
self.theme = theme self.theme = theme
self.enableAnimations = enableAnimations
self.backgroundColor = theme.list.mediaPlaceholderColor self.backgroundColor = theme.list.mediaPlaceholderColor
@@ -281,9 +286,10 @@ final class MediaPickerGridItemNode: GridItemNode {
self.updateHiddenMedia() self.updateHiddenMedia()
} }
func setup(interaction: MediaPickerInteraction, fetchResult: PHFetchResult<PHAsset>, index: Int, theme: PresentationTheme) { func setup(interaction: MediaPickerInteraction, fetchResult: PHFetchResult<PHAsset>, index: Int, theme: PresentationTheme, enableAnimations: Bool) {
self.interaction = interaction self.interaction = interaction
self.theme = theme self.theme = theme
self.enableAnimations = enableAnimations
self.backgroundColor = theme.list.mediaPlaceholderColor self.backgroundColor = theme.list.mediaPlaceholderColor
@@ -395,7 +401,7 @@ final class MediaPickerGridItemNode: GridItemNode {
if hasSpoiler { if hasSpoiler {
if self.spoilerNode == nil { if self.spoilerNode == nil {
let spoilerNode = SpoilerOverlayNode() let spoilerNode = SpoilerOverlayNode(enableAnimations: self.enableAnimations)
self.insertSubnode(spoilerNode, aboveSubnode: self.imageNode) self.insertSubnode(spoilerNode, aboveSubnode: self.imageNode)
self.spoilerNode = spoilerNode self.spoilerNode = spoilerNode
@@ -458,12 +464,12 @@ class SpoilerOverlayNode: ASDisplayNode {
private var maskView: UIView? private var maskView: UIView?
private var maskLayer: CAShapeLayer? private var maskLayer: CAShapeLayer?
override init() { init(enableAnimations: Bool) {
self.blurNode = ASImageNode() self.blurNode = ASImageNode()
self.blurNode.displaysAsynchronously = false self.blurNode.displaysAsynchronously = false
self.blurNode.contentMode = .scaleAspectFill self.blurNode.contentMode = .scaleAspectFill
self.dustNode = MediaDustNode() self.dustNode = MediaDustNode(enableAnimations: enableAnimations)
super.init() super.init()

View File

@@ -53,8 +53,8 @@ private struct MediaPickerGridEntry: Comparable, Identifiable {
return lhs.stableId < rhs.stableId return lhs.stableId < rhs.stableId
} }
func item(account: Account, interaction: MediaPickerInteraction, theme: PresentationTheme) -> MediaPickerGridItem { func item(context: AccountContext, interaction: MediaPickerInteraction, theme: PresentationTheme) -> MediaPickerGridItem {
return MediaPickerGridItem(content: self.content, interaction: interaction, theme: theme) return MediaPickerGridItem(content: self.content, interaction: interaction, theme: theme, enableAnimations: context.sharedContext.energyUsageSettings.fullTranslucency)
} }
} }
@@ -64,12 +64,12 @@ private struct MediaPickerGridTransaction {
let updates: [GridNodeUpdateItem] let updates: [GridNodeUpdateItem]
let scrollToItem: GridNodeScrollToItem? let scrollToItem: GridNodeScrollToItem?
init(previousList: [MediaPickerGridEntry], list: [MediaPickerGridEntry], account: Account, interaction: MediaPickerInteraction, theme: PresentationTheme, scrollToItem: GridNodeScrollToItem?) { init(previousList: [MediaPickerGridEntry], list: [MediaPickerGridEntry], context: AccountContext, interaction: MediaPickerInteraction, theme: PresentationTheme, scrollToItem: GridNodeScrollToItem?) {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list)
self.deletions = deleteIndices self.deletions = deleteIndices
self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction, theme: theme), previousIndex: $0.2) } self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, interaction: interaction, theme: theme), previousIndex: $0.2) }
self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction, theme: theme)) } self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, theme: theme)) }
self.scrollToItem = scrollToItem self.scrollToItem = scrollToItem
} }
@@ -545,7 +545,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
scrollToItem = GridNodeScrollToItem(index: entries.count - 1, position: .bottom(0.0), transition: .immediate, directionHint: .down, adjustForSection: false) scrollToItem = GridNodeScrollToItem(index: entries.count - 1, position: .bottom(0.0), transition: .immediate, directionHint: .down, adjustForSection: false)
} }
let transaction = MediaPickerGridTransaction(previousList: previousEntries, list: entries, account: controller.context.account, interaction: interaction, theme: self.presentationData.theme, scrollToItem: scrollToItem) let transaction = MediaPickerGridTransaction(previousList: previousEntries, list: entries, context: controller.context, interaction: interaction, theme: self.presentationData.theme, scrollToItem: scrollToItem)
self.enqueueTransaction(transaction) self.enqueueTransaction(transaction)
if updateLayout, let (layout, navigationBarHeight) = self.validLayout { if updateLayout, let (layout, navigationBarHeight) = self.validLayout {

View File

@@ -17,6 +17,7 @@ import ChatMessageBackground
private class MediaPickerSelectedItemNode: ASDisplayNode { private class MediaPickerSelectedItemNode: ASDisplayNode {
let asset: TGMediaEditableItem let asset: TGMediaEditableItem
private let interaction: MediaPickerInteraction? private let interaction: MediaPickerInteraction?
private let enableAnimations: Bool
private let imageNode: ImageNode private let imageNode: ImageNode
private var checkNode: InteractiveCheckNode? private var checkNode: InteractiveCheckNode?
@@ -56,15 +57,16 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
private var videoDuration: Double? private var videoDuration: Double?
init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?) { init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool) {
self.asset = asset
self.interaction = interaction
self.enableAnimations = enableAnimations
self.imageNode = ImageNode() self.imageNode = ImageNode()
self.imageNode.contentMode = .scaleAspectFill self.imageNode.contentMode = .scaleAspectFill
self.imageNode.clipsToBounds = true self.imageNode.clipsToBounds = true
self.imageNode.animateFirstTransition = false self.imageNode.animateFirstTransition = false
self.asset = asset
self.interaction = interaction
super.init() super.init()
self.clipsToBounds = true self.clipsToBounds = true
@@ -165,7 +167,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
if hasSpoiler { if hasSpoiler {
if self.spoilerNode == nil { if self.spoilerNode == nil {
let spoilerNode = SpoilerOverlayNode() let spoilerNode = SpoilerOverlayNode(enableAnimations: self.enableAnimations)
self.insertSubnode(spoilerNode, aboveSubnode: self.imageNode) self.insertSubnode(spoilerNode, aboveSubnode: self.imageNode)
self.spoilerNode = spoilerNode self.spoilerNode = spoilerNode
@@ -781,7 +783,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
if let current = self.itemNodes[identifier] { if let current = self.itemNodes[identifier] {
itemNode = current itemNode = current
} else { } else {
itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction) itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency)
self.itemNodes[identifier] = itemNode self.itemNodes[identifier] = itemNode
self.scrollNode.addSubnode(itemNode) self.scrollNode.addSubnode(itemNode)

View File

@@ -189,11 +189,11 @@ private class ExtendedMediaOverlayNode: ASDisplayNode {
var isRevealed = false var isRevealed = false
var tapped: () -> Void = {} var tapped: () -> Void = {}
override init() { init(enableAnimations: Bool) {
self.blurredImageNode = TransformImageNode() self.blurredImageNode = TransformImageNode()
self.blurredImageNode.contentAnimations = [] self.blurredImageNode.contentAnimations = []
self.dustNode = MediaDustNode() self.dustNode = MediaDustNode(enableAnimations: enableAnimations)
self.buttonNode = HighlightTrackingButtonNode() self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3) self.buttonNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3)
@@ -1898,7 +1898,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
if displaySpoiler { if displaySpoiler {
if self.extendedMediaOverlayNode == nil { if self.extendedMediaOverlayNode == nil {
let extendedMediaOverlayNode = ExtendedMediaOverlayNode() let extendedMediaOverlayNode = ExtendedMediaOverlayNode(enableAnimations: self.context?.sharedContext.energyUsageSettings.fullTranslucency ?? true)
extendedMediaOverlayNode.tapped = { [weak self] in extendedMediaOverlayNode.tapped = { [weak self] in
self?.internallyVisible = true self?.internallyVisible = true
self?.updateVisibility() self?.updateVisibility()