Merge commit '34372148fb42eee7bc7cd724cb475dba0dc0e95d'

# Conflicts:
#	submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift
#	submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift
#	submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift
#	submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift
#	submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift
This commit is contained in:
Isaac 2025-06-24 13:06:19 +02:00
commit adae44e26f
20 changed files with 1549 additions and 142 deletions

View File

@ -1213,6 +1213,7 @@ public protocol SharedAccountContext: AnyObject {
func makeStoryStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: EnginePeer.Id, storyId: Int32, storyItem: EngineStoryItem, fromStory: Bool) -> ViewController func makeStoryStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: EnginePeer.Id, storyId: Int32, storyItem: EngineStoryItem, fromStory: Bool) -> ViewController
func makeStarsTransactionsScreen(context: AccountContext, starsContext: StarsContext) -> ViewController func makeStarsTransactionsScreen(context: AccountContext, starsContext: StarsContext) -> ViewController
func makeTonTransactionsScreen(context: AccountContext, tonContext: StarsContext) -> ViewController
func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, completion: @escaping (Int64) -> Void) -> ViewController func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, completion: @escaping (Int64) -> Void) -> ViewController
func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, extendedMedia: [TelegramExtendedMedia], inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)?, NoError>, completion: @escaping (Bool) -> Void) -> ViewController func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, extendedMedia: [TelegramExtendedMedia], inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)?, NoError>, completion: @escaping (Bool) -> Void) -> ViewController
func makeStarsSubscriptionTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, link: String, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)?, NoError>, navigateToPeer: @escaping (EnginePeer) -> Void) -> ViewController func makeStarsSubscriptionTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, link: String, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)?, NoError>, navigateToPeer: @escaping (EnginePeer) -> Void) -> ViewController

View File

@ -87,6 +87,12 @@ public extension ComponentTransition.Update {
transition.setBounds(view: view, bounds: CGRect(origin: CGPoint(), size: size)) transition.setBounds(view: view, bounds: CGRect(origin: CGPoint(), size: size))
transition.setPosition(view: view, position: position) transition.setPosition(view: view, position: position)
transition.setScale(view: view, scale: scale) transition.setScale(view: view, scale: scale)
} else {
if view is UIScrollView {
let frame = component.size.centered(around: component._position ?? CGPoint())
if view.frame != frame {
transition.setFrame(view: view, frame: frame)
}
} else { } else {
if component._anchorPoint != nil { if component._anchorPoint != nil {
view.bounds = CGRect(origin: CGPoint(), size: size) view.bounds = CGRect(origin: CGPoint(), size: size)
@ -95,6 +101,7 @@ public extension ComponentTransition.Update {
} }
transition.setPosition(view: view, position: position) transition.setPosition(view: view, position: position)
} }
}
let opacity = component._opacity ?? 1.0 let opacity = component._opacity ?? 1.0
if view.alpha != opacity { if view.alpha != opacity {
transition.setAlpha(view: view, alpha: opacity) transition.setAlpha(view: view, alpha: opacity)

View File

@ -723,6 +723,9 @@ public extension CombinedComponent {
updatedChild.view.bounds = CGRect(origin: CGPoint(), size: updatedChild.size) updatedChild.view.bounds = CGRect(origin: CGPoint(), size: updatedChild.size)
updatedChild.view.center = updatedChild._position ?? CGPoint() updatedChild.view.center = updatedChild._position ?? CGPoint()
updatedChild.view.transform = CGAffineTransform(scaleX: scale, y: scale) updatedChild.view.transform = CGAffineTransform(scaleX: scale, y: scale)
} else {
if updatedChild.view is UIScrollView {
updatedChild.view.frame = updatedChild.size.centered(around: updatedChild._position ?? CGPoint())
} else { } else {
updatedChild.view.bounds = CGRect(origin: CGPoint(), size: updatedChild.size) updatedChild.view.bounds = CGRect(origin: CGPoint(), size: updatedChild.size)
if updatedChild.view.layer.anchorPoint != CGPoint(x: 0.5, y: 0.5) { if updatedChild.view.layer.anchorPoint != CGPoint(x: 0.5, y: 0.5) {
@ -731,6 +734,7 @@ public extension CombinedComponent {
updatedChild.view.center = updatedChild._position ?? CGPoint() updatedChild.view.center = updatedChild._position ?? CGPoint()
} }
} }
}
updatedChild.view.alpha = updatedChild._opacity ?? 1.0 updatedChild.view.alpha = updatedChild._opacity ?? 1.0
updatedChild.view.clipsToBounds = updatedChild._clipsToBounds ?? false updatedChild.view.clipsToBounds = updatedChild._clipsToBounds ?? false

Binary file not shown.

View File

@ -280,9 +280,9 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
let itemLabel: NSAttributedString let itemLabel: NSAttributedString
let labelString: String let labelString: String
let absCount = StarsAmount(value: abs(item.transaction.count.value), nanos: abs(item.transaction.count.nanos)) let absCount = StarsAmount(value: abs(item.transaction.count.amount.value), nanos: abs(item.transaction.count.amount.nanos))
let formattedLabel = presentationStringsFormattedNumber(absCount, item.presentationData.dateTimeFormat.groupingSeparator) let formattedLabel = presentationStringsFormattedNumber(absCount, item.presentationData.dateTimeFormat.groupingSeparator)
if item.transaction.count < StarsAmount.zero { if item.transaction.count.amount < StarsAmount.zero {
labelString = "- \(formattedLabel)" labelString = "- \(formattedLabel)"
} else { } else {
labelString = "+ \(formattedLabel)" labelString = "+ \(formattedLabel)"

View File

@ -618,9 +618,9 @@ private final class StarsContextImpl {
} }
var transactions = state.transactions var transactions = state.transactions
if addTransaction { if addTransaction {
transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, currency: self.ton ? .ton : .stars, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil, transactionDate: nil, transactionUrl: nil, paidMessageId: nil, giveawayMessageId: nil, media: [], subscriptionPeriod: nil, starGift: nil, floodskipNumber: nil, starrefCommissionPermille: nil, starrefPeerId: nil, starrefAmount: nil, paidMessageCount: nil, premiumGiftMonths: nil), at: 0) let count = CurrencyAmount(amount: balance, currency: self.ton ? .ton : .stars)
transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: count, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil, transactionDate: nil, transactionUrl: nil, paidMessageId: nil, giveawayMessageId: nil, media: [], subscriptionPeriod: nil, starGift: nil, floodskipNumber: nil, starrefCommissionPermille: nil, starrefPeerId: nil, starrefAmount: nil, paidMessageCount: nil, premiumGiftMonths: nil), at: 0)
} }
self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: max(StarsAmount(value: 0, nanos: 0), state.balance + balance), subscriptions: state.subscriptions, canLoadMoreSubscriptions: state.canLoadMoreSubscriptions, transactions: transactions, canLoadMoreTransactions: state.canLoadMoreTransactions, isLoading: state.isLoading)) self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: max(StarsAmount(value: 0, nanos: 0), state.balance + balance), subscriptions: state.subscriptions, canLoadMoreSubscriptions: state.canLoadMoreSubscriptions, transactions: transactions, canLoadMoreTransactions: state.canLoadMoreTransactions, isLoading: state.isLoading))
} }
@ -724,9 +724,7 @@ private extension StarsContext.State.Transaction {
let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? [] let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? []
let _ = subscriptionPeriod let _ = subscriptionPeriod
let amount = CurrencyAmount(apiAmount: stars) self.init(flags: flags, id: id, count: CurrencyAmount(apiAmount: stars), date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl, paidMessageId: paidMessageId, giveawayMessageId: giveawayMessageId, media: media, subscriptionPeriod: subscriptionPeriod, starGift: starGift.flatMap { StarGift(apiStarGift: $0) }, floodskipNumber: floodskipNumber, starrefCommissionPermille: starrefCommissionPermille, starrefPeerId: starrefPeer?.peerId, starrefAmount: starrefAmount.flatMap(StarsAmount.init(apiAmount:)), paidMessageCount: paidMessageCount, premiumGiftMonths: premiumGiftMonths)
self.init(flags: flags, id: id, count: amount.amount, currency: amount.currency, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl, paidMessageId: paidMessageId, giveawayMessageId: giveawayMessageId, media: media, subscriptionPeriod: subscriptionPeriod, starGift: starGift.flatMap { StarGift(apiStarGift: $0) }, floodskipNumber: floodskipNumber, starrefCommissionPermille: starrefCommissionPermille, starrefPeerId: starrefPeer?.peerId, starrefAmount: starrefAmount.flatMap(StarsAmount.init(apiAmount:)), paidMessageCount: paidMessageCount, premiumGiftMonths: premiumGiftMonths)
} }
} }
} }
@ -791,8 +789,7 @@ public final class StarsContext {
public let flags: Flags public let flags: Flags
public let id: String public let id: String
public let count: StarsAmount public let count: CurrencyAmount
public let currency: CurrencyAmount.Currency
public let date: Int32 public let date: Int32
public let peer: Peer public let peer: Peer
public let title: String? public let title: String?
@ -815,8 +812,7 @@ public final class StarsContext {
public init( public init(
flags: Flags, flags: Flags,
id: String, id: String,
count: StarsAmount, count: CurrencyAmount,
currency: CurrencyAmount.Currency,
date: Int32, date: Int32,
peer: Peer, peer: Peer,
title: String?, title: String?,
@ -839,7 +835,6 @@ public final class StarsContext {
self.flags = flags self.flags = flags
self.id = id self.id = id
self.count = count self.count = count
self.currency = currency
self.date = date self.date = date
self.peer = peer self.peer = peer
self.title = title self.title = title
@ -1079,7 +1074,7 @@ public final class StarsContext {
return peerId! return peerId!
} }
public let ton: Bool let ton: Bool
public var currentState: StarsContext.State? { public var currentState: StarsContext.State? {
var state: StarsContext.State? var state: StarsContext.State?
@ -1173,9 +1168,9 @@ private final class StarsTransactionsContextImpl {
case .all: case .all:
initialTransactions = currentTransactions initialTransactions = currentTransactions
case .incoming: case .incoming:
initialTransactions = currentTransactions.filter { $0.count > StarsAmount.zero } initialTransactions = currentTransactions.filter { $0.count.amount > StarsAmount.zero }
case .outgoing: case .outgoing:
initialTransactions = currentTransactions.filter { $0.count < StarsAmount.zero } initialTransactions = currentTransactions.filter { $0.count.amount < StarsAmount.zero }
} }
self._state = StarsTransactionsContext.State(transactions: initialTransactions, canLoadMore: true, isLoading: false) self._state = StarsTransactionsContext.State(transactions: initialTransactions, canLoadMore: true, isLoading: false)
@ -1193,9 +1188,9 @@ private final class StarsTransactionsContextImpl {
case .all: case .all:
filteredTransactions = currentTransactions filteredTransactions = currentTransactions
case .incoming: case .incoming:
filteredTransactions = currentTransactions.filter { $0.count > StarsAmount.zero } filteredTransactions = currentTransactions.filter { $0.count.amount > StarsAmount.zero }
case .outgoing: case .outgoing:
filteredTransactions = currentTransactions.filter { $0.count < StarsAmount.zero } filteredTransactions = currentTransactions.filter { $0.count.amount < StarsAmount.zero }
} }
if !filteredTransactions.isEmpty && self._state.transactions.isEmpty && filteredTransactions != initialTransactions { if !filteredTransactions.isEmpty && self._state.transactions.isEmpty && filteredTransactions != initialTransactions {
@ -1220,9 +1215,9 @@ private final class StarsTransactionsContextImpl {
case .all: case .all:
filteredTransactions = currentTransactions filteredTransactions = currentTransactions
case .incoming: case .incoming:
filteredTransactions = currentTransactions.filter { $0.count > StarsAmount.zero } filteredTransactions = currentTransactions.filter { $0.count.amount > StarsAmount.zero }
case .outgoing: case .outgoing:
filteredTransactions = currentTransactions.filter { $0.count < StarsAmount.zero } filteredTransactions = currentTransactions.filter { $0.count.amount < StarsAmount.zero }
} }
if filteredTransactions != initialTransactions { if filteredTransactions != initialTransactions {

View File

@ -93,6 +93,15 @@ public func formatStarsAmountText(_ amount: StarsAmount, dateTimeFormat: Present
return balanceText return balanceText
} }
public func formatCurrencyAmountText(_ amount: CurrencyAmount, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false) -> String {
switch amount.currency {
case .stars:
return formatStarsAmountText(amount.amount, dateTimeFormat: dateTimeFormat, showPlus: showPlus)
case .ton:
return formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat, showPlus: showPlus)
}
}
private let invalidAddressCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=").inverted private let invalidAddressCharacters = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=").inverted
public func isValidTonAddress(_ address: String, exactLength: Bool = false) -> Bool { public func isValidTonAddress(_ address: String, exactLength: Bool = false) -> Bool {
if address.count > walletAddressLength || address.rangeOfCharacter(from: invalidAddressCharacters) != nil { if address.count > walletAddressLength || address.rangeOfCharacter(from: invalidAddressCharacters) != nil {

View File

@ -1037,9 +1037,9 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
} }
} }
if let tonState = data.tonState { if let tonState = data.tonState {
if !isPremiumDisabled || tonState.balance != .zero { if abs(tonState.balance.value) > 0 {
let balanceText: NSAttributedString let balanceText: NSAttributedString
if tonState.balance != .zero { if abs(tonState.balance.value) > 0 {
let formattedLabel = formatTonAmountText(tonState.balance.value, dateTimeFormat: presentationData.dateTimeFormat) let formattedLabel = formatTonAmountText(tonState.balance.value, dateTimeFormat: presentationData.dateTimeFormat)
let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0))
let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) let labelFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
@ -1049,19 +1049,19 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
balanceText = NSAttributedString() balanceText = NSAttributedString()
} }
//TODO:localize //TODO:localize
items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 105, label: .attributedText(balanceText), text: "TON", icon: PresentationResourcesSettings.ton, action: { items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 103, label: .attributedText(balanceText), text: "My TON", icon: PresentationResourcesSettings.ton, action: {
interaction.openSettings(.ton) interaction.openSettings(.ton)
})) }))
} }
} }
if !isPremiumDisabled || context.isPremium { if !isPremiumDisabled || context.isPremium {
items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 103, label: .text(""), additionalBadgeLabel: nil, text: presentationData.strings.Settings_Business, icon: PresentationResourcesSettings.business, action: { items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 104, label: .text(""), additionalBadgeLabel: nil, text: presentationData.strings.Settings_Business, icon: PresentationResourcesSettings.business, action: {
interaction.openSettings(.businessSetup) interaction.openSettings(.businessSetup)
})) }))
} }
if let starsState = data.starsState { if let starsState = data.starsState {
if !isPremiumDisabled || starsState.balance > StarsAmount.zero { if !isPremiumDisabled || starsState.balance > StarsAmount.zero {
items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 104, label: .text(""), text: presentationData.strings.Settings_SendGift, icon: PresentationResourcesSettings.premiumGift, action: { items[.payment]!.append(PeerInfoScreenDisclosureItem(id: 105, label: .text(""), text: presentationData.strings.Settings_SendGift, icon: PresentationResourcesSettings.premiumGift, action: {
interaction.openSettings(.premiumGift) interaction.openSettings(.premiumGift)
})) }))
} }
@ -10670,7 +10670,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
case .ton: case .ton:
if let tonContext = self.controller?.tonContext { if let tonContext = self.controller?.tonContext {
push(self.context.sharedContext.makeStarsTransactionsScreen(context: self.context, starsContext: tonContext)) push(self.context.sharedContext.makeTonTransactionsScreen(context: self.context, tonContext: tonContext))
} }
} }
} }
@ -12971,18 +12971,23 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
self.chatLocation = .peer(id: peerId) self.chatLocation = .peer(id: peerId)
} }
if isSettings, let starsContext = context.starsContext { if isSettings {
if let starsContext = context.starsContext {
self.starsContext = starsContext self.starsContext = starsContext
starsContext.load(force: true) starsContext.load(force: true)
} else { } else {
self.starsContext = nil self.starsContext = nil
} }
if isSettings, let tonContext = context.tonContext { if let tonContext = context.tonContext {
self.tonContext = tonContext self.tonContext = tonContext
tonContext.load(force: true) tonContext.load(force: true)
} else { } else {
self.tonContext = nil self.tonContext = nil
} }
} else {
self.starsContext = nil
self.tonContext = nil
}
if isMyProfile, let profileGiftsContext { if isMyProfile, let profileGiftsContext {
profileGiftsContext.updateFilter(.All) profileGiftsContext.updateFilter(.All)

View File

@ -0,0 +1,27 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "PremiumDiamondComponent",
module_name = "PremiumDiamondComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/GZip",
"//submodules/LegacyComponents",
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
"//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,309 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import SceneKit
import GZip
import AppBundle
import LegacyComponents
import PremiumStarComponent
private let sceneVersion: Int = 5
private func deg2rad(_ number: Float) -> Float {
return number * .pi / 180
}
private func rad2deg(_ number: Float) -> Float {
return number * 180.0 / .pi
}
public final class PremiumDiamondComponent: Component {
public init() {
}
public static func ==(lhs: PremiumDiamondComponent, rhs: PremiumDiamondComponent) -> Bool {
return true
}
public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView {
public final class Tag {
public init() {
}
}
public func matches(tag: Any) -> Bool {
if let _ = tag as? Tag {
return true
}
return false
}
private var _ready = Promise<Bool>()
public var ready: Signal<Bool, NoError> {
return self._ready.get()
}
weak var animateFrom: UIView?
weak var containerView: UIView?
private let sceneView: SCNView
private var timer: SwiftSignalKit.Timer?
private var component: PremiumDiamondComponent?
override init(frame: CGRect) {
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: CGSize(width: 64.0, height: 64.0)))
self.sceneView.backgroundColor = .clear
self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
self.sceneView.isUserInteractionEnabled = false
self.sceneView.preferredFramesPerSecond = 60
self.sceneView.isJitteringEnabled = true
super.init(frame: frame)
self.addSubview(self.sceneView)
self.setup()
let panGestureRecoginzer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
self.addGestureRecognizer(panGestureRecoginzer)
self.disablesInteractiveModalDismiss = true
self.disablesInteractiveTransitionGestureRecognizer = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.timer?.invalidate()
}
private var previousYaw: Float = 0.0
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
return
}
let keys = [
"rotate",
"tapRotate",
"continuousRotation"
]
for key in keys {
node.removeAnimation(forKey: key)
}
switch gesture.state {
case .began:
self.previousYaw = 0.0
case .changed:
let translation = gesture.translation(in: gesture.view)
let yawPan = deg2rad(Float(translation.x))
func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat {
let bandedOffset = offset - bandingStart
let range: CGFloat = 60.0
let coefficient: CGFloat = 0.4
return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range
}
var pitchTranslation = rubberBandingOffset(offset: abs(translation.y), bandingStart: 0.0)
if translation.y < 0.0 {
pitchTranslation *= -1.0
}
let pitchPan = deg2rad(Float(pitchTranslation))
self.previousYaw = yawPan
// Maintain the initial tilt while adding pan gestures
let initialTiltX: Float = deg2rad(-15.0)
let initialTiltZ: Float = deg2rad(5.0)
node.eulerAngles = SCNVector3(initialTiltX + pitchPan, yawPan, initialTiltZ)
case .ended:
let velocity = gesture.velocity(in: gesture.view)
var smallAngle = false
if (self.previousYaw < .pi / 2 && self.previousYaw > -.pi / 2) && abs(velocity.x) < 200 {
smallAngle = true
}
self.playAppearanceAnimation(velocity: velocity.x, smallAngle: smallAngle, explode: !smallAngle && abs(velocity.x) > 600)
default:
break
}
}
private func setup() {
guard let scene = loadCompressedScene(name: "diamond", version: sceneVersion) else {
return
}
self.sceneView.scene = scene
self.sceneView.delegate = self
let _ = self.sceneView.snapshot()
}
private var didSetReady = false
public func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) {
if !self.didSetReady {
self.didSetReady = true
Queue.mainQueue().justDispatch {
self._ready.set(.single(true))
self.onReady()
}
}
}
private func onReady() {
self.playAppearanceAnimation(mirror: true, explode: true)
}
private func playAppearanceAnimation(velocity: CGFloat? = nil, smallAngle: Bool = false, mirror: Bool = false, explode: Bool = false) {
guard let scene = self.sceneView.scene else {
return
}
if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particlesLeft = scene.rootNode.childNode(withName: "particles_left", recursively: false), let particlesRight = scene.rootNode.childNode(withName: "particles_right", recursively: false), let particlesBottomLeft = scene.rootNode.childNode(withName: "particles_left_bottom", recursively: false), let particlesBottomRight = scene.rootNode.childNode(withName: "particles_right_bottom", recursively: false) {
if let leftParticleSystem = particlesLeft.particleSystems?.first, let rightParticleSystem = particlesRight.particleSystems?.first, let leftBottomParticleSystem = particlesBottomLeft.particleSystems?.first, let rightBottomParticleSystem = particlesBottomRight.particleSystems?.first {
leftParticleSystem.speedFactor = 2.0
leftParticleSystem.particleVelocity = 1.6
leftParticleSystem.birthRate = 60.0
leftParticleSystem.particleLifeSpan = 4.0
rightParticleSystem.speedFactor = 2.0
rightParticleSystem.particleVelocity = 1.6
rightParticleSystem.birthRate = 60.0
rightParticleSystem.particleLifeSpan = 4.0
leftBottomParticleSystem.particleVelocity = 1.6
leftBottomParticleSystem.birthRate = 24.0
leftBottomParticleSystem.particleLifeSpan = 7.0
rightBottomParticleSystem.particleVelocity = 1.6
rightBottomParticleSystem.birthRate = 24.0
rightBottomParticleSystem.particleLifeSpan = 7.0
node.physicsField?.isActive = true
Queue.mainQueue().after(1.0) {
node.physicsField?.isActive = false
leftParticleSystem.birthRate = 15.0
leftParticleSystem.particleVelocity = 1.0
leftParticleSystem.particleLifeSpan = 3.0
rightParticleSystem.birthRate = 15.0
rightParticleSystem.particleVelocity = 1.0
rightParticleSystem.particleLifeSpan = 3.0
leftBottomParticleSystem.particleVelocity = 1.0
leftBottomParticleSystem.birthRate = 10.0
leftBottomParticleSystem.particleLifeSpan = 5.0
rightBottomParticleSystem.particleVelocity = 1.0
rightBottomParticleSystem.birthRate = 10.0
rightBottomParticleSystem.particleLifeSpan = 5.0
let leftAnimation = POPBasicAnimation()
leftAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in
property?.readBlock = { particleSystem, values in
values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor
}
property?.writeBlock = { particleSystem, values in
(particleSystem as! SCNParticleSystem).speedFactor = values!.pointee
}
property?.threshold = 0.01
}) as! POPAnimatableProperty)
leftAnimation.fromValue = 1.2 as NSNumber
leftAnimation.toValue = 0.85 as NSNumber
leftAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
leftAnimation.duration = 0.5
leftParticleSystem.pop_add(leftAnimation, forKey: "speedFactor")
let rightAnimation = POPBasicAnimation()
rightAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in
property?.readBlock = { particleSystem, values in
values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor
}
property?.writeBlock = { particleSystem, values in
(particleSystem as! SCNParticleSystem).speedFactor = values!.pointee
}
property?.threshold = 0.01
}) as! POPAnimatableProperty)
rightAnimation.fromValue = 1.2 as NSNumber
rightAnimation.toValue = 0.85 as NSNumber
rightAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
rightAnimation.duration = 0.5
rightParticleSystem.pop_add(rightAnimation, forKey: "speedFactor")
}
}
}
// var from = node.presentation.eulerAngles
// if abs(from.y - .pi * 2.0) < 0.001 {
// from.y = 0.0
// }
// node.removeAnimation(forKey: "tapRotate")
//
// var toValue: Float = smallAngle ? 0.0 : .pi * 2.0
// if let velocity = velocity, !smallAngle && abs(velocity) > 200 && velocity < 0.0 {
// toValue *= -1
// }
// if mirror {
// toValue *= -1
// }
//
//
// let to = SCNVector3(x: from.x, y: toValue, z: from.z)
// let distance = rad2deg(to.y - from.y)
//
// guard !distance.isZero else {
// Queue.mainQueue().after(0.1) { [weak self] in
// self?.setupContinuousRotation()
// }
// return
// }
//
// let springAnimation = CASpringAnimation(keyPath: "eulerAngles")
// springAnimation.fromValue = NSValue(scnVector3: from)
// springAnimation.toValue = NSValue(scnVector3: to)
// springAnimation.mass = 1.0
// springAnimation.stiffness = 21.0
// springAnimation.damping = 5.8
// springAnimation.duration = springAnimation.settlingDuration * 0.75
// springAnimation.initialVelocity = velocity.flatMap { abs($0 / CGFloat(distance)) } ?? 1.7
// springAnimation.completion = { [weak self] finished in
// if finished {
// self?.setupContinuousRotation()
// }
// }
// node.addAnimation(springAnimation, forKey: "rotate")
}
func update(component: PremiumDiamondComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
self.component = component
self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0))
if self.sceneView.superview == self {
self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)
}
return availableSize
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}

View File

@ -372,42 +372,36 @@ public final class StarsAvatarComponent: Component {
} }
public final class StarsLabelComponent: CombinedComponent { public final class StarsLabelComponent: CombinedComponent {
let theme: PresentationTheme
let currency: CurrencyAmount.Currency
let textColor: UIColor
let text: NSAttributedString let text: NSAttributedString
let subtext: NSAttributedString? let subtext: NSAttributedString?
let iconName: String
let iconColor: UIColor?
public init( public init(
theme: PresentationTheme,
currency: CurrencyAmount.Currency,
textColor: UIColor,
text: NSAttributedString, text: NSAttributedString,
subtext: NSAttributedString? = nil subtext: NSAttributedString? = nil,
iconName: String = "Premium/Stars/StarMedium",
iconColor: UIColor? = nil
) { ) {
self.currency = currency
self.theme = theme
self.textColor = textColor
self.text = text self.text = text
self.subtext = subtext self.subtext = subtext
self.iconName = iconName
self.iconColor = iconColor
} }
public static func ==(lhs: StarsLabelComponent, rhs: StarsLabelComponent) -> Bool { public static func ==(lhs: StarsLabelComponent, rhs: StarsLabelComponent) -> Bool {
if lhs.currency != rhs.currency {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.textColor != rhs.textColor {
return false
}
if lhs.text != rhs.text { if lhs.text != rhs.text {
return false return false
} }
if lhs.subtext != rhs.subtext { if lhs.subtext != rhs.subtext {
return false return false
} }
if lhs.iconName != rhs.iconName {
return false
}
if lhs.iconColor != rhs.iconColor {
return false
}
return true return true
} }
@ -425,6 +419,7 @@ public final class StarsLabelComponent: CombinedComponent {
transition: context.transition transition: context.transition
) )
var subtext: _UpdatedChildComponent? = nil var subtext: _UpdatedChildComponent? = nil
if let sublabel = component.subtext { if let sublabel = component.subtext {
subtext = subLabel.update( subtext = subLabel.update(
@ -434,12 +429,11 @@ public final class StarsLabelComponent: CombinedComponent {
) )
} }
let iconSize: CGSize = component.currency == .ton ? CGSize(width: 16.0, height: 16.0) : CGSize(width: 20.0, height: 20.0) let iconSize = CGSize(width: 20.0, height: 20.0)
let icon = icon.update( let icon = icon.update(
component: BundleIconComponent( component: BundleIconComponent(
name: component.currency == .ton ? "Ads/TonBig" : "Premium/Stars/StarMedium", name: component.iconName,
tintColor: component.currency == .ton ? component.textColor : nil, tintColor: component.iconColor
maxSize: iconSize
), ),
availableSize: iconSize, availableSize: iconSize,
transition: context.transition transition: context.transition

View File

@ -217,7 +217,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
var statusText: String? var statusText: String?
var statusIsDestructive = false var statusIsDestructive = false
let count: CurrencyAmount let count: StarsAmount
var countIsGeneric = false var countIsGeneric = false
var countOnTop = false var countOnTop = false
var transactionId: String? var transactionId: String?
@ -257,7 +257,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
titleText = strings.Stars_Transaction_Giveaway_Boost_Stars(Int32(stars)) titleText = strings.Stars_Transaction_Giveaway_Boost_Stars(Int32(stars))
descriptionText = "" descriptionText = ""
boostsText = strings.Stars_Transaction_Giveaway_Boost_Boosts(boosts) boostsText = strings.Stars_Transaction_Giveaway_Boost_Boosts(boosts)
count = CurrencyAmount(amount: StarsAmount(value: stars, nanos: 0), currency: .stars) count = StarsAmount(value: stars, nanos: 0)
date = boost.date date = boost.date
toPeer = state.peerMap[peerId] toPeer = state.peerMap[peerId]
giveawayMessageId = boost.giveawayMessageId giveawayMessageId = boost.giveawayMessageId
@ -266,7 +266,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let usdValue = formatTonUsdValue(pricing.amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat) let usdValue = formatTonUsdValue(pricing.amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat)
titleText = strings.Stars_Transaction_Subscription_Title titleText = strings.Stars_Transaction_Subscription_Title
descriptionText = strings.Stars_Transaction_Subscription_PerMonthUsd(usdValue).string descriptionText = strings.Stars_Transaction_Subscription_PerMonthUsd(usdValue).string
count = CurrencyAmount(amount: pricing.amount, currency: .stars) count = pricing.amount
countOnTop = true countOnTop = true
date = importer.date date = importer.date
toPeer = importer.peer.peer.flatMap(EnginePeer.init) toPeer = importer.peer.peer.flatMap(EnginePeer.init)
@ -288,7 +288,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
photo = subscription.photo photo = subscription.photo
descriptionText = "" descriptionText = ""
count = CurrencyAmount(amount: subscription.pricing.amount, currency: .stars) count = subscription.pricing.amount
date = subscription.untilDate date = subscription.untilDate
if let creationDate = (subscription.peer._asPeer() as? TelegramChannel)?.creationDate, creationDate > 0 { if let creationDate = (subscription.peer._asPeer() as? TelegramChannel)?.creationDate, creationDate > 0 {
additionalDate = creationDate additionalDate = creationDate
@ -376,7 +376,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
titleText = gift.title titleText = gift.title
descriptionText = "\(strings.Gift_Unique_Collectible) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))" descriptionText = "\(strings.Gift_Unique_Collectible) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))"
} }
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency) count = transaction.count.amount
transactionId = transaction.id transactionId = transaction.id
date = transaction.date date = transaction.date
if case let .peer(peer) = transaction.peer { if case let .peer(peer) = transaction.peer {
@ -395,7 +395,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else if let giveawayMessageIdValue = transaction.giveawayMessageId { } else if let giveawayMessageIdValue = transaction.giveawayMessageId {
titleText = strings.Stars_Transaction_Giveaway_Title titleText = strings.Stars_Transaction_Giveaway_Title
descriptionText = "" descriptionText = ""
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency) count = transaction.count.amount
transactionId = transaction.id transactionId = transaction.id
date = transaction.date date = transaction.date
giveawayMessageId = giveawayMessageIdValue giveawayMessageId = giveawayMessageIdValue
@ -406,7 +406,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else if let _ = transaction.subscriptionPeriod { } else if let _ = transaction.subscriptionPeriod {
titleText = strings.Stars_Transaction_SubscriptionFee titleText = strings.Stars_Transaction_SubscriptionFee
descriptionText = "" descriptionText = ""
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency) count = transaction.count.amount
transactionId = transaction.id transactionId = transaction.id
date = transaction.date date = transaction.date
if case let .peer(peer) = transaction.peer { if case let .peer(peer) = transaction.peer {
@ -417,7 +417,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else if transaction.flags.contains(.isGift) { } else if transaction.flags.contains(.isGift) {
titleText = strings.Stars_Gift_Received_Title titleText = strings.Stars_Gift_Received_Title
descriptionText = strings.Stars_Gift_Received_Text descriptionText = strings.Stars_Gift_Received_Text
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency) count = transaction.count.amount
countOnTop = true countOnTop = true
transactionId = transaction.id transactionId = transaction.id
date = transaction.date date = transaction.date
@ -446,7 +446,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
countOnTop = false countOnTop = false
descriptionText = "" descriptionText = ""
} }
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency) count = transaction.count.amount
transactionId = transaction.id transactionId = transaction.id
date = transaction.date date = transaction.date
transactionPeer = transaction.peer transactionPeer = transaction.peer
@ -457,7 +457,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
titleText = strings.Stars_Transaction_Reaction_Title titleText = strings.Stars_Transaction_Reaction_Title
descriptionText = "" descriptionText = ""
messageId = transaction.paidMessageId messageId = transaction.paidMessageId
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency) count = transaction.count.amount
transactionId = transaction.id transactionId = transaction.id
date = transaction.date date = transaction.date
if case let .peer(peer) = transaction.peer { if case let .peer(peer) = transaction.peer {
@ -490,7 +490,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
via = strings.Stars_Transaction_PremiumBotTopUp_Subtitle via = strings.Stars_Transaction_PremiumBotTopUp_Subtitle
case .fragment: case .fragment:
if parentPeer.id == component.context.account.peerId { if parentPeer.id == component.context.account.peerId {
if (transaction.count.value < 0 && !transaction.flags.contains(.isRefund)) || (transaction.count.value > 0 && transaction.flags.contains(.isRefund)) { if (transaction.count.amount.value < 0 && !transaction.flags.contains(.isRefund)) || (transaction.count.amount.value > 0 && transaction.flags.contains(.isRefund)) {
titleText = strings.Stars_Transaction_FragmentWithdrawal_Title titleText = strings.Stars_Transaction_FragmentWithdrawal_Title
via = strings.Stars_Transaction_FragmentWithdrawal_Subtitle via = strings.Stars_Transaction_FragmentWithdrawal_Subtitle
} else { } else {
@ -545,7 +545,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
messageId = transaction.paidMessageId messageId = transaction.paidMessageId
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency) count = transaction.count.amount
transactionId = transaction.id transactionId = transaction.id
date = transaction.date date = transaction.date
if case let .peer(peer) = transaction.peer { if case let .peer(peer) = transaction.peer {
@ -564,7 +564,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
case let .receipt(receipt): case let .receipt(receipt):
titleText = receipt.invoiceMedia.title titleText = receipt.invoiceMedia.title
descriptionText = receipt.invoiceMedia.description descriptionText = receipt.invoiceMedia.description
count = CurrencyAmount(amount: StarsAmount(value: (receipt.invoice.prices.first?.amount ?? receipt.invoiceMedia.totalAmount) * -1, nanos: 0), currency: .stars) count = StarsAmount(value: (receipt.invoice.prices.first?.amount ?? receipt.invoiceMedia.totalAmount) * -1, nanos: 0)
transactionId = receipt.transactionId transactionId = receipt.transactionId
date = receipt.date date = receipt.date
if let peer = state.peerMap[receipt.botPaymentId] { if let peer = state.peerMap[receipt.botPaymentId] {
@ -581,7 +581,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
if case let .giftStars(_, _, countValue, _, _, _) = action.action { if case let .giftStars(_, _, countValue, _, _, _) = action.action {
titleText = incoming ? strings.Stars_Gift_Received_Title : strings.Stars_Gift_Sent_Title titleText = incoming ? strings.Stars_Gift_Received_Title : strings.Stars_Gift_Sent_Title
count = CurrencyAmount(amount: StarsAmount(value: countValue, nanos: 0), currency: .stars) count = StarsAmount(value: countValue, nanos: 0)
if !incoming { if !incoming {
countIsGeneric = true countIsGeneric = true
} }
@ -595,7 +595,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else if case let .prizeStars(countValue, _, boostPeerId, _, giveawayMessageIdValue) = action.action { } else if case let .prizeStars(countValue, _, boostPeerId, _, giveawayMessageIdValue) = action.action {
titleText = strings.Stars_Transaction_Giveaway_Title titleText = strings.Stars_Transaction_Giveaway_Title
count = CurrencyAmount(amount: StarsAmount(value: countValue, nanos: 0), currency: .stars) count = StarsAmount(value: countValue, nanos: 0)
countOnTop = true countOnTop = true
transactionId = nil transactionId = nil
giveawayMessageId = giveawayMessageIdValue giveawayMessageId = giveawayMessageIdValue
@ -648,14 +648,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
headerTextColor = theme.actionSheet.primaryTextColor headerTextColor = theme.actionSheet.primaryTextColor
} }
let absCount = StarsAmount(value: abs(count.amount.value), nanos: abs(count.amount.nanos)) let absCount = StarsAmount(value: abs(count.value), nanos: abs(count.nanos))
let formattedAmount: String let formattedAmount = formatStarsAmountText(absCount, dateTimeFormat: dateTimeFormat)
switch count.currency {
case .stars:
formattedAmount = formatStarsAmountText(absCount, dateTimeFormat: dateTimeFormat)
case .ton:
formattedAmount = formatTonAmountText(absCount.value, dateTimeFormat: dateTimeFormat)
}
let countColor: UIColor let countColor: UIColor
var countFont: UIFont = isSubscription || isSubscriber ? Font.regular(17.0) : Font.semibold(17.0) var countFont: UIFont = isSubscription || isSubscriber ? Font.regular(17.0) : Font.semibold(17.0)
var countBackgroundColor: UIColor? var countBackgroundColor: UIColor?
@ -670,7 +664,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else if countIsGeneric { } else if countIsGeneric {
amountText = "\(formattedAmount)" amountText = "\(formattedAmount)"
countColor = theme.list.itemPrimaryTextColor countColor = theme.list.itemPrimaryTextColor
} else if count.amount < StarsAmount.zero { } else if count < StarsAmount.zero {
amountText = "- \(formattedAmount)" amountText = "- \(formattedAmount)"
if case .unique = giftAnimationSubject { if case .unique = giftAnimationSubject {
countColor = .white countColor = .white
@ -712,9 +706,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
imageSubject = .gift(premiumGiftMonths) imageSubject = .gift(premiumGiftMonths)
} else if isGift { } else if isGift {
var value: Int32 = 3 var value: Int32 = 3
if count.amount.value <= 1000 { if count.value <= 1000 {
value = 3 value = 3
} else if count.amount.value < 2500 { } else if count.value < 2500 {
value = 6 value = 6
} else { } else {
value = 12 value = 12
@ -732,9 +726,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
imageSubject = .none imageSubject = .none
} }
if isSubscription || isSubscriber || isSubscriptionFee || giveawayMessageId != nil { if isSubscription || isSubscriber || isSubscriptionFee || giveawayMessageId != nil {
imageIcon = count.currency == .ton ? .ton : .star imageIcon = .star
} else { } else {
imageIcon = count.currency == .ton ? .ton : nil imageIcon = nil
} }
if isSubscription && "".isEmpty { if isSubscription && "".isEmpty {
@ -817,26 +811,10 @@ private final class StarsTransactionSheetContent: CombinedComponent {
transition: .immediate transition: .immediate
) )
let amountStarIconName: String
var amountStarTintColor: UIColor?
var amountStarMaxSize: CGSize?
var amountOffset = CGPoint()
if boostsText != nil {
amountStarIconName = "Premium/BoostButtonIcon"
} else if case .ton = count.currency {
amountStarIconName = "Ads/TonBig"
amountStarTintColor = countColor
amountStarMaxSize = CGSize(width: 14.0, height: 14.0)
amountOffset.y += 3.0
} else {
amountStarIconName = "Premium/Stars/StarMedium"
}
let amountStar = amountStar.update( let amountStar = amountStar.update(
component: BundleIconComponent( component: BundleIconComponent(
name: amountStarIconName, name: boostsText != nil ? "Premium/BoostButtonIcon" : "Premium/Stars/StarMedium",
tintColor: amountStarTintColor, tintColor: nil
maxSize: amountStarMaxSize
), ),
availableSize: context.availableSize, availableSize: context.availableSize,
transition: .immediate transition: .immediate
@ -858,7 +836,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
)) ))
} else if case .unique = giftAnimationSubject { } else if case .unique = giftAnimationSubject {
let reason: String let reason: String
if count.amount < StarsAmount.zero, case let .transaction(transaction, _) = subject { if count < StarsAmount.zero, case let .transaction(transaction, _) = subject {
if transaction.flags.contains(.isStarGiftResale) { if transaction.flags.contains(.isStarGiftResale) {
reason = strings.Stars_Transaction_GiftPurchase reason = strings.Stars_Transaction_GiftPurchase
} else { } else {
@ -914,7 +892,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else if isSubscriber { } else if isSubscriber {
title = strings.Stars_Transaction_Subscription_Subscriber title = strings.Stars_Transaction_Subscription_Subscriber
} else { } else {
title = count.amount < StarsAmount.zero || countIsGeneric ? strings.Stars_Transaction_To : strings.Stars_Transaction_From title = count < StarsAmount.zero || countIsGeneric ? strings.Stars_Transaction_To : strings.Stars_Transaction_From
} }
let toComponent: AnyComponent<Empty> let toComponent: AnyComponent<Empty>
@ -1019,7 +997,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
id: "prize", id: "prize",
title: strings.Stars_Transaction_Giveaway_Prize, title: strings.Stars_Transaction_Giveaway_Prize,
component: AnyComponent( component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Stars_Transaction_Giveaway_Stars(Int32(count.amount.value)), font: tableFont, textColor: tableTextColor))) MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Stars_Transaction_Giveaway_Stars(Int32(count.value)), font: tableFont, textColor: tableTextColor)))
) )
)) ))
@ -1195,7 +1173,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} }
if let starrefCommissionPermille = transaction.starrefCommissionPermille, transaction.starrefPeerId != nil { if let starrefCommissionPermille = transaction.starrefCommissionPermille, transaction.starrefPeerId != nil {
if transaction.flags.contains(.isPaidMessage) || transaction.flags.contains(.isStarGiftResale) { if transaction.flags.contains(.isPaidMessage) || transaction.flags.contains(.isStarGiftResale) {
var totalStars = transaction.count var totalStars = transaction.count.amount
if let starrefCount = transaction.starrefAmount { if let starrefCount = transaction.starrefAmount {
totalStars = totalStars + starrefCount totalStars = totalStars + starrefCount
} }
@ -1521,7 +1499,6 @@ private final class StarsTransactionSheetContent: CombinedComponent {
amountLabelOffsetY = 2.0 amountLabelOffsetY = 2.0
amountStarOffsetY = 5.0 amountStarOffsetY = 5.0
} }
amountStarOffsetY += amountOffset.y
context.add(amount context.add(amount
.position(CGPoint(x: amountLabelOriginX, y: amountOrigin + amount.size.height / 2.0 + amountLabelOffsetY)) .position(CGPoint(x: amountLabelOriginX, y: amountOrigin + amount.size.height / 2.0 + amountLabelOffsetY))

View File

@ -50,6 +50,8 @@ swift_library(
"//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/LottieComponentResourceContent", "//submodules/TelegramUI/Components/LottieComponentResourceContent",
"//submodules/TelegramUI/Components/Gifts/GiftAnimationComponent", "//submodules/TelegramUI/Components/Gifts/GiftAnimationComponent",
"//submodules/TelegramUI/Components/Premium/PremiumCoinComponent",
"//submodules/TelegramUI/Components/Premium/PremiumDiamondComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -16,8 +16,8 @@ final class StarsBalanceComponent: Component {
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let dateTimeFormat: PresentationDateTimeFormat let dateTimeFormat: PresentationDateTimeFormat
let currency: CurrencyAmount.Currency
let count: StarsAmount let count: StarsAmount
let isTon: Bool
let rate: Double? let rate: Double?
let actionTitle: String let actionTitle: String
let actionAvailable: Bool let actionAvailable: Bool
@ -35,8 +35,8 @@ final class StarsBalanceComponent: Component {
theme: PresentationTheme, theme: PresentationTheme,
strings: PresentationStrings, strings: PresentationStrings,
dateTimeFormat: PresentationDateTimeFormat, dateTimeFormat: PresentationDateTimeFormat,
currency: CurrencyAmount.Currency,
count: StarsAmount, count: StarsAmount,
isTon: Bool = false,
rate: Double?, rate: Double?,
actionTitle: String, actionTitle: String,
actionAvailable: Bool, actionAvailable: Bool,
@ -53,8 +53,8 @@ final class StarsBalanceComponent: Component {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.currency = currency
self.count = count self.count = count
self.isTon = isTon
self.rate = rate self.rate = rate
self.actionTitle = actionTitle self.actionTitle = actionTitle
self.actionAvailable = actionAvailable self.actionAvailable = actionAvailable
@ -100,6 +100,9 @@ final class StarsBalanceComponent: Component {
if lhs.count != rhs.count { if lhs.count != rhs.count {
return false return false
} }
if lhs.isTon != rhs.isTon {
return false
}
if lhs.rate != rhs.rate { if lhs.rate != rhs.rate {
return false return false
} }
@ -123,8 +126,6 @@ final class StarsBalanceComponent: Component {
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
self.icon.image = UIImage(bundleImageName: "Premium/Stars/BalanceStar")
self.addSubview(self.icon) self.addSubview(self.icon)
} }
@ -133,6 +134,14 @@ final class StarsBalanceComponent: Component {
} }
func update(component: StarsBalanceComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize { func update(component: StarsBalanceComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
if self.component == nil {
if component.isTon {
self.icon.image = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonBig"), color: component.theme.list.itemAccentColor)
} else {
self.icon.image = UIImage(bundleImageName: "Premium/Stars/BalanceStar")
}
}
self.component = component self.component = component
self.state = state self.state = state
@ -168,11 +177,10 @@ final class StarsBalanceComponent: Component {
var contentHeight: CGFloat = sideInset var contentHeight: CGFloat = sideInset
let formattedLabel: String let formattedLabel: String
switch component.currency { if component.isTon {
case .stars:
formattedLabel = formatStarsAmountText(component.count, dateTimeFormat: component.dateTimeFormat)
case .ton:
formattedLabel = formatTonAmountText(component.count.value, dateTimeFormat: component.dateTimeFormat) formattedLabel = formatTonAmountText(component.count.value, dateTimeFormat: component.dateTimeFormat)
} else {
formattedLabel = formatStarsAmountText(component.count, dateTimeFormat: component.dateTimeFormat)
} }
let labelFont: UIFont let labelFont: UIFont
if formattedLabel.contains(component.dateTimeFormat.decimalSeparator) { if formattedLabel.contains(component.dateTimeFormat.decimalSeparator) {
@ -197,13 +205,13 @@ final class StarsBalanceComponent: Component {
self.addSubview(titleView) self.addSubview(titleView)
} }
if let icon = self.icon.image { if let icon = self.icon.image {
let spacing: CGFloat = 3.0 let spacing: CGFloat = 4.0
let totalWidth = titleSize.width + icon.size.width + spacing let totalWidth = titleSize.width + icon.size.width + spacing
let origin = floorToScreenPixels((availableSize.width - totalWidth) / 2.0) let origin = floorToScreenPixels((availableSize.width - totalWidth) / 2.0)
let titleFrame = CGRect(origin: CGPoint(x: origin + icon.size.width + spacing, y: contentHeight - 3.0), size: titleSize) let titleFrame = CGRect(origin: CGPoint(x: origin + icon.size.width + spacing, y: contentHeight - 3.0), size: titleSize)
titleView.frame = titleFrame titleView.frame = titleFrame
self.icon.frame = CGRect(origin: CGPoint(x: origin, y: contentHeight), size: icon.size) self.icon.frame = CGRect(origin: CGPoint(x: origin, y: floorToScreenPixels(titleFrame.midY - icon.size.height / 2.0)), size: icon.size)
} }
} }
contentHeight += titleSize.height contentHeight += titleSize.height

View File

@ -21,7 +21,7 @@ import TelegramStringFormatting
private extension StarsContext.State.Transaction { private extension StarsContext.State.Transaction {
var extendedId: String { var extendedId: String {
if self.count > StarsAmount.zero { if self.count.amount > StarsAmount.zero {
return "\(id)_in" return "\(id)_in"
} else { } else {
return "\(id)_out" return "\(id)_out"
@ -320,7 +320,7 @@ final class StarsTransactionsListPanelComponent: Component {
switch starGift { switch starGift {
case let .generic(gift): case let .generic(gift):
itemFile = gift.file itemFile = gift.file
itemSubtitle = item.count > StarsAmount.zero ? environment.strings.Stars_Intro_Transaction_ConvertedGift : environment.strings.Stars_Intro_Transaction_Gift itemSubtitle = item.count.amount > StarsAmount.zero ? environment.strings.Stars_Intro_Transaction_ConvertedGift : environment.strings.Stars_Intro_Transaction_Gift
case let .unique(gift): case let .unique(gift):
for attribute in gift.attributes { for attribute in gift.attributes {
if case let .model(_, file, _) = attribute { if case let .model(_, file, _) = attribute {
@ -328,7 +328,7 @@ final class StarsTransactionsListPanelComponent: Component {
break break
} }
} }
if item.count > StarsAmount.zero { if item.count.amount > StarsAmount.zero {
itemSubtitle = environment.strings.Stars_Intro_Transaction_GiftSale itemSubtitle = environment.strings.Stars_Intro_Transaction_GiftSale
} else { } else {
if item.flags.contains(.isStarGiftResale) { if item.flags.contains(.isStarGiftResale) {
@ -373,7 +373,7 @@ final class StarsTransactionsListPanelComponent: Component {
itemSubtitle = environment.strings.Stars_Intro_Transaction_Gift_Title itemSubtitle = environment.strings.Stars_Intro_Transaction_Gift_Title
itemPeer = .fragment itemPeer = .fragment
} else { } else {
if (item.count.value < 0 && !item.flags.contains(.isRefund)) || (item.count.value > 0 && item.flags.contains(.isRefund)) { if (item.count.amount.value < 0 && !item.flags.contains(.isRefund)) || (item.count.amount.value > 0 && item.flags.contains(.isRefund)) {
itemTitle = environment.strings.Stars_Intro_Transaction_FragmentWithdrawal_Title itemTitle = environment.strings.Stars_Intro_Transaction_FragmentWithdrawal_Title
itemSubtitle = environment.strings.Stars_Intro_Transaction_FragmentWithdrawal_Subtitle itemSubtitle = environment.strings.Stars_Intro_Transaction_FragmentWithdrawal_Subtitle
} else { } else {
@ -382,7 +382,7 @@ final class StarsTransactionsListPanelComponent: Component {
} }
} }
} else { } else {
if item.count > StarsAmount.zero && !item.flags.contains(.isRefund) { if item.count.amount > StarsAmount.zero && !item.flags.contains(.isRefund) {
itemTitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Title itemTitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Title
itemSubtitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Subtitle itemSubtitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Subtitle
} else { } else {
@ -409,19 +409,24 @@ final class StarsTransactionsListPanelComponent: Component {
} }
let itemLabel: NSAttributedString let itemLabel: NSAttributedString
let formattedLabel: String let formattedLabel = formatCurrencyAmountText(item.count, dateTimeFormat: environment.dateTimeFormat, showPlus: true)
switch item.currency {
case .stars:
formattedLabel = formatStarsAmountText(item.count, dateTimeFormat: environment.dateTimeFormat, showPlus: true)
case .ton:
formattedLabel = formatTonAmountText(item.count.value, dateTimeFormat: environment.dateTimeFormat, showPlus: true)
}
let smallLabelFont = Font.with(size: floor(fontBaseDisplaySize / 17.0 * 13.0)) let smallLabelFont = Font.with(size: floor(fontBaseDisplaySize / 17.0 * 13.0))
let labelFont = Font.medium(fontBaseDisplaySize) let labelFont = Font.medium(fontBaseDisplaySize)
let labelColor = formattedLabel.hasPrefix("-") ? environment.theme.list.itemDestructiveColor : environment.theme.list.itemDisclosureActions.constructive.fillColor let labelColor = formattedLabel.hasPrefix("-") ? environment.theme.list.itemDestructiveColor : environment.theme.list.itemDisclosureActions.constructive.fillColor
itemLabel = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: environment.dateTimeFormat.decimalSeparator) itemLabel = tonAmountAttributedString(formattedLabel, integralFont: labelFont, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: environment.dateTimeFormat.decimalSeparator)
let itemIconName: String
let itemIconColor: UIColor?
switch item.count.currency {
case .stars:
itemIconName = "Premium/Stars/StarMedium"
itemIconColor = nil
case .ton:
itemIconName = "Ads/TonAbout"
itemIconColor = labelColor
}
var itemDateColor = environment.theme.list.itemSecondaryTextColor var itemDateColor = environment.theme.list.itemSecondaryTextColor
itemDate = stringForMediumCompactDate(timestamp: item.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat) itemDate = stringForMediumCompactDate(timestamp: item.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat)
if item.flags.contains(.isRefund) { if item.flags.contains(.isRefund) {
@ -502,7 +507,7 @@ final class StarsTransactionsListPanelComponent: Component {
contentInsets: UIEdgeInsets(top: 9.0, left: environment.containerInsets.left, bottom: 8.0, right: environment.containerInsets.right), contentInsets: UIEdgeInsets(top: 9.0, left: environment.containerInsets.left, bottom: 8.0, right: environment.containerInsets.right),
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: itemPeer, photo: item.photo, media: item.media, uniqueGift: uniqueGift, backgroundColor: environment.theme.list.plainBackgroundColor))), false), leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: itemPeer, photo: item.photo, media: item.media, uniqueGift: uniqueGift, backgroundColor: environment.theme.list.plainBackgroundColor))), false),
icon: nil, icon: nil,
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(theme: environment.theme, currency: item.currency, textColor: labelColor, text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))), accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel, iconName: itemIconName, iconColor: itemIconColor))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
action: { [weak self] _ in action: { [weak self] _ in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return

View File

@ -29,6 +29,11 @@ func openWebAppImpl(
skipTermsOfService: Bool, skipTermsOfService: Bool,
payload: String? payload: String?
) { ) {
if context.isFrozen {
parentController.push(context.sharedContext.makeAccountFreezeInfoScreen(context: context))
return
}
let presentationData: PresentationData let presentationData: PresentationData
if let parentController = parentController as? ChatControllerImpl { if let parentController = parentController as? ChatControllerImpl {
presentationData = parentController.presentationData presentationData = parentController.presentationData

View File

@ -3687,6 +3687,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return StarsTransactionsScreen(context: context, starsContext: starsContext) return StarsTransactionsScreen(context: context, starsContext: starsContext)
} }
public func makeTonTransactionsScreen(context: AccountContext, tonContext: StarsContext) -> ViewController {
return TonTransactionsScreen(context: context, tonContext: tonContext)
}
public func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, completion: @escaping (Int64) -> Void) -> ViewController { public func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, completion: @escaping (Int64) -> Void) -> ViewController {
return StarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: purpose, completion: completion) return StarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: purpose, completion: completion)
} }