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.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.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 iconName: String?
|
||||
public let iconPosition: SolidRoundedButtonIconPosition
|
||||
public let isLoading: Bool
|
||||
public let action: () -> Void
|
||||
|
||||
public init(
|
||||
@ -31,6 +32,7 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
gloss: Bool = false,
|
||||
iconName: String? = nil,
|
||||
iconPosition: SolidRoundedButtonIconPosition = .left,
|
||||
isLoading: Bool = false,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.title = title
|
||||
@ -43,6 +45,7 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
self.gloss = gloss
|
||||
self.iconName = iconName
|
||||
self.iconPosition = iconPosition
|
||||
self.isLoading = isLoading
|
||||
self.action = action
|
||||
}
|
||||
|
||||
@ -77,7 +80,9 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
if lhs.iconPosition != rhs.iconPosition {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.isLoading != rhs.isLoading {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -85,6 +90,8 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
private var component: SolidRoundedButtonComponent?
|
||||
private var button: SolidRoundedButtonView?
|
||||
|
||||
private var currentIsLoading = false
|
||||
|
||||
public func update(component: SolidRoundedButtonComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||
if self.button == nil {
|
||||
let button = SolidRoundedButtonView(
|
||||
@ -98,6 +105,7 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
gloss: component.gloss
|
||||
)
|
||||
button.iconPosition = component.iconPosition
|
||||
button.progressType = .embedded
|
||||
button.icon = component.iconName.flatMap { UIImage(bundleImageName: $0) }
|
||||
self.button = button
|
||||
self.addSubview(button)
|
||||
@ -111,6 +119,15 @@ public final class SolidRoundedButtonComponent: Component {
|
||||
button.updateTheme(component.theme)
|
||||
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)
|
||||
|
||||
if self.currentIsLoading != component.isLoading {
|
||||
self.currentIsLoading = component.isLoading
|
||||
if component.isLoading {
|
||||
button.transitionToProgress()
|
||||
} else {
|
||||
button.transitionFromProgress()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
@ -334,6 +334,7 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega
|
||||
|
||||
var statusBarTransition = transition
|
||||
|
||||
var ignoreInputHeight = false
|
||||
if let pending = self.state.pending {
|
||||
if pending.isReady {
|
||||
self.state.pending = nil
|
||||
@ -344,6 +345,9 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega
|
||||
if pending.value.value.view.disableAutomaticKeyboardHandling.isEmpty {
|
||||
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.state.top?.value.isInFocus = self.isInFocus
|
||||
statusBarTransition = pending.transition
|
||||
@ -369,6 +373,9 @@ public final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelega
|
||||
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
|
||||
}
|
||||
}
|
||||
if ignoreInputHeight {
|
||||
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
|
||||
}
|
||||
self.applyLayout(layout: updatedLayout, to: top, isMaster: true, transition: transition)
|
||||
if let childTransition = self.state.transition, childTransition.coordinator.isInteractive {
|
||||
switch childTransition.type {
|
||||
|
@ -5,15 +5,6 @@ import StoreKit
|
||||
import Postbox
|
||||
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 Product {
|
||||
let skProduct: SKProduct
|
||||
@ -30,14 +21,31 @@ public final class InAppPurchaseManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
public enum PurchaseResult {
|
||||
case success
|
||||
public enum PurchaseState {
|
||||
case purchased(transactionId: String)
|
||||
}
|
||||
|
||||
public enum PurchaseError {
|
||||
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 var products: [Product] = []
|
||||
@ -57,7 +65,7 @@ public final class InAppPurchaseManager: NSObject {
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
SKPaymentQueue.default().remove(self)
|
||||
}
|
||||
|
||||
private func requestProducts() {
|
||||
@ -79,27 +87,28 @@ public final class InAppPurchaseManager: NSObject {
|
||||
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)
|
||||
payment.applicationUsername = "\(account.peerId.id._internalGetInt64Value())"
|
||||
SKPaymentQueue.default().add(payment)
|
||||
|
||||
let productIdentifier = payment.productIdentifier
|
||||
let signal = Signal<PurchaseResult, PurchaseError> { subscriber in
|
||||
let signal = Signal<PurchaseState, PurchaseError> { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.stateQueue.async {
|
||||
let paymentContext = PaymentTransactionContext(subscriber: { state in
|
||||
switch state {
|
||||
case .purchased, .restored:
|
||||
subscriber.putNext(.success)
|
||||
subscriber.putCompletion()
|
||||
case let .purchased(transactionId), let .restored(transactionId):
|
||||
if let transactionId = transactionId {
|
||||
subscriber.putNext(.purchased(transactionId: transactionId))
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putError(.generic)
|
||||
}
|
||||
case .failed:
|
||||
subscriber.putError(.generic)
|
||||
case .deferred, .purchasing:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
self.paymentContexts[productIdentifier] = paymentContext
|
||||
@ -135,7 +144,24 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
|
||||
let productIdentifier = transaction.payment.productIdentifier
|
||||
self.stateQueue.async {
|
||||
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 var previousInteractionTimestamp: Double = 0.0
|
||||
private var timer: SwiftSignalKit.Timer?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: frame)
|
||||
self.sceneView.backgroundColor = .clear
|
||||
@ -79,11 +82,17 @@ private class StarComponent: Component {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.timer?.invalidate()
|
||||
}
|
||||
|
||||
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
|
||||
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
|
||||
return
|
||||
}
|
||||
|
||||
self.previousInteractionTimestamp = CACurrentMediaTime()
|
||||
|
||||
var left = true
|
||||
if let view = gesture.view {
|
||||
let point = gesture.location(in: view)
|
||||
@ -133,6 +142,8 @@ private class StarComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
self.previousInteractionTimestamp = CACurrentMediaTime()
|
||||
|
||||
if #available(iOS 11.0, *) {
|
||||
node.removeAnimation(forKey: "rotate", blendOutDuration: 0.1)
|
||||
node.removeAnimation(forKey: "tapRotate", blendOutDuration: 0.1)
|
||||
@ -189,6 +200,17 @@ private class StarComponent: Component {
|
||||
self.setupShineAnimation()
|
||||
|
||||
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() {
|
||||
@ -240,6 +262,8 @@ private class StarComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
self.previousInteractionTimestamp = CACurrentMediaTime()
|
||||
|
||||
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
|
||||
particleSystem?.particleColorVariation = SCNVector4(0.15, 0.2, 0.35, 0.3)
|
||||
@ -251,7 +275,7 @@ private class StarComponent: Component {
|
||||
Queue.mainQueue().after(1.0) {
|
||||
node.physicsField?.isActive = false
|
||||
particles.particleSystems?.first?.birthRate = 1.2
|
||||
particleSystem?.particleVelocity = 1.65
|
||||
particleSystem?.particleVelocity = 1.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 distance = rad2deg(to.w - from.w)
|
||||
|
||||
guard !distance.isZero else {
|
||||
return
|
||||
}
|
||||
|
||||
let springAnimation = CASpringAnimation(keyPath: "rotation")
|
||||
springAnimation.fromValue = NSValue(scnVector4: from)
|
||||
springAnimation.toValue = NSValue(scnVector4: to)
|
||||
@ -302,9 +330,11 @@ private class StarComponent: Component {
|
||||
private final class SectionGroupComponent: Component {
|
||||
public final class Item: Equatable {
|
||||
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.action = action
|
||||
}
|
||||
|
||||
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||
@ -318,15 +348,18 @@ private final class SectionGroupComponent: Component {
|
||||
|
||||
public let items: [Item]
|
||||
public let backgroundColor: UIColor
|
||||
public let selectionColor: UIColor
|
||||
public let separatorColor: UIColor
|
||||
|
||||
public init(
|
||||
items: [Item],
|
||||
backgroundColor: UIColor,
|
||||
selectionColor: UIColor,
|
||||
separatorColor: UIColor
|
||||
) {
|
||||
self.items = items
|
||||
self.backgroundColor = backgroundColor
|
||||
self.selectionColor = selectionColor
|
||||
self.separatorColor = separatorColor
|
||||
}
|
||||
|
||||
@ -337,6 +370,9 @@ private final class SectionGroupComponent: Component {
|
||||
if lhs.backgroundColor != rhs.backgroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.selectionColor != rhs.selectionColor {
|
||||
return false
|
||||
}
|
||||
if lhs.separatorColor != rhs.separatorColor {
|
||||
return false
|
||||
}
|
||||
@ -344,28 +380,34 @@ private final class SectionGroupComponent: Component {
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let backgroundView: UIView
|
||||
private var buttonViews: [AnyHashable: HighlightTrackingButton] = [:]
|
||||
private var itemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
|
||||
private var separatorViews: [UIView] = []
|
||||
|
||||
private var component: SectionGroupComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.backgroundView = UIView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.backgroundView.layer.cornerRadius = 10.0
|
||||
self.backgroundView.layer.masksToBounds = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
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 {
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
self.backgroundView.backgroundColor = component.backgroundColor
|
||||
self.backgroundColor = component.backgroundColor
|
||||
|
||||
var size = CGSize(width: availableSize.width, height: 0.0)
|
||||
|
||||
@ -375,8 +417,19 @@ private final class SectionGroupComponent: Component {
|
||||
for item in component.items {
|
||||
validIds.append(item.content.id)
|
||||
|
||||
let buttonView: HighlightTrackingButton
|
||||
let itemView: ComponentHostView<Empty>
|
||||
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] {
|
||||
itemView = current
|
||||
} else {
|
||||
@ -393,7 +446,19 @@ private final class SectionGroupComponent: Component {
|
||||
)
|
||||
|
||||
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.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
|
||||
|
||||
@ -431,7 +496,7 @@ private final class SectionGroupComponent: Component {
|
||||
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
|
||||
}
|
||||
@ -508,10 +573,15 @@ private final class ScrollComponent<ChildEnvironment: Equatable>: Component {
|
||||
}
|
||||
self.delegate = self
|
||||
self.showsVerticalScrollIndicator = false
|
||||
self.canCancelContentTouches = true
|
||||
|
||||
self.addSubview(self.contentView)
|
||||
}
|
||||
|
||||
public override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
private var ignoreDidScroll = false
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard let component = self.component, !self.ignoreDidScroll else {
|
||||
@ -740,6 +810,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
let fade = Child(RoundedRectangle.self)
|
||||
let text = Child(MultilineTextComponent.self)
|
||||
let section = Child(SectionGroupComponent.self)
|
||||
let infoBackground = Child(RoundedRectangle.self)
|
||||
let infoTitle = Child(MultilineTextComponent.self)
|
||||
let infoText = Child(MultilineTextComponent.self)
|
||||
|
||||
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))
|
||||
)
|
||||
|
||||
size.height += 183.0 + 10.0
|
||||
size.height += 183.0 + 10.0 + environment.navigationHeight - 56.0
|
||||
|
||||
let textColor = theme.list.itemPrimaryTextColor
|
||||
let titleColor = theme.list.itemPrimaryTextColor
|
||||
@ -833,7 +905,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
arrowColor: arrowColor
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
|
||||
}
|
||||
),
|
||||
SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
@ -852,7 +927,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
arrowColor: arrowColor
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
|
||||
}
|
||||
),
|
||||
SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
@ -871,7 +949,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
arrowColor: arrowColor
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
|
||||
}
|
||||
),
|
||||
SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
@ -890,7 +971,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
arrowColor: arrowColor
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
|
||||
}
|
||||
),
|
||||
SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
@ -909,7 +993,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
arrowColor: arrowColor
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
|
||||
}
|
||||
),
|
||||
SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
@ -928,7 +1015,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
arrowColor: arrowColor
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
|
||||
}
|
||||
),
|
||||
SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
@ -947,7 +1037,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
arrowColor: arrowColor
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
|
||||
}
|
||||
),
|
||||
SectionGroupComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
@ -966,10 +1059,14 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
arrowColor: arrowColor
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
|
||||
}
|
||||
),
|
||||
],
|
||||
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
|
||||
selectionColor: environment.theme.list.itemHighlightedBackgroundColor,
|
||||
separatorColor: environment.theme.list.itemBlocksSeparatorColor
|
||||
),
|
||||
environment: {},
|
||||
@ -978,31 +1075,65 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
)
|
||||
context.add(section
|
||||
.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 += 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(
|
||||
text: .plain(
|
||||
NSAttributedString(
|
||||
string: strings.Premium_HelpUs,
|
||||
font: Font.regular(15.0),
|
||||
textColor: environment.theme.list.freeTextColor
|
||||
)
|
||||
NSAttributedString(string: strings.Premium_AboutTitle.uppercased(), font: Font.regular(14.0), textColor: environment.theme.list.freeTextColor)
|
||||
),
|
||||
horizontalAlignment: .center,
|
||||
horizontalAlignment: .natural,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: availableWidth - sideInsets, height: 120.0),
|
||||
availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(infoText
|
||||
.position(CGPoint(x: size.width / 2.0, y: size.height + infoText.size.height / 2.0))
|
||||
context.add(infoTitle
|
||||
.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 += scrollEnvironment.insets.bottom
|
||||
|
||||
@ -1089,7 +1220,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
var inProgress = false
|
||||
var premiumProduct: InAppPurchaseManager.Product?
|
||||
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) {
|
||||
self.context = context
|
||||
@ -1111,7 +1243,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
self.actionDisposable.dispose()
|
||||
self.paymentDisposable.dispose()
|
||||
self.activationDisposable.dispose()
|
||||
}
|
||||
|
||||
func buy() {
|
||||
@ -1124,10 +1257,17 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
self.updateInProgress(true)
|
||||
self.updated(transition: .immediate)
|
||||
|
||||
self.actionDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct, account: self.context.account)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.completion()
|
||||
self.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct, account: self.context.account)
|
||||
|> 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 {
|
||||
strongSelf.completion()
|
||||
}
|
||||
}))
|
||||
}
|
||||
}, error: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
@ -1202,7 +1342,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
component: SolidRoundedButtonComponent(
|
||||
title: environment.strings.Premium_SubscribeFor(state.premiumProduct?.price ?? "—").string,
|
||||
theme: SolidRoundedButtonComponent.Theme(
|
||||
backgroundColor: .black,
|
||||
backgroundColor: UIColor(rgb: 0x8878ff),
|
||||
backgroundColors: [
|
||||
UIColor(rgb: 0x0077ff),
|
||||
UIColor(rgb: 0x6b93ff),
|
||||
@ -1214,6 +1354,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
height: 50.0,
|
||||
cornerRadius: 10.0,
|
||||
gloss: true,
|
||||
isLoading: state.inProgress,
|
||||
action: {
|
||||
state.buy()
|
||||
}
|
||||
@ -1263,6 +1404,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let topInset: CGFloat = environment.navigationHeight - 56.0
|
||||
|
||||
context.add(background
|
||||
.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 titleOffset: 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 {
|
||||
topPanelAlpha = min(20.0, max(0.0, topContentOffset - 95.0)) / 20.0
|
||||
@ -1289,7 +1432,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
@ -1303,7 +1446,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
)
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
@ -1340,7 +1483,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
public init(context: AccountContext) {
|
||||
public init(context: AccountContext, modal: Bool = true) {
|
||||
self.context = context
|
||||
|
||||
var updateInProgressImpl: ((Bool) -> Void)?
|
||||
@ -1357,10 +1500,13 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||
self.navigationItem.setLeftBarButton(cancelItem, animated: false)
|
||||
|
||||
self.navigationPresentation = .modal
|
||||
if modal {
|
||||
let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||
self.navigationItem.setLeftBarButton(cancelItem, animated: false)
|
||||
self.navigationPresentation = .modal
|
||||
} else {
|
||||
self.navigationPresentation = .modalInLargeLayout
|
||||
}
|
||||
|
||||
updateInProgressImpl = { [weak self] inProgress in
|
||||
if let strongSelf = self {
|
||||
|
@ -71,6 +71,8 @@ private class PremiumLimitAnimationComponent: Component {
|
||||
private let badgeIcon: UIImageView
|
||||
private let badgeCountLabel: RollingLabel
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.container = SimpleLayer()
|
||||
self.container.masksToBounds = true
|
||||
@ -102,6 +104,8 @@ private class PremiumLimitAnimationComponent: Component {
|
||||
self.badgeMaskView = UIView()
|
||||
self.badgeMaskView.addSubview(self.badgeMaskBackgroundView)
|
||||
self.badgeMaskView.addSubview(self.badgeMaskArrowView)
|
||||
self.badgeMaskView.layer.rasterizationScale = UIScreenScale
|
||||
self.badgeMaskView.layer.shouldRasterize = true
|
||||
self.badgeView.mask = self.badgeMaskView
|
||||
|
||||
self.badgeForeground = SimpleLayer()
|
||||
@ -156,6 +160,10 @@ private class PremiumLimitAnimationComponent: Component {
|
||||
rotateAnimation.fillMode = .forwards
|
||||
rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
||||
|
||||
Queue.mainQueue().after(0.5, {
|
||||
self.hapticFeedback.impact(.light)
|
||||
})
|
||||
|
||||
let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
||||
returnAnimation.fromValue = 0.2 as NSNumber
|
||||
returnAnimation.toValue = 0.0 as NSNumber
|
||||
|
@ -11,7 +11,7 @@ private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter:
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
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.strokePath()
|
||||
})
|
||||
@ -52,6 +52,11 @@ public enum SolidRoundedButtonIconPosition {
|
||||
case right
|
||||
}
|
||||
|
||||
public enum SolidRoundedButtonProgressType {
|
||||
case fullSize
|
||||
case embedded
|
||||
}
|
||||
|
||||
public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
private var theme: SolidRoundedButtonTheme
|
||||
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) {
|
||||
self.theme = theme
|
||||
self.font = font
|
||||
@ -530,9 +537,8 @@ public final class SolidRoundedButtonView: UIView {
|
||||
self.buttonBackgroundNode.clipsToBounds = true
|
||||
self.buttonBackgroundNode.layer.cornerRadius = cornerRadius
|
||||
|
||||
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
|
||||
if theme.backgroundColors.count > 1 {
|
||||
self.buttonBackgroundNode.backgroundColor = nil
|
||||
|
||||
var locations: [CGFloat] = []
|
||||
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
|
||||
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)
|
||||
self.buttonBackgroundNode.addSubview(buttonBackgroundAnimationView)
|
||||
self.buttonBackgroundAnimationView = buttonBackgroundAnimationView
|
||||
} else {
|
||||
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
|
||||
}
|
||||
|
||||
self.buttonNode = HighlightTrackingButton()
|
||||
@ -675,40 +679,90 @@ public final class SolidRoundedButtonView: UIView {
|
||||
|
||||
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 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()
|
||||
progressNode.frame = progressFrame
|
||||
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.buttonBackgroundNode.backgroundColor ?? .clear, diameter: self.buttonHeight, lineWidth: 2.0 + UIScreenPixel)
|
||||
self.insertSubview(progressNode, at: 0)
|
||||
|
||||
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.image = generateIndefiniteActivityIndicatorImage(color: self.buttonBackgroundNode.backgroundColor ?? .clear, diameter: self.buttonHeight, lineWidth: 2.0 + UIScreenPixel)
|
||||
|
||||
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.animateFrame(from: self.buttonBackgroundNode.frame, to: progressFrame, duration: 0.2)
|
||||
|
||||
self.buttonBackgroundNode.alpha = 0.0
|
||||
self.buttonBackgroundNode.layer.animateAlpha(from: 0.55, to: 0.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
|
||||
|
||||
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.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.animateFrame(from: self.buttonBackgroundNode.frame, to: progressFrame, duration: 0.2)
|
||||
|
||||
self.buttonBackgroundNode.alpha = 0.0
|
||||
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.titleNode.alpha = 0.0
|
||||
self.titleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
|
||||
|
||||
self.subtitleNode.alpha = 0.0
|
||||
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() {
|
||||
@ -744,9 +798,8 @@ public final class SolidRoundedButtonView: UIView {
|
||||
}
|
||||
self.theme = theme
|
||||
|
||||
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
|
||||
if theme.backgroundColors.count > 1 {
|
||||
self.buttonBackgroundNode.backgroundColor = nil
|
||||
|
||||
var locations: [CGFloat] = []
|
||||
let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1)
|
||||
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)
|
||||
} else {
|
||||
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
|
||||
self.buttonBackgroundNode.image = nil
|
||||
}
|
||||
|
||||
|
@ -188,7 +188,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
||||
var centerOffset: CGFloat = 0.0
|
||||
if self.item.file.isPremiumSticker {
|
||||
let originalImageFrame = imageFrame
|
||||
imageFrame.origin.x = size.width - imageFrame.width
|
||||
imageFrame.origin.x = size.width - imageFrame.width - 18.0
|
||||
centerOffset = imageFrame.minX - originalImageFrame.minX
|
||||
}
|
||||
self.imageNode.frame = imageFrame
|
||||
@ -197,7 +197,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
|
||||
animationNode.updateLayout(size: imageSize)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import MtProtoKit
|
||||
import SwiftSignalKit
|
||||
import TelegramApi
|
||||
|
||||
|
||||
public enum AssignAppStoreTransactionError {
|
||||
case generic
|
||||
}
|
||||
@ -17,6 +16,17 @@ func _internal_assignAppStoreTransaction(account: Account, transactionId: String
|
||||
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
|
||||
account.stateManager.addUpdates(updates)
|
||||
|
||||
return .never()
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,11 +238,11 @@ public struct PresentationResourcesChatList {
|
||||
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
|
||||
UIColor(rgb: 0x1d95fa).cgColor,
|
||||
UIColor(rgb: 0x1d95fa).cgColor,
|
||||
UIColor(rgb: 0x7c8cfe).cgColor,
|
||||
UIColor(rgb: 0xcb87f7).cgColor,
|
||||
UIColor(rgb: 0xcb87f7).cgColor
|
||||
]
|
||||
var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0]
|
||||
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) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
UIView.performWithoutAnimation {
|
||||
self.view.endEditing(true)
|
||||
}
|
||||
|
||||
self.chatDisplayNode.historyNode.canReadHistory.set(.single(false))
|
||||
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) {
|
||||
let displaySize = CGSize(width: 180.0, height: 180.0)
|
||||
var displaySize = CGSize(width: 180.0, height: 180.0)
|
||||
let telegramFile = self.telegramFile
|
||||
let emojiFile = self.emojiFile
|
||||
let telegramDice = self.telegramDice
|
||||
@ -742,7 +742,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
||||
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||
let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData)
|
||||
|
||||
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
|
||||
if let _ = telegramDice {
|
||||
imageSize = displaySize
|
||||
@ -879,7 +888,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
let imageInset: CGFloat = 10.0
|
||||
var innerImageSize = imageSize
|
||||
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 {
|
||||
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 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))
|
||||
var animationFrame: CGRect
|
||||
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 {
|
||||
animationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
}
|
||||
|
@ -6132,6 +6132,26 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
|
||||
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 {
|
||||
case .avatar:
|
||||
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)))
|
||||
}
|
||||
case .recentCalls:
|
||||
self.controller?.push(CallListController(context: context, mode: .navigation))
|
||||
push(CallListController(context: context, mode: .navigation))
|
||||
case .devices:
|
||||
let _ = (self.activeSessionsContextAndCount.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] activeSessionsContextAndCount in
|
||||
if let strongSelf = self, let activeSessionsContextAndCount = 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:
|
||||
self.controller?.push(chatListFilterPresetListController(context: self.context, mode: .default))
|
||||
push(chatListFilterPresetListController(context: self.context, mode: .default))
|
||||
case .notificationsAndSounds:
|
||||
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:
|
||||
if let settings = self.data?.globalSettings {
|
||||
@ -6166,7 +6186,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] blockedPeersContext, hasTwoStepAuth in
|
||||
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))
|
||||
}, updatedBlockedPeers: { [weak self] blockedPeersContext in
|
||||
self?.blockedPeers.set(.single(blockedPeersContext))
|
||||
@ -6177,23 +6197,23 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
})
|
||||
}
|
||||
case .dataAndStorage:
|
||||
self.controller?.push(dataAndStorageController(context: self.context))
|
||||
push(dataAndStorageController(context: self.context))
|
||||
case .appearance:
|
||||
self.controller?.push(themeSettingsController(context: self.context))
|
||||
push(themeSettingsController(context: self.context))
|
||||
case .language:
|
||||
self.controller?.push(LocalizationListController(context: self.context))
|
||||
push(LocalizationListController(context: self.context))
|
||||
case .premium:
|
||||
self.controller?.push(PremiumIntroScreen(context: self.context))
|
||||
self.controller?.push(PremiumIntroScreen(context: self.context, modal: false))
|
||||
case .stickers:
|
||||
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))
|
||||
}))
|
||||
}
|
||||
case .passport:
|
||||
self.controller?.push(SecureIdAuthController(context: self.context, mode: .list))
|
||||
case .watch:
|
||||
self.controller?.push(watchSettingsController(context: self.context))
|
||||
push(watchSettingsController(context: self.context))
|
||||
case .support:
|
||||
let supportPeer = Promise<PeerId?>()
|
||||
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
|
||||
self?.supportPeerDisposable.set((supportPeer.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peerId in
|
||||
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))
|
||||
@ -6219,10 +6239,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
navigationController.replaceTopController(ChangePhoneNumberController(context: strongSelf.context), animated: true)
|
||||
}
|
||||
})
|
||||
self.controller?.push(introController)
|
||||
push(introController)
|
||||
}
|
||||
case .username:
|
||||
self.controller?.push(usernameSetupController(context: self.context))
|
||||
push(usernameSetupController(context: self.context))
|
||||
case .addAccount:
|
||||
self.context.sharedContext.beginNewAuth(testingEnvironment: self.context.account.testingEnvironment)
|
||||
case .logout:
|
||||
@ -6233,7 +6253,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
case .rememberPassword:
|
||||
let context = self.context
|
||||
let controller = TwoFactorDataInputScreen(sharedContext: self.context.sharedContext, engine: .authorized(self.context.engine), mode: .rememberPassword(doneText: self.presentationData.strings.TwoFactorSetup_Done_Action), stateUpdated: { _ in
|
||||
let controller = TwoFactorDataInputScreen(sharedContext: self.context.sharedContext, engine: .authorized(self.context.engine), mode: .rememberPassword(doneText: self.presentationData.strings.TwoFactorSetup_Done_Action), stateUpdated: { _ in
|
||||
}, presentation: .modalInLargeLayout)
|
||||
controller.twoStepAuthSettingsController = { configuration in
|
||||
return twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: false, data: .single(TwoStepVerificationUnlockSettingsControllerData.access(configuration: TwoStepVerificationAccessConfiguration(configuration: configuration, password: nil)))))
|
||||
@ -6241,7 +6261,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
controller.passwordRemembered = {
|
||||
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
|
||||
}
|
||||
|
||||
let eventData = (body["eventData"] as? String)?.data(using: .utf8)
|
||||
let json = try? JSONSerialization.jsonObject(with: eventData ?? Data(), options: []) as? [String: Any]
|
||||
|
||||
switch eventName {
|
||||
case "web_app_ready":
|
||||
self.animateTransitionIn()
|
||||
@ -447,7 +450,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
case "web_app_setup_main_button":
|
||||
if let webView = self.webView, !webView.didTouchOnce && controller.url == nil {
|
||||
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 {
|
||||
let text = json["text"] as? String
|
||||
if (text ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
@ -475,6 +478,14 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.controller?.requestAttachmentMenuExpansion()
|
||||
case "web_app_close":
|
||||
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:
|
||||
break
|
||||
}
|
||||
@ -527,6 +538,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
themeParamsString.append("}}")
|
||||
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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user