Swiftgram/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift
2022-05-31 04:44:42 +04:00

294 lines
12 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SwiftSignalKit
import StickerResources
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ContextUI
import SolidRoundedButtonNode
import TelegramPresentationData
import AccountContext
import AppBundle
public enum StickerPreviewPeekItem: Equatable {
case pack(StickerPackItem)
case found(FoundStickerItem)
public var file: TelegramMediaFile {
switch self {
case let .pack(item):
return item.file
case let .found(item):
return item.file
}
}
}
public final class StickerPreviewPeekContent: PeekControllerContent {
let account: Account
let theme: PresentationTheme
let strings: PresentationStrings
public let item: StickerPreviewPeekItem
let isLocked: Bool
let menu: [ContextMenuItem]
let openPremiumIntro: () -> Void
public init(account: Account, theme: PresentationTheme, strings: PresentationStrings, item: StickerPreviewPeekItem, isLocked: Bool = false, menu: [ContextMenuItem], openPremiumIntro: @escaping () -> Void) {
self.account = account
self.theme = theme
self.strings = strings
self.item = item
self.isLocked = isLocked
if isLocked {
self.menu = []
} else {
self.menu = menu
}
self.openPremiumIntro = openPremiumIntro
}
public func presentation() -> PeekControllerContentPresentation {
return .freeform
}
public func menuActivation() -> PeerControllerMenuActivation {
return .press
}
public func menuItems() -> [ContextMenuItem] {
return self.menu
}
public func node() -> PeekControllerContentNode & ASDisplayNode {
return StickerPreviewPeekContentNode(account: self.account, item: self.item)
}
public func topAccessoryNode() -> ASDisplayNode? {
return nil
}
public func fullScreenAccessoryNode(blurView: UIVisualEffectView) -> (PeekControllerAccessoryNode & ASDisplayNode)? {
if self.isLocked {
return PremiumStickerPackAccessoryNode(theme: self.theme, strings: self.strings, proceed: self.openPremiumIntro)
} else {
return nil
}
}
public func isEqual(to: PeekControllerContent) -> Bool {
if let to = to as? StickerPreviewPeekContent {
return self.item == to.item
} else {
return false
}
}
}
public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerContentNode {
private let account: Account
private let item: StickerPreviewPeekItem
private var textNode: ASTextNode
public var imageNode: TransformImageNode
public var animationNode: AnimatedStickerNode?
public var additionalAnimationNode: AnimatedStickerNode?
private let effectDisposable = MetaDisposable()
private var containerLayout: (ContainerViewLayout, CGFloat)?
init(account: Account, item: StickerPreviewPeekItem) {
self.account = account
self.item = item
self.textNode = ASTextNode()
self.imageNode = TransformImageNode()
for case let .Sticker(text, _, _) in item.file.attributes {
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(32.0), textColor: .black)
break
}
let isPremiumSticker = item.file.isPremiumSticker
if item.file.isAnimatedSticker || item.file.isVideoSticker {
let animationNode = AnimatedStickerNode()
self.animationNode = animationNode
let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0))
animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource, isVideo: item.file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: isPremiumSticker ? .once : .loop, mode: .direct(cachePathPrefix: nil))
animationNode.visibility = true
animationNode.addSubnode(self.textNode)
if isPremiumSticker, let effect = item.file.videoThumbnails.first {
self.effectDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: .standalone(media: item.file), resource: effect.resource).start())
let source = AnimatedStickerResourceSource(account: account, resource: effect.resource, fitzModifier: nil)
let additionalAnimationNode = AnimatedStickerNode()
additionalAnimationNode.setup(source: source, width: Int(fittedDimensions.width * 2.0), height: Int(fittedDimensions.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
additionalAnimationNode.visibility = true
self.additionalAnimationNode = additionalAnimationNode
}
} else {
self.imageNode.addSubnode(self.textNode)
self.animationNode = nil
}
self.imageNode.setSignal(chatMessageSticker(account: account, file: item.file, small: false, fetched: true))
super.init()
self.isUserInteractionEnabled = false
if let animationNode = self.animationNode {
self.addSubnode(animationNode)
if isPremiumSticker {
animationNode.completed = { [weak self] _ in
if let strongSelf = self, let animationNode = strongSelf.animationNode, let additionalAnimationNode = strongSelf.additionalAnimationNode {
Queue.mainQueue().async {
animationNode.play()
additionalAnimationNode.play()
}
}
}
}
} else {
self.addSubnode(self.imageNode)
}
if let additionalAnimationNode = self.additionalAnimationNode {
self.addSubnode(additionalAnimationNode)
}
}
deinit {
self.effectDisposable.dispose()
}
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
let boundingSize: CGSize
if let _ = self.additionalAnimationNode {
boundingSize = CGSize(width: 240.0, height: 240.0).fitted(size)
} else {
boundingSize = CGSize(width: 180.0, height: 180.0).fitted(size)
}
if let dimensitons = self.item.file.dimensions {
var topOffset: CGFloat = 0.0
var textSpacing: CGFloat = 50.0
if size.width == 292.0 {
topOffset = 60.0
textSpacing -= 10.0
}
let textSize = self.textNode.measure(CGSize(width: 100.0, height: 100.0))
let imageSize = dimensitons.cgSize.aspectFitted(boundingSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
var imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: textSize.height + textSpacing - topOffset), size: imageSize)
var centerOffset: CGFloat = 0.0
if self.item.file.isPremiumSticker {
let originalImageFrame = imageFrame
imageFrame.origin.x = size.width - imageFrame.width - 18.0
centerOffset = imageFrame.minX - originalImageFrame.minX
}
self.imageNode.frame = imageFrame
if let animationNode = self.animationNode {
animationNode.frame = imageFrame
animationNode.updateLayout(size: imageSize)
if let additionalAnimationNode = self.additionalAnimationNode {
additionalAnimationNode.frame = imageFrame.offsetBy(dx: -imageFrame.width * 0.245 + 21, dy: -1.0).insetBy(dx: -imageFrame.width * 0.245, dy: -imageFrame.height * 0.245)
additionalAnimationNode.updateLayout(size: additionalAnimationNode.frame.size)
}
}
self.textNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - textSize.width) / 2.0) - centerOffset, y: -textSize.height - textSpacing), size: textSize)
return CGSize(width: size.width, height: imageFrame.height + textSize.height + textSpacing)
} else {
return CGSize(width: size.width, height: 10.0)
}
}
}
final class PremiumStickerPackAccessoryNode: SparseNode, PeekControllerAccessoryNode {
var dismiss: () -> Void = {}
let proceed: () -> Void
let textNode: ImmediateTextNode
let proceedButton: SolidRoundedButtonNode
let cancelButton: HighlightableButtonNode
init(theme: PresentationTheme, strings: PresentationStrings, proceed: @escaping () -> Void) {
self.proceed = proceed
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.textAlignment = .center
self.textNode.maximumNumberOfLines = 0
self.textNode.attributedText = NSAttributedString(string: strings.Premium_Stickers_Description, font: Font.regular(17.0), textColor: theme.actionSheet.secondaryTextColor)
self.textNode.lineSpacing = 0.1
self.proceedButton = SolidRoundedButtonNode(title: strings.Premium_Stickers_Proceed, theme: SolidRoundedButtonTheme(
backgroundColor: .white,
backgroundColors: [
UIColor(rgb: 0x0077ff),
UIColor(rgb: 0x6b93ff),
UIColor(rgb: 0x8878ff),
UIColor(rgb: 0xe46ace)
], foregroundColor: .white), height: 50.0, cornerRadius: 11.0, gloss: true)
self.proceedButton.iconPosition = .right
self.proceedButton.iconSpacing = 6.0
self.proceedButton.animation = "premium_unlock"
self.cancelButton = HighlightableButtonNode()
self.cancelButton.setTitle(strings.Common_Cancel, with: Font.regular(17.0), with: theme.list.itemAccentColor, for: .normal)
super.init()
self.addSubnode(self.textNode)
self.addSubnode(self.proceedButton)
self.addSubnode(self.cancelButton)
self.proceedButton.pressed = { [weak self] in
self?.dismiss()
self?.proceed()
}
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
}
@objc func cancelPressed() {
self.dismiss()
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
let sideInset: CGFloat = 16.0
var bottomOffset: CGFloat = 0.0
if size.width == 320.0 {
bottomOffset = 30.0
}
let cancelSize = self.cancelButton.measure(size)
self.cancelButton.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - cancelSize.width) / 2.0), y: size.height - cancelSize.height - 49.0 + bottomOffset), size: cancelSize)
let buttonWidth = size.width - sideInset * 2.0
let buttonHeight = self.proceedButton.updateLayout(width: buttonWidth, transition: transition)
self.proceedButton.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - buttonWidth) / 2.0), y: size.height - cancelSize.height - 49.0 - buttonHeight - 23.0 + bottomOffset), size: CGSize(width: buttonWidth, height: buttonHeight))
let textSideInset = size.width == 320.0 ? sideInset : sideInset * 2.0
let textSize = self.textNode.updateLayout(CGSize(width: size.width - textSideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - cancelSize.height - 48.0 - buttonHeight - 20.0 - textSize.height - 31.0 + bottomOffset), size: textSize)
}
}