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
7008cd1559
commit
fcd43ff537
@ -7597,8 +7597,7 @@ Sorry for the inconvenience.";
|
|||||||
"Premium.Avatar" = "Animated Profile Pictures";
|
"Premium.Avatar" = "Animated Profile Pictures";
|
||||||
"Premium.AvatarInfo" = "Video avatars animated in chat lists and chats to allow for additional self-expression.";
|
"Premium.AvatarInfo" = "Video avatars animated in chat lists and chats to allow for additional self-expression.";
|
||||||
|
|
||||||
"Premium.HelpUs" = "Help us maintain Premium Features while keeping Telegram free for everyone.";
|
|
||||||
|
|
||||||
"Premium.SubscribeFor" = "Subscribe for %@ per month";
|
"Premium.SubscribeFor" = "Subscribe for %@ per month";
|
||||||
|
|
||||||
|
"Premium.AboutTitle" = "ABOUT TELEGRAM PREMIUM";
|
||||||
|
"Premium.AboutText" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone.";
|
||||||
|
@ -18,6 +18,7 @@ public final class SolidRoundedButtonComponent: Component {
|
|||||||
public let gloss: Bool
|
public let gloss: Bool
|
||||||
public let iconName: String?
|
public let iconName: String?
|
||||||
public let iconPosition: SolidRoundedButtonIconPosition
|
public let iconPosition: SolidRoundedButtonIconPosition
|
||||||
|
public let isLoading: Bool
|
||||||
public let action: () -> Void
|
public let action: () -> Void
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@ -31,6 +32,7 @@ public final class SolidRoundedButtonComponent: Component {
|
|||||||
gloss: Bool = false,
|
gloss: Bool = false,
|
||||||
iconName: String? = nil,
|
iconName: String? = nil,
|
||||||
iconPosition: SolidRoundedButtonIconPosition = .left,
|
iconPosition: SolidRoundedButtonIconPosition = .left,
|
||||||
|
isLoading: Bool = false,
|
||||||
action: @escaping () -> Void
|
action: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.title = title
|
self.title = title
|
||||||
@ -43,6 +45,7 @@ public final class SolidRoundedButtonComponent: Component {
|
|||||||
self.gloss = gloss
|
self.gloss = gloss
|
||||||
self.iconName = iconName
|
self.iconName = iconName
|
||||||
self.iconPosition = iconPosition
|
self.iconPosition = iconPosition
|
||||||
|
self.isLoading = isLoading
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +80,9 @@ public final class SolidRoundedButtonComponent: Component {
|
|||||||
if lhs.iconPosition != rhs.iconPosition {
|
if lhs.iconPosition != rhs.iconPosition {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isLoading != rhs.isLoading {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +90,8 @@ public final class SolidRoundedButtonComponent: Component {
|
|||||||
private var component: SolidRoundedButtonComponent?
|
private var component: SolidRoundedButtonComponent?
|
||||||
private var button: SolidRoundedButtonView?
|
private var button: SolidRoundedButtonView?
|
||||||
|
|
||||||
|
private var currentIsLoading = false
|
||||||
|
|
||||||
public func update(component: SolidRoundedButtonComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
public func update(component: SolidRoundedButtonComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||||
if self.button == nil {
|
if self.button == nil {
|
||||||
let button = SolidRoundedButtonView(
|
let button = SolidRoundedButtonView(
|
||||||
@ -98,6 +105,7 @@ public final class SolidRoundedButtonComponent: Component {
|
|||||||
gloss: component.gloss
|
gloss: component.gloss
|
||||||
)
|
)
|
||||||
button.iconPosition = component.iconPosition
|
button.iconPosition = component.iconPosition
|
||||||
|
button.progressType = .embedded
|
||||||
button.icon = component.iconName.flatMap { UIImage(bundleImageName: $0) }
|
button.icon = component.iconName.flatMap { UIImage(bundleImageName: $0) }
|
||||||
self.button = button
|
self.button = button
|
||||||
self.addSubview(button)
|
self.addSubview(button)
|
||||||
@ -111,6 +119,15 @@ public final class SolidRoundedButtonComponent: Component {
|
|||||||
button.updateTheme(component.theme)
|
button.updateTheme(component.theme)
|
||||||
let height = button.updateLayout(width: availableSize.width, transition: .immediate)
|
let height = button.updateLayout(width: availableSize.width, transition: .immediate)
|
||||||
transition.setFrame(view: button, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height)), completion: nil)
|
transition.setFrame(view: button, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height)), completion: nil)
|
||||||
|
|
||||||
|
if self.currentIsLoading != component.isLoading {
|
||||||
|
self.currentIsLoading = component.isLoading
|
||||||
|
if component.isLoading {
|
||||||
|
button.transitionToProgress()
|
||||||
|
} else {
|
||||||
|
button.transitionFromProgress()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.component = component
|
self.component = component
|
||||||
|
@ -334,6 +334,7 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega
|
|||||||
|
|
||||||
var statusBarTransition = transition
|
var statusBarTransition = transition
|
||||||
|
|
||||||
|
var ignoreInputHeight = false
|
||||||
if let pending = self.state.pending {
|
if let pending = self.state.pending {
|
||||||
if pending.isReady {
|
if pending.isReady {
|
||||||
self.state.pending = nil
|
self.state.pending = nil
|
||||||
@ -344,6 +345,9 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega
|
|||||||
if pending.value.value.view.disableAutomaticKeyboardHandling.isEmpty {
|
if pending.value.value.view.disableAutomaticKeyboardHandling.isEmpty {
|
||||||
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
|
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
|
||||||
}
|
}
|
||||||
|
if case .regular = layout.metrics.widthClass, pending.value.layout.inputHeight == nil {
|
||||||
|
ignoreInputHeight = true
|
||||||
|
}
|
||||||
self.topTransition(from: previous, to: pending.value, transitionType: pending.transitionType, layout: updatedLayout, transition: pending.transition)
|
self.topTransition(from: previous, to: pending.value, transitionType: pending.transitionType, layout: updatedLayout, transition: pending.transition)
|
||||||
self.state.top?.value.isInFocus = self.isInFocus
|
self.state.top?.value.isInFocus = self.isInFocus
|
||||||
statusBarTransition = pending.transition
|
statusBarTransition = pending.transition
|
||||||
@ -369,6 +373,9 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega
|
|||||||
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
|
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ignoreInputHeight {
|
||||||
|
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
|
||||||
|
}
|
||||||
self.applyLayout(layout: updatedLayout, to: top, isMaster: true, transition: transition)
|
self.applyLayout(layout: updatedLayout, to: top, isMaster: true, transition: transition)
|
||||||
if let childTransition = self.state.transition, childTransition.coordinator.isInteractive {
|
if let childTransition = self.state.transition, childTransition.coordinator.isInteractive {
|
||||||
switch childTransition.type {
|
switch childTransition.type {
|
||||||
|
@ -5,15 +5,6 @@ import StoreKit
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
|
||||||
private final class PaymentTransactionContext {
|
|
||||||
var state: SKPaymentTransactionState?
|
|
||||||
let subscriber: (SKPaymentTransactionState) -> Void
|
|
||||||
|
|
||||||
init(subscriber: @escaping (SKPaymentTransactionState) -> Void) {
|
|
||||||
self.subscriber = subscriber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class InAppPurchaseManager: NSObject {
|
public final class InAppPurchaseManager: NSObject {
|
||||||
public final class Product {
|
public final class Product {
|
||||||
let skProduct: SKProduct
|
let skProduct: SKProduct
|
||||||
@ -30,14 +21,31 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PurchaseResult {
|
public enum PurchaseState {
|
||||||
case success
|
case purchased(transactionId: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PurchaseError {
|
public enum PurchaseError {
|
||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class PaymentTransactionContext {
|
||||||
|
var state: SKPaymentTransactionState?
|
||||||
|
let subscriber: (TransactionState) -> Void
|
||||||
|
|
||||||
|
init(subscriber: @escaping (TransactionState) -> Void) {
|
||||||
|
self.subscriber = subscriber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TransactionState {
|
||||||
|
case purchased(transactionId: String?)
|
||||||
|
case restored(transactionId: String?)
|
||||||
|
case purchasing
|
||||||
|
case failed
|
||||||
|
case deferred
|
||||||
|
}
|
||||||
|
|
||||||
private let premiumProductId: String
|
private let premiumProductId: String
|
||||||
|
|
||||||
private var products: [Product] = []
|
private var products: [Product] = []
|
||||||
@ -57,7 +65,7 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
SKPaymentQueue.default().remove(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestProducts() {
|
private func requestProducts() {
|
||||||
@ -79,27 +87,28 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
return self.productsPromise.get()
|
return self.productsPromise.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func buyProduct(_ product: Product, account: Account) -> Signal<PurchaseResult, PurchaseError> {
|
public func buyProduct(_ product: Product, account: Account) -> Signal<PurchaseState, PurchaseError> {
|
||||||
let payment = SKMutablePayment(product: product.skProduct)
|
let payment = SKMutablePayment(product: product.skProduct)
|
||||||
payment.applicationUsername = "\(account.peerId.id._internalGetInt64Value())"
|
|
||||||
SKPaymentQueue.default().add(payment)
|
SKPaymentQueue.default().add(payment)
|
||||||
|
|
||||||
let productIdentifier = payment.productIdentifier
|
let productIdentifier = payment.productIdentifier
|
||||||
let signal = Signal<PurchaseResult, PurchaseError> { subscriber in
|
let signal = Signal<PurchaseState, PurchaseError> { subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
self.stateQueue.async {
|
self.stateQueue.async {
|
||||||
let paymentContext = PaymentTransactionContext(subscriber: { state in
|
let paymentContext = PaymentTransactionContext(subscriber: { state in
|
||||||
switch state {
|
switch state {
|
||||||
case .purchased, .restored:
|
case let .purchased(transactionId), let .restored(transactionId):
|
||||||
subscriber.putNext(.success)
|
if let transactionId = transactionId {
|
||||||
|
subscriber.putNext(.purchased(transactionId: transactionId))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
|
} else {
|
||||||
|
subscriber.putError(.generic)
|
||||||
|
}
|
||||||
case .failed:
|
case .failed:
|
||||||
subscriber.putError(.generic)
|
subscriber.putError(.generic)
|
||||||
case .deferred, .purchasing:
|
case .deferred, .purchasing:
|
||||||
break
|
break
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self.paymentContexts[productIdentifier] = paymentContext
|
self.paymentContexts[productIdentifier] = paymentContext
|
||||||
@ -135,7 +144,24 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
|
|||||||
let productIdentifier = transaction.payment.productIdentifier
|
let productIdentifier = transaction.payment.productIdentifier
|
||||||
self.stateQueue.async {
|
self.stateQueue.async {
|
||||||
if let context = self.paymentContexts[productIdentifier] {
|
if let context = self.paymentContexts[productIdentifier] {
|
||||||
context.subscriber(transaction.transactionState)
|
let transactionState: TransactionState?
|
||||||
|
switch transaction.transactionState {
|
||||||
|
case .purchased:
|
||||||
|
transactionState = .purchased(transactionId: transaction.transactionIdentifier)
|
||||||
|
case .restored:
|
||||||
|
transactionState = .restored(transactionId: transaction.transactionIdentifier)
|
||||||
|
case .failed:
|
||||||
|
transactionState = .failed
|
||||||
|
case .purchasing:
|
||||||
|
transactionState = .purchasing
|
||||||
|
case .deferred:
|
||||||
|
transactionState = .deferred
|
||||||
|
default:
|
||||||
|
transactionState = nil
|
||||||
|
}
|
||||||
|
if let transactionState = transactionState {
|
||||||
|
context.subscriber(transactionState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 25 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1002 B |
Binary file not shown.
@ -53,6 +53,9 @@ private class StarComponent: Component {
|
|||||||
|
|
||||||
private let sceneView: SCNView
|
private let sceneView: SCNView
|
||||||
|
|
||||||
|
private var previousInteractionTimestamp: Double = 0.0
|
||||||
|
private var timer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.sceneView = SCNView(frame: frame)
|
self.sceneView = SCNView(frame: frame)
|
||||||
self.sceneView.backgroundColor = .clear
|
self.sceneView.backgroundColor = .clear
|
||||||
@ -79,11 +82,17 @@ private class StarComponent: Component {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.timer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
|
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
|
||||||
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
|
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.previousInteractionTimestamp = CACurrentMediaTime()
|
||||||
|
|
||||||
var left = true
|
var left = true
|
||||||
if let view = gesture.view {
|
if let view = gesture.view {
|
||||||
let point = gesture.location(in: view)
|
let point = gesture.location(in: view)
|
||||||
@ -133,6 +142,8 @@ private class StarComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.previousInteractionTimestamp = CACurrentMediaTime()
|
||||||
|
|
||||||
if #available(iOS 11.0, *) {
|
if #available(iOS 11.0, *) {
|
||||||
node.removeAnimation(forKey: "rotate", blendOutDuration: 0.1)
|
node.removeAnimation(forKey: "rotate", blendOutDuration: 0.1)
|
||||||
node.removeAnimation(forKey: "tapRotate", blendOutDuration: 0.1)
|
node.removeAnimation(forKey: "tapRotate", blendOutDuration: 0.1)
|
||||||
@ -189,6 +200,17 @@ private class StarComponent: Component {
|
|||||||
self.setupShineAnimation()
|
self.setupShineAnimation()
|
||||||
|
|
||||||
self.playAppearanceAnimation(explode: true)
|
self.playAppearanceAnimation(explode: true)
|
||||||
|
|
||||||
|
self.previousInteractionTimestamp = CACurrentMediaTime()
|
||||||
|
self.timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let currentTimestamp = CACurrentMediaTime()
|
||||||
|
if currentTimestamp > strongSelf.previousInteractionTimestamp + 5.0 {
|
||||||
|
strongSelf.playAppearanceAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, queue: Queue.mainQueue())
|
||||||
|
self.timer?.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupGradientAnimation() {
|
private func setupGradientAnimation() {
|
||||||
@ -240,6 +262,8 @@ private class StarComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.previousInteractionTimestamp = CACurrentMediaTime()
|
||||||
|
|
||||||
if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particles = scene.rootNode.childNode(withName: "particles", recursively: false) {
|
if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particles = scene.rootNode.childNode(withName: "particles", recursively: false) {
|
||||||
let particleSystem = particles.particleSystems?.first
|
let particleSystem = particles.particleSystems?.first
|
||||||
particleSystem?.particleColorVariation = SCNVector4(0.15, 0.2, 0.35, 0.3)
|
particleSystem?.particleColorVariation = SCNVector4(0.15, 0.2, 0.35, 0.3)
|
||||||
@ -251,7 +275,7 @@ private class StarComponent: Component {
|
|||||||
Queue.mainQueue().after(1.0) {
|
Queue.mainQueue().after(1.0) {
|
||||||
node.physicsField?.isActive = false
|
node.physicsField?.isActive = false
|
||||||
particles.particleSystems?.first?.birthRate = 1.2
|
particles.particleSystems?.first?.birthRate = 1.2
|
||||||
particleSystem?.particleVelocity = 1.65
|
particleSystem?.particleVelocity = 1.0
|
||||||
particleSystem?.particleLifeSpan = 4.0
|
particleSystem?.particleLifeSpan = 4.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,6 +293,10 @@ private class StarComponent: Component {
|
|||||||
let to = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: toValue)
|
let to = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: toValue)
|
||||||
let distance = rad2deg(to.w - from.w)
|
let distance = rad2deg(to.w - from.w)
|
||||||
|
|
||||||
|
guard !distance.isZero else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let springAnimation = CASpringAnimation(keyPath: "rotation")
|
let springAnimation = CASpringAnimation(keyPath: "rotation")
|
||||||
springAnimation.fromValue = NSValue(scnVector4: from)
|
springAnimation.fromValue = NSValue(scnVector4: from)
|
||||||
springAnimation.toValue = NSValue(scnVector4: to)
|
springAnimation.toValue = NSValue(scnVector4: to)
|
||||||
@ -302,9 +330,11 @@ private class StarComponent: Component {
|
|||||||
private final class SectionGroupComponent: Component {
|
private final class SectionGroupComponent: Component {
|
||||||
public final class Item: Equatable {
|
public final class Item: Equatable {
|
||||||
public let content: AnyComponentWithIdentity<Empty>
|
public let content: AnyComponentWithIdentity<Empty>
|
||||||
|
public let action: () -> Void
|
||||||
|
|
||||||
public init(_ content: AnyComponentWithIdentity<Empty>) {
|
public init(_ content: AnyComponentWithIdentity<Empty>, action: @escaping () -> Void) {
|
||||||
self.content = content
|
self.content = content
|
||||||
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||||
@ -318,15 +348,18 @@ private final class SectionGroupComponent: Component {
|
|||||||
|
|
||||||
public let items: [Item]
|
public let items: [Item]
|
||||||
public let backgroundColor: UIColor
|
public let backgroundColor: UIColor
|
||||||
|
public let selectionColor: UIColor
|
||||||
public let separatorColor: UIColor
|
public let separatorColor: UIColor
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
items: [Item],
|
items: [Item],
|
||||||
backgroundColor: UIColor,
|
backgroundColor: UIColor,
|
||||||
|
selectionColor: UIColor,
|
||||||
separatorColor: UIColor
|
separatorColor: UIColor
|
||||||
) {
|
) {
|
||||||
self.items = items
|
self.items = items
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
|
self.selectionColor = selectionColor
|
||||||
self.separatorColor = separatorColor
|
self.separatorColor = separatorColor
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,6 +370,9 @@ private final class SectionGroupComponent: Component {
|
|||||||
if lhs.backgroundColor != rhs.backgroundColor {
|
if lhs.backgroundColor != rhs.backgroundColor {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.selectionColor != rhs.selectionColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.separatorColor != rhs.separatorColor {
|
if lhs.separatorColor != rhs.separatorColor {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -344,28 +380,34 @@ private final class SectionGroupComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class View: UIView {
|
public final class View: UIView {
|
||||||
private let backgroundView: UIView
|
private var buttonViews: [AnyHashable: HighlightTrackingButton] = [:]
|
||||||
private var itemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
private var itemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||||
private var separatorViews: [UIView] = []
|
private var separatorViews: [UIView] = []
|
||||||
|
|
||||||
|
private var component: SectionGroupComponent?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.backgroundView = UIView()
|
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.addSubview(self.backgroundView)
|
|
||||||
self.backgroundView.layer.cornerRadius = 10.0
|
|
||||||
self.backgroundView.layer.masksToBounds = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func buttonPressed(_ sender: HighlightTrackingButton) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (id, _) = self.buttonViews.first(where: { $0.value === sender }), let item = component.items.first(where: { $0.content.id == id }) {
|
||||||
|
item.action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: SectionGroupComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(component: SectionGroupComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
|
|
||||||
self.backgroundView.backgroundColor = component.backgroundColor
|
self.backgroundColor = component.backgroundColor
|
||||||
|
|
||||||
var size = CGSize(width: availableSize.width, height: 0.0)
|
var size = CGSize(width: availableSize.width, height: 0.0)
|
||||||
|
|
||||||
@ -375,8 +417,19 @@ private final class SectionGroupComponent: Component {
|
|||||||
for item in component.items {
|
for item in component.items {
|
||||||
validIds.append(item.content.id)
|
validIds.append(item.content.id)
|
||||||
|
|
||||||
|
let buttonView: HighlightTrackingButton
|
||||||
let itemView: ComponentHostView<Empty>
|
let itemView: ComponentHostView<Empty>
|
||||||
var itemTransition = transition
|
var itemTransition = transition
|
||||||
|
|
||||||
|
if let current = self.buttonViews[item.content.id] {
|
||||||
|
buttonView = current
|
||||||
|
} else {
|
||||||
|
buttonView = HighlightTrackingButton()
|
||||||
|
buttonView.addTarget(self, action: #selector(self.buttonPressed(_:)), for: .touchUpInside)
|
||||||
|
self.buttonViews[item.content.id] = buttonView
|
||||||
|
self.addSubview(buttonView)
|
||||||
|
}
|
||||||
|
|
||||||
if let current = self.itemViews[item.content.id] {
|
if let current = self.itemViews[item.content.id] {
|
||||||
itemView = current
|
itemView = current
|
||||||
} else {
|
} else {
|
||||||
@ -393,7 +446,19 @@ private final class SectionGroupComponent: Component {
|
|||||||
)
|
)
|
||||||
|
|
||||||
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: itemSize)
|
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: itemSize)
|
||||||
|
buttonView.frame = CGRect(origin: itemFrame.origin, size: CGSize(width: availableSize.width, height: itemSize.height + UIScreenPixel))
|
||||||
itemView.frame = CGRect(origin: CGPoint(x: itemFrame.minX + sideInset, y: itemFrame.minY + floor((itemFrame.height - itemSize.height) / 2.0)), size: itemSize)
|
itemView.frame = CGRect(origin: CGPoint(x: itemFrame.minX + sideInset, y: itemFrame.minY + floor((itemFrame.height - itemSize.height) / 2.0)), size: itemSize)
|
||||||
|
itemView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
buttonView.highligthedChanged = { [weak buttonView] highlighted in
|
||||||
|
if highlighted {
|
||||||
|
buttonView?.backgroundColor = component.selectionColor
|
||||||
|
} else {
|
||||||
|
UIView.animate(withDuration: 0.3, animations: {
|
||||||
|
buttonView?.backgroundColor = nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
size.height += itemSize.height
|
size.height += itemSize.height
|
||||||
|
|
||||||
@ -431,7 +496,7 @@ private final class SectionGroupComponent: Component {
|
|||||||
self.separatorViews.removeSubrange((component.items.count - 1) ..< self.separatorViews.count)
|
self.separatorViews.removeSubrange((component.items.count - 1) ..< self.separatorViews.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size), completion: nil)
|
self.component = component
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
@ -508,10 +573,15 @@ private final class ScrollComponent<ChildEnvironment: Equatable>: Component {
|
|||||||
}
|
}
|
||||||
self.delegate = self
|
self.delegate = self
|
||||||
self.showsVerticalScrollIndicator = false
|
self.showsVerticalScrollIndicator = false
|
||||||
|
self.canCancelContentTouches = true
|
||||||
|
|
||||||
self.addSubview(self.contentView)
|
self.addSubview(self.contentView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private var ignoreDidScroll = false
|
private var ignoreDidScroll = false
|
||||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
guard let component = self.component, !self.ignoreDidScroll else {
|
guard let component = self.component, !self.ignoreDidScroll else {
|
||||||
@ -740,6 +810,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
let fade = Child(RoundedRectangle.self)
|
let fade = Child(RoundedRectangle.self)
|
||||||
let text = Child(MultilineTextComponent.self)
|
let text = Child(MultilineTextComponent.self)
|
||||||
let section = Child(SectionGroupComponent.self)
|
let section = Child(SectionGroupComponent.self)
|
||||||
|
let infoBackground = Child(RoundedRectangle.self)
|
||||||
|
let infoTitle = Child(MultilineTextComponent.self)
|
||||||
let infoText = Child(MultilineTextComponent.self)
|
let infoText = Child(MultilineTextComponent.self)
|
||||||
|
|
||||||
return { context in
|
return { context in
|
||||||
@ -780,7 +852,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
.position(CGPoint(x: fade.size.width / 2.0, y: fade.size.height / 2.0))
|
.position(CGPoint(x: fade.size.width / 2.0, y: fade.size.height / 2.0))
|
||||||
)
|
)
|
||||||
|
|
||||||
size.height += 183.0 + 10.0
|
size.height += 183.0 + 10.0 + environment.navigationHeight - 56.0
|
||||||
|
|
||||||
let textColor = theme.list.itemPrimaryTextColor
|
let textColor = theme.list.itemPrimaryTextColor
|
||||||
let titleColor = theme.list.itemPrimaryTextColor
|
let titleColor = theme.list.itemPrimaryTextColor
|
||||||
@ -833,7 +905,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
arrowColor: arrowColor
|
arrowColor: arrowColor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
action: {
|
||||||
|
|
||||||
|
}
|
||||||
),
|
),
|
||||||
SectionGroupComponent.Item(
|
SectionGroupComponent.Item(
|
||||||
AnyComponentWithIdentity(
|
AnyComponentWithIdentity(
|
||||||
@ -852,7 +927,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
arrowColor: arrowColor
|
arrowColor: arrowColor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
action: {
|
||||||
|
|
||||||
|
}
|
||||||
),
|
),
|
||||||
SectionGroupComponent.Item(
|
SectionGroupComponent.Item(
|
||||||
AnyComponentWithIdentity(
|
AnyComponentWithIdentity(
|
||||||
@ -871,7 +949,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
arrowColor: arrowColor
|
arrowColor: arrowColor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
action: {
|
||||||
|
|
||||||
|
}
|
||||||
),
|
),
|
||||||
SectionGroupComponent.Item(
|
SectionGroupComponent.Item(
|
||||||
AnyComponentWithIdentity(
|
AnyComponentWithIdentity(
|
||||||
@ -890,7 +971,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
arrowColor: arrowColor
|
arrowColor: arrowColor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
action: {
|
||||||
|
|
||||||
|
}
|
||||||
),
|
),
|
||||||
SectionGroupComponent.Item(
|
SectionGroupComponent.Item(
|
||||||
AnyComponentWithIdentity(
|
AnyComponentWithIdentity(
|
||||||
@ -909,7 +993,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
arrowColor: arrowColor
|
arrowColor: arrowColor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
action: {
|
||||||
|
|
||||||
|
}
|
||||||
),
|
),
|
||||||
SectionGroupComponent.Item(
|
SectionGroupComponent.Item(
|
||||||
AnyComponentWithIdentity(
|
AnyComponentWithIdentity(
|
||||||
@ -928,7 +1015,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
arrowColor: arrowColor
|
arrowColor: arrowColor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
action: {
|
||||||
|
|
||||||
|
}
|
||||||
),
|
),
|
||||||
SectionGroupComponent.Item(
|
SectionGroupComponent.Item(
|
||||||
AnyComponentWithIdentity(
|
AnyComponentWithIdentity(
|
||||||
@ -947,7 +1037,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
arrowColor: arrowColor
|
arrowColor: arrowColor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
action: {
|
||||||
|
|
||||||
|
}
|
||||||
),
|
),
|
||||||
SectionGroupComponent.Item(
|
SectionGroupComponent.Item(
|
||||||
AnyComponentWithIdentity(
|
AnyComponentWithIdentity(
|
||||||
@ -966,10 +1059,14 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
arrowColor: arrowColor
|
arrowColor: arrowColor
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
action: {
|
||||||
|
|
||||||
|
}
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
||||||
|
selectionColor: environment.theme.list.itemHighlightedBackgroundColor,
|
||||||
separatorColor: environment.theme.list.itemBlocksSeparatorColor
|
separatorColor: environment.theme.list.itemBlocksSeparatorColor
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -978,31 +1075,65 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
context.add(section
|
context.add(section
|
||||||
.position(CGPoint(x: availableWidth / 2.0, y: size.height + section.size.height / 2.0))
|
.position(CGPoint(x: availableWidth / 2.0, y: size.height + section.size.height / 2.0))
|
||||||
|
.clipsToBounds(true)
|
||||||
|
.cornerRadius(10.0)
|
||||||
)
|
)
|
||||||
size.height += section.size.height
|
size.height += section.size.height
|
||||||
size.height += 17.0
|
size.height += 23.0
|
||||||
|
|
||||||
let infoText = infoText.update(
|
let textSideInset: CGFloat = 16.0
|
||||||
|
let textPadding: CGFloat = 13.0
|
||||||
|
|
||||||
|
let infoTitle = infoTitle.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: .plain(
|
text: .plain(
|
||||||
NSAttributedString(
|
NSAttributedString(string: strings.Premium_AboutTitle.uppercased(), font: Font.regular(14.0), textColor: environment.theme.list.freeTextColor)
|
||||||
string: strings.Premium_HelpUs,
|
|
||||||
font: Font.regular(15.0),
|
|
||||||
textColor: environment.theme.list.freeTextColor
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .natural,
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
lineSpacing: 0.2
|
lineSpacing: 0.2
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
availableSize: CGSize(width: availableWidth - sideInsets, height: 120.0),
|
availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude),
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
context.add(infoText
|
context.add(infoTitle
|
||||||
.position(CGPoint(x: size.width / 2.0, y: size.height + infoText.size.height / 2.0))
|
.position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + infoTitle.size.width / 2.0, y: size.height + infoTitle.size.height / 2.0))
|
||||||
)
|
)
|
||||||
size.height += text.size.height
|
size.height += infoTitle.size.height
|
||||||
|
size.height += 3.0
|
||||||
|
|
||||||
|
let infoText = infoText.update(
|
||||||
|
component: MultilineTextComponent(
|
||||||
|
text: .markdown(
|
||||||
|
text: strings.Premium_AboutText,
|
||||||
|
attributes: markdownAttributes
|
||||||
|
),
|
||||||
|
horizontalAlignment: .natural,
|
||||||
|
maximumNumberOfLines: 0,
|
||||||
|
lineSpacing: 0.2
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
availableSize: CGSize(width: availableWidth - sideInsets - textSideInset * 2.0, height: .greatestFiniteMagnitude),
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
|
||||||
|
let infoBackground = infoBackground.update(
|
||||||
|
component: RoundedRectangle(
|
||||||
|
color: environment.theme.list.itemBlocksBackgroundColor,
|
||||||
|
cornerRadius: 10.0
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
availableSize: CGSize(width: availableWidth - sideInsets, height: infoText.size.height + textPadding * 2.0),
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
context.add(infoBackground
|
||||||
|
.position(CGPoint(x: size.width / 2.0, y: size.height + infoBackground.size.height / 2.0))
|
||||||
|
)
|
||||||
|
context.add(infoText
|
||||||
|
.position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + infoText.size.width / 2.0, y: size.height + textPadding + infoText.size.height / 2.0))
|
||||||
|
)
|
||||||
|
size.height += infoBackground.size.height
|
||||||
size.height += 3.0
|
size.height += 3.0
|
||||||
size.height += scrollEnvironment.insets.bottom
|
size.height += scrollEnvironment.insets.bottom
|
||||||
|
|
||||||
@ -1089,7 +1220,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
var inProgress = false
|
var inProgress = false
|
||||||
var premiumProduct: InAppPurchaseManager.Product?
|
var premiumProduct: InAppPurchaseManager.Product?
|
||||||
private var disposable: Disposable?
|
private var disposable: Disposable?
|
||||||
private var actionDisposable = MetaDisposable()
|
private var paymentDisposable = MetaDisposable()
|
||||||
|
private var activationDisposable = MetaDisposable()
|
||||||
|
|
||||||
init(context: AccountContext, updateInProgress: @escaping (Bool) -> Void, completion: @escaping () -> Void) {
|
init(context: AccountContext, updateInProgress: @escaping (Bool) -> Void, completion: @escaping () -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -1111,7 +1243,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.disposable?.dispose()
|
self.disposable?.dispose()
|
||||||
self.actionDisposable.dispose()
|
self.paymentDisposable.dispose()
|
||||||
|
self.activationDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func buy() {
|
func buy() {
|
||||||
@ -1124,11 +1257,18 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
self.updateInProgress(true)
|
self.updateInProgress(true)
|
||||||
self.updated(transition: .immediate)
|
self.updated(transition: .immediate)
|
||||||
|
|
||||||
self.actionDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct, account: self.context.account)
|
self.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct, account: self.context.account)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||||
|
if let strongSelf = self, case let .purchased(transactionId) = status {
|
||||||
|
strongSelf.activationDisposable.set((strongSelf.context.engine.payments.assignAppStoreTransaction(transactionId: transactionId)
|
||||||
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
|
|
||||||
|
}, completed: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.completion()
|
strongSelf.completion()
|
||||||
}
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
}, error: { [weak self] _ in
|
}, error: { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.inProgress = false
|
strongSelf.inProgress = false
|
||||||
@ -1202,7 +1342,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
component: SolidRoundedButtonComponent(
|
component: SolidRoundedButtonComponent(
|
||||||
title: environment.strings.Premium_SubscribeFor(state.premiumProduct?.price ?? "—").string,
|
title: environment.strings.Premium_SubscribeFor(state.premiumProduct?.price ?? "—").string,
|
||||||
theme: SolidRoundedButtonComponent.Theme(
|
theme: SolidRoundedButtonComponent.Theme(
|
||||||
backgroundColor: .black,
|
backgroundColor: UIColor(rgb: 0x8878ff),
|
||||||
backgroundColors: [
|
backgroundColors: [
|
||||||
UIColor(rgb: 0x0077ff),
|
UIColor(rgb: 0x0077ff),
|
||||||
UIColor(rgb: 0x6b93ff),
|
UIColor(rgb: 0x6b93ff),
|
||||||
@ -1214,6 +1354,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
height: 50.0,
|
height: 50.0,
|
||||||
cornerRadius: 10.0,
|
cornerRadius: 10.0,
|
||||||
gloss: true,
|
gloss: true,
|
||||||
|
isLoading: state.inProgress,
|
||||||
action: {
|
action: {
|
||||||
state.buy()
|
state.buy()
|
||||||
}
|
}
|
||||||
@ -1263,6 +1404,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let topInset: CGFloat = environment.navigationHeight - 56.0
|
||||||
|
|
||||||
context.add(background
|
context.add(background
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||||
)
|
)
|
||||||
@ -1274,7 +1417,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
let topPanelAlpha: CGFloat
|
let topPanelAlpha: CGFloat
|
||||||
let titleOffset: CGFloat
|
let titleOffset: CGFloat
|
||||||
let titleScale: CGFloat
|
let titleScale: CGFloat
|
||||||
let titleOffsetDelta = 160.0 - environment.navigationHeight / 2.0
|
let titleOffsetDelta = (topInset + 160.0) - (environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)
|
||||||
|
|
||||||
if let topContentOffset = state.topContentOffset {
|
if let topContentOffset = state.topContentOffset {
|
||||||
topPanelAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0
|
topPanelAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0
|
||||||
@ -1289,7 +1432,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.add(star
|
context.add(star
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 - 30.0 - titleOffset * titleScale))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: topInset + star.size.height / 2.0 - 30.0 - titleOffset * titleScale))
|
||||||
.scale(titleScale)
|
.scale(titleScale)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1303,7 +1446,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
|
|
||||||
context.add(title
|
context.add(title
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: max(160.0 - titleOffset, environment.navigationHeight / 2.0)))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: max(topInset + 160.0 - titleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)))
|
||||||
.scale(titleScale)
|
.scale(titleScale)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1340,7 +1483,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
|||||||
return self._ready
|
return self._ready
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(context: AccountContext) {
|
public init(context: AccountContext, modal: Bool = true) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
var updateInProgressImpl: ((Bool) -> Void)?
|
var updateInProgressImpl: ((Bool) -> Void)?
|
||||||
@ -1357,10 +1500,13 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
if modal {
|
||||||
let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
|
let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||||
self.navigationItem.setLeftBarButton(cancelItem, animated: false)
|
self.navigationItem.setLeftBarButton(cancelItem, animated: false)
|
||||||
|
|
||||||
self.navigationPresentation = .modal
|
self.navigationPresentation = .modal
|
||||||
|
} else {
|
||||||
|
self.navigationPresentation = .modalInLargeLayout
|
||||||
|
}
|
||||||
|
|
||||||
updateInProgressImpl = { [weak self] inProgress in
|
updateInProgressImpl = { [weak self] inProgress in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
@ -71,6 +71,8 @@ private class PremiumLimitAnimationComponent: Component {
|
|||||||
private let badgeIcon: UIImageView
|
private let badgeIcon: UIImageView
|
||||||
private let badgeCountLabel: RollingLabel
|
private let badgeCountLabel: RollingLabel
|
||||||
|
|
||||||
|
private let hapticFeedback = HapticFeedback()
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.container = SimpleLayer()
|
self.container = SimpleLayer()
|
||||||
self.container.masksToBounds = true
|
self.container.masksToBounds = true
|
||||||
@ -102,6 +104,8 @@ private class PremiumLimitAnimationComponent: Component {
|
|||||||
self.badgeMaskView = UIView()
|
self.badgeMaskView = UIView()
|
||||||
self.badgeMaskView.addSubview(self.badgeMaskBackgroundView)
|
self.badgeMaskView.addSubview(self.badgeMaskBackgroundView)
|
||||||
self.badgeMaskView.addSubview(self.badgeMaskArrowView)
|
self.badgeMaskView.addSubview(self.badgeMaskArrowView)
|
||||||
|
self.badgeMaskView.layer.rasterizationScale = UIScreenScale
|
||||||
|
self.badgeMaskView.layer.shouldRasterize = true
|
||||||
self.badgeView.mask = self.badgeMaskView
|
self.badgeView.mask = self.badgeMaskView
|
||||||
|
|
||||||
self.badgeForeground = SimpleLayer()
|
self.badgeForeground = SimpleLayer()
|
||||||
@ -156,6 +160,10 @@ private class PremiumLimitAnimationComponent: Component {
|
|||||||
rotateAnimation.fillMode = .forwards
|
rotateAnimation.fillMode = .forwards
|
||||||
rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
||||||
|
|
||||||
|
Queue.mainQueue().after(0.5, {
|
||||||
|
self.hapticFeedback.impact(.light)
|
||||||
|
})
|
||||||
|
|
||||||
let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
||||||
returnAnimation.fromValue = 0.2 as NSNumber
|
returnAnimation.fromValue = 0.2 as NSNumber
|
||||||
returnAnimation.toValue = 0.0 as NSNumber
|
returnAnimation.toValue = 0.0 as NSNumber
|
||||||
|
@ -11,7 +11,7 @@ private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter:
|
|||||||
context.setStrokeColor(color.cgColor)
|
context.setStrokeColor(color.cgColor)
|
||||||
context.setLineWidth(lineWidth)
|
context.setLineWidth(lineWidth)
|
||||||
context.setLineCap(.round)
|
context.setLineCap(.round)
|
||||||
let cutoutAngle: CGFloat = CGFloat.pi * 30.0 / 180.0
|
let cutoutAngle: CGFloat = CGFloat.pi * 60.0 / 180.0
|
||||||
context.addArc(center: CGPoint(x: size.width / 2.0, y: size.height / 2.0), radius: size.width / 2.0 - lineWidth / 2.0, startAngle: 0.0, endAngle: CGFloat.pi * 2.0 - cutoutAngle, clockwise: false)
|
context.addArc(center: CGPoint(x: size.width / 2.0, y: size.height / 2.0), radius: size.width / 2.0 - lineWidth / 2.0, startAngle: 0.0, endAngle: CGFloat.pi * 2.0 - cutoutAngle, clockwise: false)
|
||||||
context.strokePath()
|
context.strokePath()
|
||||||
})
|
})
|
||||||
@ -52,6 +52,11 @@ public enum SolidRoundedButtonIconPosition {
|
|||||||
case right
|
case right
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum SolidRoundedButtonProgressType {
|
||||||
|
case fullSize
|
||||||
|
case embedded
|
||||||
|
}
|
||||||
|
|
||||||
public final class SolidRoundedButtonNode: ASDisplayNode {
|
public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||||
private var theme: SolidRoundedButtonTheme
|
private var theme: SolidRoundedButtonTheme
|
||||||
private var font: SolidRoundedButtonFont
|
private var font: SolidRoundedButtonFont
|
||||||
@ -518,6 +523,8 @@ public final class SolidRoundedButtonView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var progressType: SolidRoundedButtonProgressType = .fullSize
|
||||||
|
|
||||||
public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
|
public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.font = font
|
self.font = font
|
||||||
@ -530,9 +537,8 @@ public final class SolidRoundedButtonView: UIView {
|
|||||||
self.buttonBackgroundNode.clipsToBounds = true
|
self.buttonBackgroundNode.clipsToBounds = true
|
||||||
self.buttonBackgroundNode.layer.cornerRadius = cornerRadius
|
self.buttonBackgroundNode.layer.cornerRadius = cornerRadius
|
||||||
|
|
||||||
|
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
|
||||||
if theme.backgroundColors.count > 1 {
|
if theme.backgroundColors.count > 1 {
|
||||||
self.buttonBackgroundNode.backgroundColor = nil
|
|
||||||
|
|
||||||
var locations: [CGFloat] = []
|
var locations: [CGFloat] = []
|
||||||
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
|
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
|
||||||
for i in 0 ..< theme.backgroundColors.count {
|
for i in 0 ..< theme.backgroundColors.count {
|
||||||
@ -544,8 +550,6 @@ public final class SolidRoundedButtonView: UIView {
|
|||||||
buttonBackgroundAnimationView.image = generateGradientImage(size: CGSize(width: 200.0, height: height), colors: theme.backgroundColors, locations: locations, direction: .horizontal)
|
buttonBackgroundAnimationView.image = generateGradientImage(size: CGSize(width: 200.0, height: height), colors: theme.backgroundColors, locations: locations, direction: .horizontal)
|
||||||
self.buttonBackgroundNode.addSubview(buttonBackgroundAnimationView)
|
self.buttonBackgroundNode.addSubview(buttonBackgroundAnimationView)
|
||||||
self.buttonBackgroundAnimationView = buttonBackgroundAnimationView
|
self.buttonBackgroundAnimationView = buttonBackgroundAnimationView
|
||||||
} else {
|
|
||||||
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.buttonNode = HighlightTrackingButton()
|
self.buttonNode = HighlightTrackingButton()
|
||||||
@ -675,25 +679,25 @@ public final class SolidRoundedButtonView: UIView {
|
|||||||
|
|
||||||
self.isUserInteractionEnabled = false
|
self.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
||||||
|
rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
||||||
|
rotationAnimation.duration = 1.0
|
||||||
|
rotationAnimation.fromValue = NSNumber(value: Float(0.0))
|
||||||
|
rotationAnimation.toValue = NSNumber(value: Float.pi * 2.0)
|
||||||
|
rotationAnimation.repeatCount = Float.infinity
|
||||||
|
rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||||
|
rotationAnimation.beginTime = 1.0
|
||||||
|
|
||||||
let buttonOffset = self.buttonBackgroundNode.frame.minX
|
let buttonOffset = self.buttonBackgroundNode.frame.minX
|
||||||
let buttonWidth = self.buttonBackgroundNode.frame.width
|
let buttonWidth = self.buttonBackgroundNode.frame.width
|
||||||
|
|
||||||
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(buttonOffset + (buttonWidth - self.buttonHeight) / 2.0), y: 0.0), size: CGSize(width: self.buttonHeight, height: self.buttonHeight))
|
|
||||||
let progressNode = UIImageView()
|
let progressNode = UIImageView()
|
||||||
|
|
||||||
|
switch self.progressType {
|
||||||
|
case .fullSize:
|
||||||
|
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(buttonOffset + (buttonWidth - self.buttonHeight) / 2.0), y: 0.0), size: CGSize(width: self.buttonHeight, height: self.buttonHeight))
|
||||||
progressNode.frame = progressFrame
|
progressNode.frame = progressFrame
|
||||||
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.buttonBackgroundNode.backgroundColor ?? .clear, diameter: self.buttonHeight, lineWidth: 2.0 + UIScreenPixel)
|
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.buttonBackgroundNode.backgroundColor ?? .clear, diameter: self.buttonHeight, lineWidth: 2.0 + UIScreenPixel)
|
||||||
self.insertSubview(progressNode, at: 0)
|
|
||||||
self.progressNode = progressNode
|
|
||||||
|
|
||||||
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
|
||||||
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
|
|
||||||
basicAnimation.duration = 0.5
|
|
||||||
basicAnimation.fromValue = NSNumber(value: Float(0.0))
|
|
||||||
basicAnimation.toValue = NSNumber(value: Float.pi * 2.0)
|
|
||||||
basicAnimation.repeatCount = Float.infinity
|
|
||||||
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
|
||||||
basicAnimation.beginTime = 1.0
|
|
||||||
progressNode.layer.add(basicAnimation, forKey: "progressRotation")
|
|
||||||
|
|
||||||
self.buttonBackgroundNode.layer.cornerRadius = self.buttonHeight / 2.0
|
self.buttonBackgroundNode.layer.cornerRadius = self.buttonHeight / 2.0
|
||||||
self.buttonBackgroundNode.layer.animate(from: self.buttonCornerRadius as NSNumber, to: self.buttonHeight / 2.0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
self.buttonBackgroundNode.layer.animate(from: self.buttonCornerRadius as NSNumber, to: self.buttonHeight / 2.0 as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
||||||
@ -702,13 +706,63 @@ public final class SolidRoundedButtonView: UIView {
|
|||||||
self.buttonBackgroundNode.alpha = 0.0
|
self.buttonBackgroundNode.alpha = 0.0
|
||||||
self.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
self.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
|
||||||
progressNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false)
|
self.insertSubview(progressNode, at: 0)
|
||||||
|
case .embedded:
|
||||||
|
let diameter: CGFloat = self.buttonHeight - 22.0
|
||||||
|
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(buttonOffset + (buttonWidth - diameter) / 2.0), y: floorToScreenPixels((self.buttonHeight - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter))
|
||||||
|
progressNode.frame = progressFrame
|
||||||
|
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.theme.foregroundColor, diameter: diameter, lineWidth: 3.0)
|
||||||
|
|
||||||
|
self.addSubview(progressNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
progressNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
progressNode.layer.add(rotationAnimation, forKey: "progressRotation")
|
||||||
|
self.progressNode = progressNode
|
||||||
|
|
||||||
self.titleNode.alpha = 0.0
|
self.titleNode.alpha = 0.0
|
||||||
self.titleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
|
self.titleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
|
||||||
|
|
||||||
self.subtitleNode.alpha = 0.0
|
self.subtitleNode.alpha = 0.0
|
||||||
self.subtitleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
|
self.subtitleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
|
||||||
|
|
||||||
|
self.shimmerView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
self.borderShimmerView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func transitionFromProgress() {
|
||||||
|
guard let progressNode = self.progressNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.progressNode = nil
|
||||||
|
|
||||||
|
switch self.progressType {
|
||||||
|
case .fullSize:
|
||||||
|
self.buttonBackgroundNode.layer.cornerRadius = self.buttonCornerRadius
|
||||||
|
self.buttonBackgroundNode.layer.animate(from: self.buttonHeight / 2.0 as NSNumber, to: self.buttonCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
||||||
|
self.buttonBackgroundNode.layer.animateFrame(from: progressNode.frame, to: self.bounds, duration: 0.2)
|
||||||
|
|
||||||
|
self.buttonBackgroundNode.alpha = 1.0
|
||||||
|
self.buttonBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
case .embedded:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
progressNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak progressNode, weak self] _ in
|
||||||
|
progressNode?.removeFromSuperview()
|
||||||
|
self?.isUserInteractionEnabled = true
|
||||||
|
})
|
||||||
|
|
||||||
|
self.titleNode.alpha = 1.0
|
||||||
|
self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
|
||||||
|
self.subtitleNode.alpha = 1.0
|
||||||
|
self.subtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
|
||||||
|
self.shimmerView?.layer.removeAllAnimations()
|
||||||
|
self.shimmerView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
self.borderShimmerView?.layer.removeAllAnimations()
|
||||||
|
self.borderShimmerView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateShimmerParameters() {
|
func updateShimmerParameters() {
|
||||||
@ -744,9 +798,8 @@ public final class SolidRoundedButtonView: UIView {
|
|||||||
}
|
}
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
|
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
|
||||||
if theme.backgroundColors.count > 1 {
|
if theme.backgroundColors.count > 1 {
|
||||||
self.buttonBackgroundNode.backgroundColor = nil
|
|
||||||
|
|
||||||
var locations: [CGFloat] = []
|
var locations: [CGFloat] = []
|
||||||
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
|
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
|
||||||
for i in 0 ..< theme.backgroundColors.count {
|
for i in 0 ..< theme.backgroundColors.count {
|
||||||
@ -754,7 +807,6 @@ public final class SolidRoundedButtonView: UIView {
|
|||||||
}
|
}
|
||||||
self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal)
|
self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal)
|
||||||
} else {
|
} else {
|
||||||
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
|
|
||||||
self.buttonBackgroundNode.image = nil
|
self.buttonBackgroundNode.image = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
|||||||
var centerOffset: CGFloat = 0.0
|
var centerOffset: CGFloat = 0.0
|
||||||
if self.item.file.isPremiumSticker {
|
if self.item.file.isPremiumSticker {
|
||||||
let originalImageFrame = imageFrame
|
let originalImageFrame = imageFrame
|
||||||
imageFrame.origin.x = size.width - imageFrame.width
|
imageFrame.origin.x = size.width - imageFrame.width - 18.0
|
||||||
centerOffset = imageFrame.minX - originalImageFrame.minX
|
centerOffset = imageFrame.minX - originalImageFrame.minX
|
||||||
}
|
}
|
||||||
self.imageNode.frame = imageFrame
|
self.imageNode.frame = imageFrame
|
||||||
@ -197,7 +197,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
|||||||
animationNode.updateLayout(size: imageSize)
|
animationNode.updateLayout(size: imageSize)
|
||||||
|
|
||||||
if let additionalAnimationNode = self.additionalAnimationNode {
|
if let additionalAnimationNode = self.additionalAnimationNode {
|
||||||
additionalAnimationNode.frame = imageFrame.offsetBy(dx: -imageFrame.width * 0.25, dy: 0.0).insetBy(dx: -imageFrame.width * 0.25, dy: -imageFrame.height * 0.25)
|
additionalAnimationNode.frame = imageFrame.offsetBy(dx: -imageFrame.width * 0.245 + 21, dy: -1.0).insetBy(dx: -imageFrame.width * 0.245, dy: -imageFrame.height * 0.245)
|
||||||
additionalAnimationNode.updateLayout(size: additionalAnimationNode.frame.size)
|
additionalAnimationNode.updateLayout(size: additionalAnimationNode.frame.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import MtProtoKit
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramApi
|
import TelegramApi
|
||||||
|
|
||||||
|
|
||||||
public enum AssignAppStoreTransactionError {
|
public enum AssignAppStoreTransactionError {
|
||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
@ -17,6 +16,17 @@ func _internal_assignAppStoreTransaction(account: Account, transactionId: String
|
|||||||
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
|
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
|
||||||
account.stateManager.addUpdates(updates)
|
account.stateManager.addUpdates(updates)
|
||||||
|
|
||||||
|
return account.postbox.peerView(id: account.peerId)
|
||||||
|
|> castError(AssignAppStoreTransactionError.self)
|
||||||
|
|> take(until: { view in
|
||||||
|
if let peer = view.peers[view.peerId], peer.isPremium {
|
||||||
|
return SignalTakeAction(passthrough: false, complete: true)
|
||||||
|
} else {
|
||||||
|
return SignalTakeAction(passthrough: false, complete: false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> mapToSignal { _ -> Signal<Never, AssignAppStoreTransactionError> in
|
||||||
return .never()
|
return .never()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,11 +238,11 @@ public struct PresentationResourcesChatList {
|
|||||||
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
|
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
|
||||||
|
|
||||||
let colorsArray: [CGColor] = [
|
let colorsArray: [CGColor] = [
|
||||||
UIColor(rgb: 0x6B93FF).cgColor,
|
UIColor(rgb: 0x1d95fa).cgColor,
|
||||||
UIColor(rgb: 0x6B93FF).cgColor,
|
UIColor(rgb: 0x1d95fa).cgColor,
|
||||||
UIColor(rgb: 0x976FFF).cgColor,
|
UIColor(rgb: 0x7c8cfe).cgColor,
|
||||||
UIColor(rgb: 0xE46ACE).cgColor,
|
UIColor(rgb: 0xcb87f7).cgColor,
|
||||||
UIColor(rgb: 0xE46ACE).cgColor
|
UIColor(rgb: 0xcb87f7).cgColor
|
||||||
]
|
]
|
||||||
var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0]
|
var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0]
|
||||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
|
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
|
||||||
|
@ -9374,6 +9374,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
override public func viewWillDisappear(_ animated: Bool) {
|
override public func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
self.view.endEditing(true)
|
||||||
|
}
|
||||||
|
|
||||||
self.chatDisplayNode.historyNode.canReadHistory.set(.single(false))
|
self.chatDisplayNode.historyNode.canReadHistory.set(.single(false))
|
||||||
self.saveInterfaceState()
|
self.saveInterfaceState()
|
||||||
|
|
||||||
|
@ -721,7 +721,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||||
let displaySize = CGSize(width: 180.0, height: 180.0)
|
var displaySize = CGSize(width: 180.0, height: 180.0)
|
||||||
let telegramFile = self.telegramFile
|
let telegramFile = self.telegramFile
|
||||||
let emojiFile = self.emojiFile
|
let emojiFile = self.emojiFile
|
||||||
let telegramDice = self.telegramDice
|
let telegramDice = self.telegramDice
|
||||||
@ -742,7 +742,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
||||||
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||||
let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData)
|
let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData)
|
||||||
|
|
||||||
var imageSize: CGSize = CGSize(width: 200.0, height: 200.0)
|
var imageSize: CGSize = CGSize(width: 200.0, height: 200.0)
|
||||||
|
var imageVerticalInset: CGFloat = 0.0
|
||||||
|
var imageHorizontalOffset: CGFloat = 0.0
|
||||||
|
if !(telegramFile?.videoThumbnails.isEmpty ?? true) {
|
||||||
|
displaySize = CGSize(width: 240.0, height: 240.0)
|
||||||
|
imageVerticalInset = -30.0
|
||||||
|
imageHorizontalOffset = 12.0
|
||||||
|
}
|
||||||
|
|
||||||
var isEmoji = false
|
var isEmoji = false
|
||||||
if let _ = telegramDice {
|
if let _ = telegramDice {
|
||||||
imageSize = displaySize
|
imageSize = displaySize
|
||||||
@ -879,7 +888,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
let imageInset: CGFloat = 10.0
|
let imageInset: CGFloat = 10.0
|
||||||
var innerImageSize = imageSize
|
var innerImageSize = imageSize
|
||||||
imageSize = CGSize(width: imageSize.width + imageInset * 2.0, height: imageSize.height + imageInset * 2.0)
|
imageSize = CGSize(width: imageSize.width + imageInset * 2.0, height: imageSize.height + imageInset * 2.0)
|
||||||
let imageFrame = CGRect(origin: CGPoint(x: 0.0 + (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - imageSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: CGSize(width: imageSize.width, height: imageSize.height))
|
let imageFrame = CGRect(origin: CGPoint(x: 0.0 + (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - imageSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset - imageHorizontalOffset)), y: imageVerticalInset), size: CGSize(width: imageSize.width, height: imageSize.height))
|
||||||
if isEmoji {
|
if isEmoji {
|
||||||
innerImageSize = imageSize
|
innerImageSize = imageSize
|
||||||
}
|
}
|
||||||
@ -1017,7 +1026,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentHeight = max(imageSize.height, layoutConstants.image.minDimensions.height)
|
let contentHeight = max(imageSize.height + imageVerticalInset * 2.0, layoutConstants.image.minDimensions.height)
|
||||||
|
|
||||||
var forwardSource: Peer?
|
var forwardSource: Peer?
|
||||||
var forwardAuthorSignature: String?
|
var forwardAuthorSignature: String?
|
||||||
@ -1632,7 +1641,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
additionalAnimationNode.setup(source: source, width: Int(animationSize.width * 2), height: Int(animationSize.height * 2), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix))
|
additionalAnimationNode.setup(source: source, width: Int(animationSize.width * 2), height: Int(animationSize.height * 2), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix))
|
||||||
var animationFrame: CGRect
|
var animationFrame: CGRect
|
||||||
if isStickerEffect {
|
if isStickerEffect {
|
||||||
animationFrame = animationNode.frame.offsetBy(dx: incomingMessage ? animationNode.frame.width * 0.5 : -animationNode.frame.width * 0.5, dy: 35.0).insetBy(dx: -animationNode.frame.width * 0.5, dy: -animationNode.frame.height * 0.5)
|
let scale: CGFloat = 0.245
|
||||||
|
animationFrame = animationNode.frame.offsetBy(dx: incomingMessage ? animationNode.frame.width * scale : -animationNode.frame.width * scale + 21.0, dy: -1.0).insetBy(dx: -animationNode.frame.width * scale, dy: -animationNode.frame.height * scale)
|
||||||
if incomingMessage {
|
if incomingMessage {
|
||||||
animationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
animationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||||
}
|
}
|
||||||
|
@ -6132,6 +6132,26 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func openSettings(section: PeerInfoSettingsSection) {
|
fileprivate func openSettings(section: PeerInfoSettingsSection) {
|
||||||
|
let push: (ViewController) -> Void = { [weak self] c in
|
||||||
|
guard let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var updatedControllers = navigationController.viewControllers
|
||||||
|
for controller in navigationController.viewControllers.reversed() {
|
||||||
|
if controller !== strongSelf && !(controller is TabBarController) {
|
||||||
|
updatedControllers.removeLast()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updatedControllers.append(c)
|
||||||
|
|
||||||
|
var animated = true
|
||||||
|
if let validLayout = strongSelf.validLayout?.0, case .regular = validLayout.metrics.widthClass {
|
||||||
|
animated = false
|
||||||
|
}
|
||||||
|
navigationController.setViewControllers(updatedControllers, animated: animated)
|
||||||
|
}
|
||||||
switch section {
|
switch section {
|
||||||
case .avatar:
|
case .avatar:
|
||||||
self.openAvatarForEditing()
|
self.openAvatarForEditing()
|
||||||
@ -6144,21 +6164,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: self.context.account.peerId)))
|
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: self.context.account.peerId)))
|
||||||
}
|
}
|
||||||
case .recentCalls:
|
case .recentCalls:
|
||||||
self.controller?.push(CallListController(context: context, mode: .navigation))
|
push(CallListController(context: context, mode: .navigation))
|
||||||
case .devices:
|
case .devices:
|
||||||
let _ = (self.activeSessionsContextAndCount.get()
|
let _ = (self.activeSessionsContextAndCount.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] activeSessionsContextAndCount in
|
|> deliverOnMainQueue).start(next: { [weak self] activeSessionsContextAndCount in
|
||||||
if let strongSelf = self, let activeSessionsContextAndCount = activeSessionsContextAndCount {
|
if let strongSelf = self, let activeSessionsContextAndCount = activeSessionsContextAndCount {
|
||||||
let (activeSessionsContext, _, webSessionsContext) = activeSessionsContextAndCount
|
let (activeSessionsContext, _, webSessionsContext) = activeSessionsContextAndCount
|
||||||
strongSelf.controller?.push(recentSessionsController(context: strongSelf.context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false))
|
push(recentSessionsController(context: strongSelf.context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
case .chatFolders:
|
case .chatFolders:
|
||||||
self.controller?.push(chatListFilterPresetListController(context: self.context, mode: .default))
|
push(chatListFilterPresetListController(context: self.context, mode: .default))
|
||||||
case .notificationsAndSounds:
|
case .notificationsAndSounds:
|
||||||
if let settings = self.data?.globalSettings {
|
if let settings = self.data?.globalSettings {
|
||||||
self.controller?.push(notificationsAndSoundsController(context: self.context, exceptionsList: settings.notificationExceptions))
|
push(notificationsAndSoundsController(context: self.context, exceptionsList: settings.notificationExceptions))
|
||||||
}
|
}
|
||||||
case .privacyAndSecurity:
|
case .privacyAndSecurity:
|
||||||
if let settings = self.data?.globalSettings {
|
if let settings = self.data?.globalSettings {
|
||||||
@ -6166,7 +6186,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] blockedPeersContext, hasTwoStepAuth in
|
|> deliverOnMainQueue).start(next: { [weak self] blockedPeersContext, hasTwoStepAuth in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.controller?.push(privacyAndSecurityController(context: strongSelf.context, initialSettings: settings.privacySettings, updatedSettings: { [weak self] settings in
|
push(privacyAndSecurityController(context: strongSelf.context, initialSettings: settings.privacySettings, updatedSettings: { [weak self] settings in
|
||||||
self?.privacySettings.set(.single(settings))
|
self?.privacySettings.set(.single(settings))
|
||||||
}, updatedBlockedPeers: { [weak self] blockedPeersContext in
|
}, updatedBlockedPeers: { [weak self] blockedPeersContext in
|
||||||
self?.blockedPeers.set(.single(blockedPeersContext))
|
self?.blockedPeers.set(.single(blockedPeersContext))
|
||||||
@ -6177,23 +6197,23 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
case .dataAndStorage:
|
case .dataAndStorage:
|
||||||
self.controller?.push(dataAndStorageController(context: self.context))
|
push(dataAndStorageController(context: self.context))
|
||||||
case .appearance:
|
case .appearance:
|
||||||
self.controller?.push(themeSettingsController(context: self.context))
|
push(themeSettingsController(context: self.context))
|
||||||
case .language:
|
case .language:
|
||||||
self.controller?.push(LocalizationListController(context: self.context))
|
push(LocalizationListController(context: self.context))
|
||||||
case .premium:
|
case .premium:
|
||||||
self.controller?.push(PremiumIntroScreen(context: self.context))
|
self.controller?.push(PremiumIntroScreen(context: self.context, modal: false))
|
||||||
case .stickers:
|
case .stickers:
|
||||||
if let settings = self.data?.globalSettings {
|
if let settings = self.data?.globalSettings {
|
||||||
self.controller?.push(installedStickerPacksController(context: self.context, mode: .general, archivedPacks: settings.archivedStickerPacks, updatedPacks: { [weak self] packs in
|
push(installedStickerPacksController(context: self.context, mode: .general, archivedPacks: settings.archivedStickerPacks, updatedPacks: { [weak self] packs in
|
||||||
self?.archivedPacks.set(.single(packs))
|
self?.archivedPacks.set(.single(packs))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
case .passport:
|
case .passport:
|
||||||
self.controller?.push(SecureIdAuthController(context: self.context, mode: .list))
|
self.controller?.push(SecureIdAuthController(context: self.context, mode: .list))
|
||||||
case .watch:
|
case .watch:
|
||||||
self.controller?.push(watchSettingsController(context: self.context))
|
push(watchSettingsController(context: self.context))
|
||||||
case .support:
|
case .support:
|
||||||
let supportPeer = Promise<PeerId?>()
|
let supportPeer = Promise<PeerId?>()
|
||||||
supportPeer.set(context.engine.peers.supportPeerId())
|
supportPeer.set(context.engine.peers.supportPeerId())
|
||||||
@ -6204,7 +6224,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { [weak self] in
|
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { [weak self] in
|
||||||
self?.supportPeerDisposable.set((supportPeer.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peerId in
|
self?.supportPeerDisposable.set((supportPeer.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peerId in
|
||||||
if let strongSelf = self, let peerId = peerId {
|
if let strongSelf = self, let peerId = peerId {
|
||||||
strongSelf.controller?.push(strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)))
|
push(strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)))
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
})]), in: .window(.root))
|
})]), in: .window(.root))
|
||||||
@ -6219,10 +6239,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
navigationController.replaceTopController(ChangePhoneNumberController(context: strongSelf.context), animated: true)
|
navigationController.replaceTopController(ChangePhoneNumberController(context: strongSelf.context), animated: true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self.controller?.push(introController)
|
push(introController)
|
||||||
}
|
}
|
||||||
case .username:
|
case .username:
|
||||||
self.controller?.push(usernameSetupController(context: self.context))
|
push(usernameSetupController(context: self.context))
|
||||||
case .addAccount:
|
case .addAccount:
|
||||||
self.context.sharedContext.beginNewAuth(testingEnvironment: self.context.account.testingEnvironment)
|
self.context.sharedContext.beginNewAuth(testingEnvironment: self.context.account.testingEnvironment)
|
||||||
case .logout:
|
case .logout:
|
||||||
@ -6241,7 +6261,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
controller.passwordRemembered = {
|
controller.passwordRemembered = {
|
||||||
let _ = dismissServerProvidedSuggestion(account: context.account, suggestion: .validatePassword).start()
|
let _ = dismissServerProvidedSuggestion(account: context.account, suggestion: .validatePassword).start()
|
||||||
}
|
}
|
||||||
self.controller?.push(controller)
|
push(controller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,6 +437,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let eventData = (body["eventData"] as? String)?.data(using: .utf8)
|
||||||
|
let json = try? JSONSerialization.jsonObject(with: eventData ?? Data(), options: []) as? [String: Any]
|
||||||
|
|
||||||
switch eventName {
|
switch eventName {
|
||||||
case "web_app_ready":
|
case "web_app_ready":
|
||||||
self.animateTransitionIn()
|
self.animateTransitionIn()
|
||||||
@ -447,7 +450,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
case "web_app_setup_main_button":
|
case "web_app_setup_main_button":
|
||||||
if let webView = self.webView, !webView.didTouchOnce && controller.url == nil {
|
if let webView = self.webView, !webView.didTouchOnce && controller.url == nil {
|
||||||
self.delayedScriptMessage = message
|
self.delayedScriptMessage = message
|
||||||
} else if let eventData = (body["eventData"] as? String)?.data(using: .utf8), let json = try? JSONSerialization.jsonObject(with: eventData, options: []) as? [String: Any] {
|
} else if let json = json {
|
||||||
if var isVisible = json["is_visible"] as? Bool {
|
if var isVisible = json["is_visible"] as? Bool {
|
||||||
let text = json["text"] as? String
|
let text = json["text"] as? String
|
||||||
if (text ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
if (text ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
@ -475,6 +478,14 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
self.controller?.requestAttachmentMenuExpansion()
|
self.controller?.requestAttachmentMenuExpansion()
|
||||||
case "web_app_close":
|
case "web_app_close":
|
||||||
self.controller?.dismiss()
|
self.controller?.dismiss()
|
||||||
|
case "web_app_open_tg_link":
|
||||||
|
if let json = json, let path = json["path_full"] as? String {
|
||||||
|
print(path)
|
||||||
|
}
|
||||||
|
case "web_app_open_invoice":
|
||||||
|
if let json = json, let slug = json["slug"] as? String {
|
||||||
|
print(slug)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -527,6 +538,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
themeParamsString.append("}}")
|
themeParamsString.append("}}")
|
||||||
self.webView?.sendEvent(name: "theme_changed", data: themeParamsString)
|
self.webView?.sendEvent(name: "theme_changed", data: themeParamsString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func sendInvoiceClosedEvent(slug: String, status: String) {
|
||||||
|
let paramsString = "{slug: \"\(slug)\", status: \"\(status)\"}"
|
||||||
|
self.webView?.sendEvent(name: "invoice_closed", data: paramsString)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var controllerNode: Node {
|
fileprivate var controllerNode: Node {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user