mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
4251ee2c1b
commit
fbccdd47df
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
// }
|
||||
//}
|
@ -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",
|
||||
|
BIN
submodules/PremiumUI/MetalResources/chars.png
Normal file
BIN
submodules/PremiumUI/MetalResources/chars.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
79
submodules/PremiumUI/MetalResources/matrix.metal
Normal file
79
submodules/PremiumUI/MetalResources/matrix.metal
Normal 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);
|
||||
}
|
BIN
submodules/PremiumUI/MetalResources/random.jpg
Normal file
BIN
submodules/PremiumUI/MetalResources/random.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/swirl.scn
Normal file
BIN
submodules/PremiumUI/Resources/swirl.scn
Normal file
Binary file not shown.
59
submodules/PremiumUI/Sources/BadgeStarsView.swift
Normal file
59
submodules/PremiumUI/Sources/BadgeStarsView.swift
Normal 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)
|
||||
}
|
||||
}
|
198
submodules/PremiumUI/Sources/DataRainView.swift
Normal file
198
submodules/PremiumUI/Sources/DataRainView.swift
Normal 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()
|
||||
|
||||
}
|
||||
}
|
107
submodules/PremiumUI/Sources/FasterStarsView.swift
Normal file
107
submodules/PremiumUI/Sources/FasterStarsView.swift
Normal 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)
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
204
submodules/PremiumUI/Sources/SwirlStarsView.swift
Normal file
204
submodules/PremiumUI/Sources/SwirlStarsView.swift
Normal 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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
21
submodules/TelegramUI/Images.xcassets/Premium/ReactionsStar.imageset/Contents.json
vendored
Normal file
21
submodules/TelegramUI/Images.xcassets/Premium/ReactionsStar.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Premium/ReactionsStar.imageset/ministar@3x.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/ReactionsStar.imageset/ministar@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 341 B |
Loading…
x
Reference in New Issue
Block a user