Various improvements

This commit is contained in:
Ilya Laktyushin 2022-06-11 09:10:01 +04:00
parent 4251ee2c1b
commit fbccdd47df
22 changed files with 992 additions and 217 deletions

View File

@ -7711,5 +7711,6 @@ Sorry for the inconvenience.";
"Premium.Purchase.ErrorUnknown" = "An error occurred. Please try again.";
"Premium.Purchase.ErrorNetwork" = "Please check your internet connection and try again.";
"Premium.Purchase.ErrorNotAllowed" = "The device is not not allowed to make the payment.";
"Premium.Purchase.ErrorCantMakePayments" = "In-app purchases are not allowed on this device.";
"Settings.Premium" = "Telegram Premium";

View File

@ -2241,7 +2241,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
transition.updateFrameAdditive(node: self.titleNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + titleOffset, y: titleFrame.origin.y), size: titleFrame.size))
let authorFrame = self.authorNode.frame
transition.updateFrame(node: self.authorNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: authorFrame.origin.y), size: authorFrame.size))
transition.updateFrame(node: self.authorNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: authorFrame.origin.y), size: authorFrame.size))
transition.updateFrame(node: self.inputActivitiesNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: self.inputActivitiesNode.frame.minY), size: self.inputActivitiesNode.bounds.size))
@ -2253,7 +2253,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
transition.updateFrameAdditive(node: dustNode, frame: textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0))
}
var mediaPreviewOffsetX = textFrame.origin.x + 1.0
var mediaPreviewOffsetX = textFrame.origin.x
let contentImageSpacing: CGFloat = 2.0
for (_, media, mediaSize) in self.currentMediaPreviewSpecs {
guard let mediaId = media.id else {
@ -2279,7 +2279,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
let mutedIconFrame = self.mutedIconNode.frame
transition.updateFrame(node: self.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 4.0, y: contentRect.origin.y - 2.0), size: mutedIconFrame.size))
transition.updateFrame(node: self.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: mutedIconFrame.minY), size: mutedIconFrame.size))
nextTitleIconOrigin += mutedIconFrame.size.width + 3.0
let badgeFrame = self.badgeNode.frame

View File

@ -30,6 +30,8 @@ public final class InAppPurchaseManager: NSObject {
case cancelled
case network
case notAllowed
case cantMakePayments
case assignFailed
}
public enum RestoreState {
@ -51,6 +53,7 @@ public final class InAppPurchaseManager: NSObject {
case restored(transactionId: String?)
case purchasing
case failed(error: SKError?)
case assignFailed
case deferred
}
@ -63,7 +66,7 @@ public final class InAppPurchaseManager: NSObject {
private let stateQueue = Queue()
private var paymentContexts: [String: PaymentTransactionContext] = [:]
private var onRestoreCompletion: ((RestoreState) -> Void)?
private let disposableSet = DisposableDict<String>()
@ -82,6 +85,10 @@ public final class InAppPurchaseManager: NSObject {
SKPaymentQueue.default().remove(self)
}
var canMakePayments: Bool {
return SKPaymentQueue.canMakePayments()
}
private func requestProducts() {
guard !self.premiumProductId.isEmpty else {
return
@ -119,10 +126,16 @@ public final class InAppPurchaseManager: NSObject {
}
}
public func buyProduct(_ product: Product, account: Account) -> Signal<PurchaseState, PurchaseError> {
Logger.shared.log("InAppPurchaseManager", "Buying product: \(product.skProduct.productIdentifier), price \(product.price)")
public func buyProduct(_ product: Product) -> Signal<PurchaseState, PurchaseError> {
if !self.canMakePayments {
return .fail(.cantMakePayments)
}
let accountPeerId = "\(self.engine.account.peerId.toInt64())"
let payment = SKPayment(product: product.skProduct)
Logger.shared.log("InAppPurchaseManager", "Buying: account \(accountPeerId), product \(product.skProduct.productIdentifier), price \(product.price)")
let payment = SKMutablePayment(product: product.skProduct)
payment.applicationUsername = accountPeerId
SKPaymentQueue.default().add(payment)
let productIdentifier = payment.productIdentifier
@ -156,6 +169,8 @@ public final class InAppPurchaseManager: NSObject {
} else {
subscriber.putError(.generic)
}
case .assignFailed:
subscriber.putError(.assignFailed)
case .deferred, .purchasing:
break
}
@ -205,39 +220,48 @@ private func getReceiptData() -> Data? {
extension InAppPurchaseManager: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
let accountPeerId = "\(self.engine.account.peerId.toInt64())"
if let applicationUsername = transaction.payment.applicationUsername, applicationUsername != accountPeerId {
continue
}
let productIdentifier = transaction.payment.productIdentifier
self.stateQueue.async {
let transactionState: TransactionState?
switch transaction.transactionState {
case .purchased:
Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") purchased")
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "none") purchased")
let transactionIdentifier = transaction.transactionIdentifier
transactionState = .purchased(transactionId: transactionIdentifier)
if let transactionIdentifier = transactionIdentifier {
self.disposableSet.set(
self.engine.payments.sendAppStoreReceipt(receipt: getReceiptData() ?? Data(), restore: false).start(error: { _ in
Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") failed to assign AppStore transaction")
self.engine.payments.sendAppStoreReceipt(receipt: getReceiptData() ?? Data(), restore: false).start(error: { [weak self] _ in
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? "") failed to assign AppStore transaction")
queue.finishTransaction(transaction)
if let strongSelf = self, let context = strongSelf.paymentContexts[productIdentifier] {
context.subscriber(.assignFailed)
}
}, completed: {
Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") successfully assigned AppStore transaction")
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? "") successfully assigned AppStore transaction")
queue.finishTransaction(transaction)
}),
forKey: transactionIdentifier
)
}
case .restored:
Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "") restroring")
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? ""), original transaction \(transaction.original?.transactionIdentifier ?? "") restroring")
let transactionIdentifier = transaction.transactionIdentifier
transactionState = .restored(transactionId: transactionIdentifier)
case .failed:
Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") failed \((transaction.error as? SKError)?.localizedDescription ?? "")")
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? "") failed \((transaction.error as? SKError)?.localizedDescription ?? "")")
transactionState = .failed(error: transaction.error as? SKError)
queue.finishTransaction(transaction)
case .purchasing:
Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") purchasing")
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? "") purchasing")
transactionState = .purchasing
case .deferred:
Logger.shared.log("InAppPurchaseManager", "Transaction \(transaction.transactionIdentifier ?? "") deferred")
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transaction \(transaction.transactionIdentifier ?? "") deferred")
transactionState = .deferred
default:
transactionState = nil

View File

@ -0,0 +1,57 @@
//import Foundation
//import UIKit
//import SwiftSignalKit
//import Postbox
//import TelegramCore
//import TelegramUIPreferences
//
//final class StoredTransactionState: Codable {
// let timestamp: Double
// let playbackRate: AudioPlaybackRate
//
// init(timestamp: Double, playbackRate: AudioPlaybackRate) {
// self.timestamp = timestamp
// self.playbackRate = playbackRate
// }
//
// public init(from decoder: Decoder) throws {
// let container = try decoder.container(keyedBy: StringCodingKey.self)
//
// self.timestamp = try container.decode(Double.self, forKey: "timestamp")
// self.playbackRate = AudioPlaybackRate(rawValue: try container.decode(Int32.self, forKey: "playbackRate")) ?? .x1
// }
//
// public func encode(to encoder: Encoder) throws {
// var container = encoder.container(keyedBy: StringCodingKey.self)
//
// try container.encode(self.timestamp, forKey: "timestamp")
// try container.encode(self.playbackRate.rawValue, forKey: "playbackRate")
// }
//}
//
//public func storedState(engine: TelegramEngine, : MessageId) -> Signal<MediaPlaybackStoredState?, NoError> {
// let key = ValueBoxKey(length: 20)
// key.setInt32(0, value: messageId.namespace)
// key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value())
// key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value())
// key.setInt32(16, value: messageId.id)
//
// return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.mediaPlaybackStoredState, id: key))
// |> map { entry -> MediaPlaybackStoredState? in
// return entry?.get(MediaPlaybackStoredState.self)
// }
//}
//
//public func updateMediaPlaybackStoredStateInteractively(engine: TelegramEngine, messageId: MessageId, state: MediaPlaybackStoredState?) -> Signal<Never, NoError> {
// let key = ValueBoxKey(length: 20)
// key.setInt32(0, value: messageId.namespace)
// key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value())
// key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value())
// key.setInt32(16, value: messageId.id)
//
// if let state = state {
// return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.mediaPlaybackStoredState, id: key, item: state)
// } else {
// return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.mediaPlaybackStoredState, id: key)
// }
//}

View File

@ -1,4 +1,44 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
load(
"@build_bazel_rules_apple//apple:resources.bzl",
"apple_resource_bundle",
"apple_resource_group",
)
load("//build-system/bazel-utils:plist_fragment.bzl",
"plist_fragment",
)
filegroup(
name = "PremiumUIMetalResources",
srcs = glob([
"MetalResources/**/*.*",
]),
visibility = ["//visibility:public"],
)
plist_fragment(
name = "PremiumUIBundleInfoPlist",
extension = "plist",
template =
"""
<key>CFBundleIdentifier</key>
<string>org.telegram.PremiumUI</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleName</key>
<string>PremiumUI</string>
"""
)
apple_resource_bundle(
name = "PremiumUIBundle",
infoplists = [
":PremiumUIBundleInfoPlist",
],
resources = [
":PremiumUIMetalResources",
],
)
filegroup(
name = "PremiumUIResources",
@ -17,6 +57,9 @@ swift_library(
copts = [
"-warnings-as-errors",
],
data = [
":PremiumUIBundle",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,79 @@
#include <metal_stdlib>
using namespace metal;
typedef struct {
packed_float2 position;
} Vertex;
struct RasterizerData
{
float4 position [[position]];
};
vertex RasterizerData matrixVertex
(
constant Vertex *vertexArray[[buffer(0)]],
uint vertexID [[ vertex_id ]]
) {
RasterizerData out;
out.position = vector_float4(vertexArray[vertexID].position[0], vertexArray[vertexID].position[1], 0.0, 1.0);
return out;
}
float text(float2 uvIn,
texture2d<half> symbolTexture,
texture2d<float> noiseTexture,
float time)
{
constexpr sampler textureSampler(min_filter::linear, mag_filter::linear, mip_filter::linear, address::repeat);
float count = 32.0;
float2 noiseResolution = float2(256.0, 256.0);
float2 uv = fmod(uvIn, 1.0 / count) * count;
float2 block = uvIn * count - uv;
uv = uv * 0.8 + 0.1;
uv += floor(noiseTexture.sample(textureSampler, block / noiseResolution + time * .00025).xy * 256.);
uv *= -1.0;
uv *= 0.25;
return symbolTexture.sample(textureSampler, uv).g;
}
float4 rain(float2 uvIn,
uint2 resolution,
float time)
{
float count = 32.0;
uvIn.x -= fmod(uvIn.x, 1.0 / count);
uvIn.y -= fmod(uvIn.y, 1.0 / count);
float2 fragCoord = uvIn * float2(resolution);
float offset = sin(fragCoord.x * 15.0);
float speed = cos(fragCoord.x * 3.0) * 0.3 + 0.7;
float y = fract(fragCoord.y / resolution.y + time * speed + offset);
return float4(1.0, 1.0, 1.0, 1.0 / (y * 30.0) - 0.02);
}
fragment half4 matrixFragment(RasterizerData in[[stage_in]],
texture2d<half> symbolTexture [[ texture(0) ]],
texture2d<float> noiseTexture [[ texture(1) ]],
constant uint2 &resolution[[buffer(0)]],
constant float &time[[buffer(1)]])
{
float2 uv = (in.position.xy / float2(resolution.xy) - float2(0.5, 0.5));
uv.y -= 0.1;
float2 lookup = float2(0.08 / (uv.x), (0.9 - abs(uv.x)) * uv.y * -1.0) * 2.0;
float4 out = text(lookup, symbolTexture, noiseTexture, time) * rain(lookup, resolution, time);
return half4(out);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

View File

@ -0,0 +1,59 @@
import Foundation
import UIKit
import SceneKit
import Display
import AppBundle
final class BadgeStarsView: UIView, PhoneDemoDecorationView {
private let sceneView: SCNView
private var leftParticles: SCNNode?
private var rightParticles: SCNNode?
override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear
if let url = getAppBundle().url(forResource: "badge", withExtension: "scn") {
self.sceneView.scene = try? SCNScene(url: url, options: nil)
}
self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60
super.init(frame: frame)
self.alpha = 0.0
self.addSubview(self.sceneView)
self.leftParticles = self.sceneView.scene?.rootNode.childNode(withName: "leftParticles", recursively: false)
self.rightParticles = self.sceneView.scene?.rootNode.childNode(withName: "rightParticles", recursively: false)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setVisible(_ visible: Bool) {
if visible, let leftParticles = self.leftParticles, let rightParticles = self.rightParticles, leftParticles.parent == nil {
self.sceneView.scene?.rootNode.addChildNode(leftParticles)
self.sceneView.scene?.rootNode.addChildNode(rightParticles)
}
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
transition.updateAlpha(layer: self.layer, alpha: visible ? 0.5 : 0.0, completion: { [weak self] finished in
if let strongSelf = self, finished && !visible && strongSelf.leftParticles?.parent != nil {
strongSelf.leftParticles?.removeFromParentNode()
strongSelf.rightParticles?.removeFromParentNode()
}
})
}
func resetAnimation() {
}
override func layoutSubviews() {
super.layoutSubviews()
self.sceneView.frame = CGRect(origin: .zero, size: frame.size)
}
}

View File

@ -0,0 +1,198 @@
import Foundation
import Metal
import MetalKit
import Display
@available(iOS 10.0, *)
public final class MatrixView: MTKView, MTKViewDelegate, PhoneDemoDecorationView {
public func draw(in view: MTKView) {
}
private let commandQueue: MTLCommandQueue
private let drawPassthroughPipelineState: MTLRenderPipelineState
private var displayLink: CADisplayLink?
// private var metalLayer: CAMetalLayer {
// return self.layer as! CAMetalLayer
// }
private let symbolTexture: MTLTexture
private let randomTexture: MTLTexture
private var viewportDimensions = CGSize(width: 1, height: 1)
private var startTimestamp = CACurrentMediaTime()
public init?(test: Bool) {
let mainBundle = Bundle(for: MatrixView.self)
guard let path = mainBundle.path(forResource: "PremiumUIBundle", ofType: "bundle") else {
return nil
}
guard let bundle = Bundle(path: path) else {
return nil
}
guard let device = MTLCreateSystemDefaultDevice() else {
return nil
}
guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: bundle) else {
return nil
}
guard let commandQueue = device.makeCommandQueue() else {
return nil
}
self.commandQueue = commandQueue
guard let loadedVertexProgram = defaultLibrary.makeFunction(name: "matrixVertex") else {
return nil
}
guard let loadedFragmentProgram = defaultLibrary.makeFunction(name: "matrixFragment") else {
return nil
}
let textureLoader = MTKTextureLoader(device: device)
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.vertexFunction = loadedVertexProgram
pipelineStateDescriptor.fragmentFunction = loadedFragmentProgram
pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
pipelineStateDescriptor.colorAttachments[0].isBlendingEnabled = true
pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = .add
pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = .add
pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha
pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha
pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha
self.drawPassthroughPipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
guard let url = bundle.url(forResource: "chars", withExtension: "png"), let texture = try? textureLoader.newTexture(URL: url, options: nil) else {
return nil
}
self.symbolTexture = texture
guard let url = bundle.url(forResource: "random", withExtension: "jpg"), let texture = try? textureLoader.newTexture(URL: url, options: nil) else {
return nil
}
self.randomTexture = texture
super.init(frame: CGRect(), device: device)
self.delegate = self
self.isOpaque = false
self.backgroundColor = .clear
self.framebufferOnly = true
class DisplayLinkProxy: NSObject {
weak var target: MatrixView?
init(target: MatrixView) {
self.target = target
}
@objc func displayLinkEvent() {
self.target?.displayLinkEvent()
}
}
self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent))
if #available(iOS 15.0, *) {
self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 60.0, preferred: 60.0)
}
self.displayLink?.add(to: .main, forMode: .common)
self.displayLink?.isPaused = false
self.isPaused = true
}
public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
self.viewportDimensions = size
}
required public init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.displayLink?.invalidate()
}
func setVisible(_ visible: Bool) {
if visible {
self.displayLink?.isPaused = false
}
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
transition.updateAlpha(layer: self.layer, alpha: visible ? 0.4 : 0.0, completion: { [weak self] finished in
if let strongSelf = self, finished && !visible {
strongSelf.displayLink?.isPaused = false
}
})
}
func resetAnimation() {
}
@objc private func displayLinkEvent() {
self.draw()
}
override public func draw(_ rect: CGRect) {
self.redraw(drawable: self.currentDrawable!)
}
private func redraw(drawable: MTLDrawable) {
guard let commandBuffer = self.commandQueue.makeCommandBuffer() else {
return
}
let renderPassDescriptor = self.currentRenderPassDescriptor!
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0.0)
guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
return
}
let viewportDimensions = self.viewportDimensions
renderEncoder.setViewport(MTLViewport(originX: 0.0, originY: 0.0, width: viewportDimensions.width, height: viewportDimensions.height, znear: -1.0, zfar: 1.0))
renderEncoder.setRenderPipelineState(self.drawPassthroughPipelineState)
var vertices: [Float] = [
1, -1,
-1, -1,
-1, 1,
1, -1,
-1, 1,
1, 1
]
renderEncoder.setVertexBytes(&vertices, length: 4 * vertices.count, index: 0)
renderEncoder.setFragmentTexture(self.symbolTexture, index: 0)
renderEncoder.setFragmentTexture(self.randomTexture, index: 1)
var resolution = simd_uint2(UInt32(viewportDimensions.width), UInt32(viewportDimensions.height))
renderEncoder.setFragmentBytes(&resolution, length: MemoryLayout<simd_uint2>.size * 2, index: 0)
var time = Float(CACurrentMediaTime() - self.startTimestamp) * 0.75
renderEncoder.setFragmentBytes(&time, length: 4, index: 1)
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6, instanceCount: 1)
renderEncoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
}
}

View File

@ -0,0 +1,107 @@
import Foundation
import UIKit
import SceneKit
import Display
import AppBundle
final class FasterStarsView: UIView, PhoneDemoDecorationView {
private let sceneView: SCNView
private var particles: SCNNode?
override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear
if let url = getAppBundle().url(forResource: "lightspeed", withExtension: "scn") {
self.sceneView.scene = try? SCNScene(url: url, options: nil)
}
self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60
super.init(frame: frame)
self.alpha = 0.0
self.addSubview(self.sceneView)
self.particles = self.sceneView.scene?.rootNode.childNode(withName: "particles", recursively: false)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.particles = nil
}
func setVisible(_ visible: Bool) {
if visible, let particles = self.particles, particles.parent == nil {
self.sceneView.scene?.rootNode.addChildNode(particles)
}
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
transition.updateAlpha(layer: self.layer, alpha: visible ? 0.4 : 0.0, completion: { [weak self] finished in
if let strongSelf = self, finished && !visible && strongSelf.particles?.parent != nil {
strongSelf.particles?.removeFromParentNode()
}
})
}
private var playing = false
func startAnimation() {
guard !self.playing, let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "particles", recursively: false), let particles = node.particleSystems?.first else {
return
}
self.playing = true
let speedAnimation = CABasicAnimation(keyPath: "speedFactor")
speedAnimation.fromValue = 1.0
speedAnimation.toValue = 1.8
speedAnimation.duration = 0.8
speedAnimation.fillMode = .forwards
particles.addAnimation(speedAnimation, forKey: "speedFactor")
particles.speedFactor = 3.0
let stretchAnimation = CABasicAnimation(keyPath: "stretchFactor")
stretchAnimation.fromValue = 0.05
stretchAnimation.toValue = 0.3
stretchAnimation.duration = 0.8
stretchAnimation.fillMode = .forwards
particles.addAnimation(stretchAnimation, forKey: "stretchFactor")
particles.stretchFactor = 0.3
}
func resetAnimation() {
guard self.playing, let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "particles", recursively: false), let particles = node.particleSystems?.first else {
return
}
self.playing = false
let speedAnimation = CABasicAnimation(keyPath: "speedFactor")
speedAnimation.fromValue = 3.0
speedAnimation.toValue = 1.0
speedAnimation.duration = 0.35
speedAnimation.fillMode = .forwards
particles.addAnimation(speedAnimation, forKey: "speedFactor")
particles.speedFactor = 1.0
let stretchAnimation = CABasicAnimation(keyPath: "stretchFactor")
stretchAnimation.fromValue = 0.3
stretchAnimation.toValue = 0.05
stretchAnimation.duration = 0.35
stretchAnimation.fillMode = .forwards
particles.addAnimation(stretchAnimation, forKey: "stretchFactor")
particles.stretchFactor = 0.05
}
override func layoutSubviews() {
super.layoutSubviews()
self.sceneView.frame = CGRect(origin: .zero, size: frame.size)
}
}

View File

@ -280,125 +280,9 @@ private final class PhoneView: UIView {
}
}
private final class FasterStarsView: UIView {
private let sceneView: SCNView
override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear
if let url = getAppBundle().url(forResource: "lightspeed", withExtension: "scn") {
self.sceneView.scene = try? SCNScene(url: url, options: nil)
}
self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60
super.init(frame: frame)
self.alpha = 0.0
self.addSubview(self.sceneView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setVisible(_ visible: Bool) {
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
transition.updateAlpha(layer: self.layer, alpha: visible ? 1.0 : 0.0)
}
private var playing = false
func startAnimation() {
guard !self.playing, let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "particles", recursively: false), let particles = node.particleSystems?.first else {
return
}
self.playing = true
let speedAnimation = CABasicAnimation(keyPath: "speedFactor")
speedAnimation.fromValue = 1.0
speedAnimation.toValue = 1.8
speedAnimation.duration = 0.8
speedAnimation.fillMode = .forwards
particles.addAnimation(speedAnimation, forKey: "speedFactor")
particles.speedFactor = 3.0
let stretchAnimation = CABasicAnimation(keyPath: "stretchFactor")
stretchAnimation.fromValue = 0.05
stretchAnimation.toValue = 0.3
stretchAnimation.duration = 0.8
stretchAnimation.fillMode = .forwards
particles.addAnimation(stretchAnimation, forKey: "stretchFactor")
particles.stretchFactor = 0.3
}
func stopAnimation() {
guard self.playing, let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "particles", recursively: false), let particles = node.particleSystems?.first else {
return
}
self.playing = false
let speedAnimation = CABasicAnimation(keyPath: "speedFactor")
speedAnimation.fromValue = 3.0
speedAnimation.toValue = 1.0
speedAnimation.duration = 0.35
speedAnimation.fillMode = .forwards
particles.addAnimation(speedAnimation, forKey: "speedFactor")
particles.speedFactor = 1.0
let stretchAnimation = CABasicAnimation(keyPath: "stretchFactor")
stretchAnimation.fromValue = 0.3
stretchAnimation.toValue = 0.05
stretchAnimation.duration = 0.35
stretchAnimation.fillMode = .forwards
particles.addAnimation(stretchAnimation, forKey: "stretchFactor")
particles.stretchFactor = 0.05
}
override func layoutSubviews() {
super.layoutSubviews()
self.sceneView.frame = CGRect(origin: .zero, size: frame.size)
}
}
private final class BadgeStarsView: UIView {
private let sceneView: SCNView
override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear
if let url = getAppBundle().url(forResource: "badge", withExtension: "scn") {
self.sceneView.scene = try? SCNScene(url: url, options: nil)
}
self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60
super.init(frame: frame)
self.alpha = 0.0
self.addSubview(self.sceneView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setVisible(_ visible: Bool) {
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
transition.updateAlpha(layer: self.layer, alpha: visible ? 0.75 : 0.0)
}
override func layoutSubviews() {
super.layoutSubviews()
self.sceneView.frame = CGRect(origin: .zero, size: frame.size)
}
protocol PhoneDemoDecorationView: UIView {
func setVisible(_ visible: Bool)
func resetAnimation()
}
final class PhoneDemoComponent: Component {
@ -411,6 +295,8 @@ final class PhoneDemoComponent: Component {
enum BackgroundDecoration {
case none
case dataRain
case swirlStars
case fasterStars
case badgeStars
}
@ -462,14 +348,12 @@ final class PhoneDemoComponent: Component {
private var isCentral = false
private var component: PhoneDemoComponent?
private let starsContainerView: UIView
private let decorationContainerView: UIView
private var decorationView: PhoneDemoDecorationView?
private let containerView: UIView
private let phoneView: PhoneView
private var fasterStarsView: FasterStarsView?
private var badgeStarsView: BadgeStarsView?
private var starsDisposable: Disposable?
private var playbackStatusDisposable: Disposable?
public var ready: Signal<Bool, NoError> {
if let videoNode = self.phoneView.videoNode {
@ -483,8 +367,8 @@ final class PhoneDemoComponent: Component {
}
public override init(frame: CGRect) {
self.starsContainerView = UIView(frame: frame)
self.starsContainerView.clipsToBounds = true
self.decorationContainerView = UIView(frame: frame)
self.decorationContainerView.clipsToBounds = true
self.containerView = UIView(frame: frame)
self.containerView.clipsToBounds = true
@ -493,7 +377,7 @@ final class PhoneDemoComponent: Component {
super.init(frame: frame)
self.addSubview(self.starsContainerView)
self.addSubview(self.decorationContainerView)
self.addSubview(self.containerView)
self.containerView.addSubview(self.phoneView)
}
@ -502,42 +386,60 @@ final class PhoneDemoComponent: Component {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.starsDisposable?.dispose()
}
// deinit {
// self.playbackStatusDisposable?.dispose()
// }
public func update(component: PhoneDemoComponent, availableSize: CGSize, environment: Environment<DemoPageEnvironment>, transition: Transition) -> CGSize {
self.component = component
self.containerView.frame = CGRect(origin: .zero, size: availableSize)
self.starsContainerView.frame = CGRect(origin: CGPoint(x: -availableSize.width * 0.5, y: 0.0), size: CGSize(width: availableSize.width * 2.0, height: availableSize.height))
self.decorationContainerView.frame = CGRect(origin: CGPoint(x: -availableSize.width * 0.5, y: 0.0), size: CGSize(width: availableSize.width * 2.0, height: availableSize.height))
self.phoneView.bounds = CGRect(origin: .zero, size: phoneSize)
switch component.decoration {
case .none:
break
case .dataRain:
if #available(iOS 10.0, *) {
if let _ = self.decorationView as? MatrixView {
} else if let rainView = MatrixView(test: true) {
rainView.frame = self.decorationContainerView.bounds.insetBy(dx: availableSize.width * 0.5, dy: 0.0)
self.decorationView = rainView
self.decorationContainerView.addSubview(rainView)
}
}
case .swirlStars:
if let _ = self.decorationView as? SwirlStarsView {
} else {
let starsView = SwirlStarsView(frame: self.decorationContainerView.bounds)
self.decorationView = starsView
self.decorationContainerView.addSubview(starsView)
}
case .fasterStars:
if self.fasterStarsView == nil {
let starsView = FasterStarsView(frame: self.starsContainerView.bounds)
self.fasterStarsView = starsView
self.starsContainerView.addSubview(starsView)
if let _ = self.decorationView as? FasterStarsView {
} else {
let starsView = FasterStarsView(frame: self.decorationContainerView.bounds)
self.decorationView = starsView
self.decorationContainerView.addSubview(starsView)
self.starsDisposable = (self.phoneView.playbackStatus
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self, let status = status {
self.playbackStatusDisposable = (self.phoneView.playbackStatus
|> deliverOnMainQueue).start(next: { [weak starsView] status in
if let starsView = starsView, let status = status {
if status.timestamp > 8.0 {
strongSelf.fasterStarsView?.stopAnimation()
starsView.resetAnimation()
} else if status.timestamp > 0.85 {
strongSelf.fasterStarsView?.startAnimation()
starsView.startAnimation()
}
}
})
}
case .badgeStars:
if self.badgeStarsView == nil {
let starsView = BadgeStarsView(frame: self.starsContainerView.bounds)
self.badgeStarsView = starsView
self.starsContainerView.addSubview(starsView)
if let _ = self.decorationView as? BadgeStarsView {
} else {
let starsView = BadgeStarsView(frame: self.decorationContainerView.bounds)
self.decorationView = starsView
self.decorationContainerView.addSubview(starsView)
}
}
@ -561,8 +463,9 @@ final class PhoneDemoComponent: Component {
let isCentral = environment[DemoPageEnvironment.self].isCentral
self.isCentral = isCentral
self.fasterStarsView?.setVisible(isVisible && abs(mappedPosition) < 0.4)
self.badgeStarsView?.setVisible(isVisible && abs(mappedPosition) < 0.4)
if let decorationView = self.decorationView {
decorationView.setVisible(isVisible && abs(mappedPosition) < 0.4)
}
self.phoneView.center = CGPoint(x: availableSize.width / 2.0 + phoneX, y: phoneY)
self.phoneView.screenRotation = mappedPosition * -0.7
@ -575,7 +478,7 @@ final class PhoneDemoComponent: Component {
self.phoneView.play()
} else if !isVisible {
self.phoneView.reset()
self.fasterStarsView?.stopAnimation()
self.decorationView?.resetAnimation()
}
if let _ = transition.userData(DemoAnimateInTransition.self), abs(mappedPosition) < .ulpOfOne {

View File

@ -725,7 +725,8 @@ private final class DemoSheetContent: CombinedComponent {
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .bottom,
videoFile: configuration.videos["more_upload"]
videoFile: configuration.videos["more_upload"],
decoration: .dataRain
)),
title: strings.Premium_UploadSize,
text: strings.Premium_UploadSizeInfo,
@ -760,7 +761,8 @@ private final class DemoSheetContent: CombinedComponent {
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoFile: configuration.videos["voice_to_text"]
videoFile: configuration.videos["voice_to_text"],
decoration: .badgeStars
)),
title: strings.Premium_VoiceToText,
text: strings.Premium_VoiceToTextInfo,
@ -777,7 +779,8 @@ private final class DemoSheetContent: CombinedComponent {
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .bottom,
videoFile: configuration.videos["no_ads"]
videoFile: configuration.videos["no_ads"],
decoration: .swirlStars
)),
title: strings.Premium_NoAds,
text: isStandalone ? strings.Premium_NoAdsStandaloneInfo : strings.Premium_NoAdsInfo,
@ -831,7 +834,8 @@ private final class DemoSheetContent: CombinedComponent {
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoFile: configuration.videos["advanced_chat_management"]
videoFile: configuration.videos["advanced_chat_management"],
decoration: .swirlStars
)),
title: strings.Premium_ChatManagement,
text: strings.Premium_ChatManagementInfo,
@ -866,7 +870,8 @@ private final class DemoSheetContent: CombinedComponent {
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoFile: configuration.videos["animated_userpics"]
videoFile: configuration.videos["animated_userpics"],
decoration: .swirlStars
)),
title: strings.Premium_Avatar,
text: strings.Premium_AvatarInfo,

View File

@ -1161,13 +1161,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
})
let termsString: MultilineTextComponent.TextContent
if context.component.isPremium == true {
if let promoConfiguration = context.state.promoConfiguration {
let attributedString = stringWithAppliedEntities(promoConfiguration.status, entities: promoConfiguration.statusEntities, baseColor: termsTextColor, linkColor: environment.theme.list.itemAccentColor, baseFont: termsFont, linkFont: termsFont, boldFont: boldTermsFont, italicFont: italicTermsFont, boldItalicFont: boldItalicTermsFont, fixedFont: monospaceTermsFont, blockQuoteFont: termsFont)
termsString = .plain(attributedString)
} else {
termsString = .plain(NSAttributedString())
}
// if context.component.isPremium == true {
if let promoConfiguration = context.state.promoConfiguration {
let attributedString = stringWithAppliedEntities(promoConfiguration.status, entities: promoConfiguration.statusEntities, baseColor: termsTextColor, linkColor: environment.theme.list.itemAccentColor, baseFont: termsFont, linkFont: termsFont, boldFont: boldTermsFont, italicFont: italicTermsFont, boldItalicFont: boldItalicTermsFont, fixedFont: monospaceTermsFont, blockQuoteFont: termsFont)
termsString = .plain(attributedString)
} else {
termsString = .markdown(
text: strings.Premium_Terms,
@ -1403,7 +1400,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|> deliverOnMainQueue).start(next: { [weak self] available in
if let strongSelf = self {
if available {
strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct, account: strongSelf.context.account)
strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self, case .purchased = status {
strongSelf.activationDisposable.set((strongSelf.context.account.postbox.peerView(id: strongSelf.context.account.peerId)
@ -1418,8 +1415,14 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|> mapToSignal { _ -> Signal<Never, AssignAppStoreTransactionError> in
return .never()
}
|> timeout(15.0, queue: Queue.mainQueue(), alternate: .fail(.timeout))
|> deliverOnMainQueue).start(error: { _ in
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail")
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
strongSelf.present(alertController)
}, completed: { [weak self] in
if let strongSelf = self {
let _ = updatePremiumPromoConfigurationOnce(account: strongSelf.context.account).start()
@ -1444,6 +1447,10 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
errorText = presentationData.strings.Premium_Purchase_ErrorNetwork
case .notAllowed:
errorText = presentationData.strings.Premium_Purchase_ErrorNotAllowed
case .cantMakePayments:
errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments
case .assignFailed:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .cancelled:
break
}

View File

@ -0,0 +1,204 @@
import Foundation
import UIKit
import SceneKit
import Display
import AppBundle
import SwiftSignalKit
final class SwirlStarsView: UIView, PhoneDemoDecorationView {
private let sceneView: SCNView
private var particles: SCNNode?
override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
self.sceneView.backgroundColor = .clear
if let url = getAppBundle().url(forResource: "swirl", withExtension: "scn") {
self.sceneView.scene = try? SCNScene(url: url, options: nil)
}
self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60
super.init(frame: frame)
self.alpha = 0.0
self.addSubview(self.sceneView)
self.particles = self.sceneView.scene?.rootNode.childNode(withName: "particles", recursively: false)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.particles = nil
}
func setVisible(_ visible: Bool) {
if visible, let particles = self.particles, particles.parent == nil {
self.sceneView.scene?.rootNode.addChildNode(particles)
}
self.setupAnimations()
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
transition.updateAlpha(layer: self.layer, alpha: visible ? 0.6 : 0.0, completion: { [weak self] finished in
if let strongSelf = self, finished && !visible && strongSelf.particles?.parent != nil {
strongSelf.particles?.removeFromParentNode()
if let node = strongSelf.sceneView.scene?.rootNode.childNode(withName: "star", recursively: false) {
node.removeAllAnimations()
}
}
})
}
func setupAnimations() {
guard let node = self.sceneView.scene?.rootNode.childNode(withName: "star", recursively: false), node.animationKeys.isEmpty else {
return
}
let initial = node.eulerAngles
let target = SCNVector3(x: node.eulerAngles.x + .pi * 2.0, y: node.eulerAngles.y, z: node.eulerAngles.z)
let animation = CABasicAnimation(keyPath: "eulerAngles")
animation.fromValue = NSValue(scnVector3: initial)
animation.toValue = NSValue(scnVector3: target)
animation.duration = 1.5
animation.timingFunction = CAMediaTimingFunction(name: .linear)
animation.fillMode = .forwards
animation.repeatCount = .infinity
node.addAnimation(animation, forKey: "rotation")
self.setupMovementAnimation()
}
func setupMovementAnimation() {
guard let node = self.sceneView.scene?.rootNode.childNode(withName: "star", recursively: false) else {
return
}
node.position = SCNVector3(3.5, 0.0, -2.0)
let firstPath = UIBezierPath()
firstPath.move(to: CGPoint(x: 3.5, y: -2.0))
firstPath.addLine(to: CGPoint(x: -15.5, y: 15.5))
let firstAction = SCNAction.moveAlong(path: firstPath, duration: 2.0)
SCNTransaction.begin()
SCNTransaction.animationDuration = 2.0
node.runAction(firstAction)
SCNTransaction.completionBlock = { [weak self, weak node] in
Queue.mainQueue().after(2.2, {
node?.position = SCNVector3(0.0, 0.0, -3.0)
let secondPath = UIBezierPath()
secondPath.move(to: CGPoint(x: 0.0, y: -3.0))
secondPath.addLine(to: CGPoint(x: 15.5, y: 20.0))
let secondAction = SCNAction.moveAlong(path: secondPath, duration: 2.0)
SCNTransaction.begin()
SCNTransaction.animationDuration = 2.0
node?.runAction(secondAction)
SCNTransaction.completionBlock = { [weak self] in
Queue.mainQueue().after(2.2, {
self?.setupMovementAnimation()
})
}
SCNTransaction.commit()
})
}
SCNTransaction.commit()
}
func resetAnimation() {
}
override func layoutSubviews() {
super.layoutSubviews()
self.sceneView.frame = CGRect(origin: .zero, size: frame.size)
}
}
extension UIBezierPath {
var elements: [PathElement] {
var pathElements = [PathElement]()
withUnsafeMutablePointer(to: &pathElements) { elementsPointer in
cgPath.apply(info: elementsPointer) { (userInfo, nextElementPointer) in
let nextElement = PathElement(element: nextElementPointer.pointee)
let elementsPointer = userInfo!.assumingMemoryBound(to: [PathElement].self)
elementsPointer.pointee.append(nextElement)
}
}
return pathElements
}
}
enum PathElement {
case moveToPoint(CGPoint)
case addLineToPoint(CGPoint)
case addQuadCurveToPoint(CGPoint, CGPoint)
case addCurveToPoint(CGPoint, CGPoint, CGPoint)
case closeSubpath
init(element: CGPathElement) {
switch element.type {
case .moveToPoint:
self = .moveToPoint(element.points[0])
case .addLineToPoint:
self = .addLineToPoint(element.points[0])
case .addQuadCurveToPoint:
self = .addQuadCurveToPoint(element.points[0], element.points[1])
case .addCurveToPoint:
self = .addCurveToPoint(element.points[0], element.points[1], element.points[2])
case .closeSubpath:
self = .closeSubpath
@unknown default:
self = .closeSubpath
}
}
}
public extension SCNAction {
class func moveAlong(path: UIBezierPath, duration animationDuration: Double) -> SCNAction {
let points = path.elements
var actions = [SCNAction]()
for point in points {
switch point {
case .moveToPoint(let a):
let moveAction = SCNAction.move(to: SCNVector3(a.x, 0, a.y), duration: animationDuration)
actions.append(moveAction)
break
case .addCurveToPoint(let a, let b, let c):
let moveAction1 = SCNAction.move(to: SCNVector3(a.x, 0, a.y), duration: animationDuration)
let moveAction2 = SCNAction.move(to: SCNVector3(b.x, 0, b.y), duration: animationDuration)
let moveAction3 = SCNAction.move(to: SCNVector3(c.x, 0, c.y), duration: animationDuration)
actions.append(moveAction1)
actions.append(moveAction2)
actions.append(moveAction3)
break
case .addLineToPoint(let a):
let moveAction = SCNAction.move(to: SCNVector3(a.x, 0, a.y), duration: animationDuration)
actions.append(moveAction)
break
case .addQuadCurveToPoint(let a, let b):
let moveAction1 = SCNAction.move(to: SCNVector3(a.x, 0, a.y), duration: animationDuration)
let moveAction2 = SCNAction.move(to: SCNVector3(b.x, 0, b.y), duration: animationDuration)
actions.append(moveAction1)
actions.append(moveAction2)
break
default:
let moveAction = SCNAction.move(to: SCNVector3(0, 0, 0), duration: animationDuration)
actions.append(moveAction)
break
}
}
return SCNAction.sequence(actions)
}
}

View File

@ -321,46 +321,82 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
}
}
private func generatePremiumReactionIcon() -> UIImage? {
return generateImage(CGSize(width: 32.0, height: 32.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
if let backgroundImage = UIImage(bundleImageName: "Premium/BackgroundIcon"), let foregroundImage = UIImage(bundleImageName: "Premium/ForegroundIcon") {
context.saveGState()
if let cgImage = backgroundImage.cgImage {
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
}
let colorsArray: [CGColor] = [
UIColor(rgb: 0x6B93FF).cgColor,
UIColor(rgb: 0x6B93FF).cgColor,
UIColor(rgb: 0x976FFF).cgColor,
UIColor(rgb: 0xE46ACE).cgColor,
UIColor(rgb: 0xE46ACE).cgColor
]
var locations: [CGFloat] = [0.0, 0.15, 0.5, 0.85, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
context.restoreGState()
if let cgImage = foregroundImage.cgImage {
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
}
context.setFillColor(UIColor.white.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
private let starsCount = 7
private final class StarsNode: ASDisplayNode {
private let starNodes: [ASImageNode]
private var timer: SwiftSignalKit.Timer?
override init() {
let image = UIImage(bundleImageName: "Premium/ReactionsStar")
var starNodes: [ASImageNode] = []
for _ in 0 ..< starsCount {
let node = ASImageNode()
node.alpha = 0.0
node.image = image
node.displaysAsynchronously = false
starNodes.append(node)
}
})
self.starNodes = starNodes
super.init()
for node in starNodes {
self.addSubnode(node)
}
self.setup(firstTime: true)
self.timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
self?.setup()
}, queue: Queue.mainQueue())
self.timer?.start()
}
deinit {
self.timer?.invalidate()
}
func setup(firstTime: Bool = false) {
let size = CGSize(width: 32.0, height: 32.0)
let starSize = CGSize(width: 6.0, height: 8.0)
for node in self.starNodes {
if node.layer.animation(forKey: "transform.scale") == nil && node.layer.animation(forKey: "opacity") == nil {
let x = CGFloat.random(in: 0 ..< size.width)
let y = CGFloat.random(in: 0 ..< size.width)
let randomTargetScale = CGFloat.random(in: 0.8 ..< 1.0)
node.bounds = CGRect(origin: .zero, size: starSize)
node.position = CGPoint(x: x, y: y)
node.alpha = 1.0
let duration = CGFloat.random(in: 0.4 ..< 0.65)
let delay = firstTime ? CGFloat.random(in: 0.0 ..< 0.25) : 0.0
node.layer.animateScale(from: 0.001, to: randomTargetScale, duration: duration, delay: delay, removeOnCompletion: false, completion: { [weak self, weak node] _ in
let duration = CGFloat.random(in: 0.3 ..< 0.35)
node?.alpha = 0.0
node?.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { [weak self, weak node] _ in
node?.layer.removeAllAnimations()
self?.setup()
})
})
}
}
}
}
final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
var isExtracted: Bool = false
var backgroundView: UIVisualEffectView?
let backgroundMaskNode: ASImageNode
let backgroundOverlayNode: ASImageNode
let imageNode: ASImageNode
let maskImageNode: ASImageNode
private var backgroundView: UIVisualEffectView?
private let backgroundMaskNode: ASImageNode
private let backgroundOverlayNode: ASImageNode
private let imageNode: ASImageNode
private var starsNode: StarsNode?
private let maskContainerNode: ASDisplayNode
private let maskImageNode: ASImageNode
init(theme: PresentationTheme) {
self.backgroundMaskNode = ASImageNode()
@ -370,7 +406,7 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
self.backgroundMaskNode.image = UIImage(bundleImageName: "Premium/ReactionsBackground")
self.backgroundOverlayNode = ASImageNode()
self.backgroundOverlayNode.alpha = 0.05
self.backgroundOverlayNode.alpha = 0.1
self.backgroundOverlayNode.contentMode = .center
self.backgroundOverlayNode.displaysAsynchronously = false
self.backgroundOverlayNode.isUserInteractionEnabled = false
@ -382,20 +418,24 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
self.imageNode.isUserInteractionEnabled = false
self.imageNode.image = UIImage(bundleImageName: "Premium/ReactionsForeground")
self.maskContainerNode = ASDisplayNode()
self.maskImageNode = ASImageNode()
if let backgroundImage = UIImage(bundleImageName: "Premium/ReactionsBackground") {
self.maskImageNode.image = generateImage(CGSize(width: 40.0, height: 52.0), contextGenerator: { size, context in
self.maskImageNode.image = generateImage(CGSize(width: 40.0 * 4.0, height: 52.0 * 4.0), contextGenerator: { size, context in
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(origin: .zero, size: size))
if let cgImage = backgroundImage.cgImage {
let maskFrame = CGRect(origin: .zero, size: size).insetBy(dx: 4.0, dy: 10.0)
let maskFrame = CGRect(origin: .zero, size: size).insetBy(dx: 4.0 + 40.0 * 2.0 - 16.0, dy: 10.0 + 52.0 * 2.0 - 16.0)
context.clip(to: maskFrame, mask: cgImage)
}
context.setBlendMode(.clear)
context.fill(CGRect(origin: .zero, size: size))
})
}
self.maskImageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((40.0 - 40.0 * 4.0) / 2.0), y: floorToScreenPixels((52.0 - 52.0 * 4.0) / 2.0)), size: CGSize(width: 40.0 * 4.0, height: 52.0 * 4.0))
self.maskContainerNode.addSubnode(self.maskImageNode)
super.init()
@ -416,10 +456,37 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
backgroundView.mask = self.backgroundMaskNode.view
self.view.insertSubview(backgroundView, at: 0)
self.backgroundView = backgroundView
let starsNode = StarsNode()
starsNode.frame = CGRect(origin: .zero, size: CGSize(width: 32.0, height: 32.0))
self.backgroundView?.contentView.addSubview(starsNode.view)
self.starsNode = starsNode
}
func appear(animated: Bool) {
if animated {
let delay: Double = 0.1
let duration: Double = 0.85
let damping: CGFloat = 60.0
let initialScale: CGFloat = 0.25
self.maskImageNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, delay: delay, damping: damping)
self.backgroundView?.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, delay: delay, damping: damping)
self.backgroundOverlayNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, delay: delay, damping: damping)
self.imageNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, delay: delay, damping: damping)
Queue.mainQueue().after(0.25, {
let shimmerNode = ASImageNode()
shimmerNode.displaysAsynchronously = false
shimmerNode.image = generateGradientImage(size: CGSize(width: 32.0, height: 32.0), colors: [UIColor(rgb: 0xffffff, alpha: 0.0), UIColor(rgb: 0xffffff, alpha: 0.24), UIColor(rgb: 0xffffff, alpha: 0.0)], locations: [0.0, 0.5, 1.0], direction: .horizontal)
shimmerNode.frame = CGRect(origin: .zero, size: CGSize(width: 32.0, height: 32.0))
self.backgroundView?.contentView.addSubview(shimmerNode.view)
shimmerNode.layer.animatePosition(from: CGPoint(x: -60.0, y: 0.0), to: CGPoint(x: 60.0, y: 0.0), duration: 0.75, removeOnCompletion: false, additive: true, completion: { [weak shimmerNode] _ in
shimmerNode?.view.removeFromSuperview()
})
})
}
}
func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) {
@ -430,8 +497,7 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
self.imageNode.frame = bounds
}
var maskNode: ASDisplayNode? {
return self.maskImageNode
return self.maskContainerNode
}
}

View File

@ -6,6 +6,7 @@ import TelegramApi
public enum AssignAppStoreTransactionError {
case generic
case timeout
}
func _internal_sendAppStoreReceipt(account: Account, receipt: Data, restore: Bool) -> Signal<Never, AssignAppStoreTransactionError> {

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ministar@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B