Various improvements

This commit is contained in:
Ilya Laktyushin 2024-05-27 13:38:47 +04:00
parent acf32bead8
commit 4b785835ee
15 changed files with 881 additions and 148 deletions

View File

@ -2803,14 +2803,14 @@ public func chatWebFileImage(account: Account, file: TelegramMediaWebFile) -> Si
c.setBlendMode(.normal) c.setBlendMode(.normal)
} }
} else { }
context.withFlippedContext { c in } else {
c.setBlendMode(.copy) context.withFlippedContext { c in
c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) c.setBlendMode(.copy)
c.fill(arguments.drawingRect) c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor)
c.fill(arguments.drawingRect)
c.setBlendMode(.normal)
} c.setBlendMode(.normal)
} }
} }

View File

@ -72,7 +72,7 @@ struct InternalStarsStatus {
let nextOffset: String? let nextOffset: String?
} }
func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, offset: String?) -> Signal<InternalStarsStatus, NoError> { private func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, subject: StarsTransactionsContext.Subject, offset: String?) -> Signal<InternalStarsStatus, NoError> {
return account.postbox.transaction { transaction -> Peer? in return account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId) return transaction.getPeer(peerId)
} |> mapToSignal { peer -> Signal<InternalStarsStatus, NoError> in } |> mapToSignal { peer -> Signal<InternalStarsStatus, NoError> in
@ -82,7 +82,16 @@ func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, offset
let signal: Signal<Api.payments.StarsStatus, MTRpcError> let signal: Signal<Api.payments.StarsStatus, MTRpcError>
if let offset { if let offset {
signal = account.network.request(Api.functions.payments.getStarsTransactions(flags: 0, peer: inputPeer, offset: offset)) var flags: Int32 = 0
switch subject {
case .incoming:
flags = 1 << 0
case .outgoing:
flags = 1 << 1
default:
break
}
signal = account.network.request(Api.functions.payments.getStarsTransactions(flags: flags, peer: inputPeer, offset: offset))
} else { } else {
signal = account.network.request(Api.functions.payments.getStarsStatus(peer: inputPeer)) signal = account.network.request(Api.functions.payments.getStarsStatus(peer: inputPeer))
} }
@ -111,9 +120,9 @@ func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, offset
private final class StarsContextImpl { private final class StarsContextImpl {
private let account: Account private let account: Account
private let peerId: EnginePeer.Id fileprivate let peerId: EnginePeer.Id
private var _state: StarsContext.State? fileprivate var _state: StarsContext.State?
private let _statePromise = Promise<StarsContext.State?>() private let _statePromise = Promise<StarsContext.State?>()
var state: Signal<StarsContext.State?, NoError> { var state: Signal<StarsContext.State?, NoError> {
return self._statePromise.get() return self._statePromise.get()
@ -160,7 +169,7 @@ private final class StarsContextImpl {
} }
self.previousLoadTimestamp = currentTimestamp self.previousLoadTimestamp = currentTimestamp
self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, offset: nil) self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, subject: .all, offset: nil)
|> deliverOnMainQueue).start(next: { [weak self] status in |> deliverOnMainQueue).start(next: { [weak self] status in
if let self { if let self {
self.updateState(StarsContext.State(flags: [], balance: status.balance, transactions: status.transactions, canLoadMore: status.nextOffset != nil, isLoading: false)) self.updateState(StarsContext.State(flags: [], balance: status.balance, transactions: status.transactions, canLoadMore: status.nextOffset != nil, isLoading: false))
@ -188,7 +197,7 @@ private final class StarsContextImpl {
self._state?.isLoading = true self._state?.isLoading = true
self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, offset: nextOffset) self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, subject: .all, offset: nextOffset)
|> deliverOnMainQueue).start(next: { [weak self] status in |> deliverOnMainQueue).start(next: { [weak self] status in
if let self { if let self {
self.updateState(StarsContext.State(flags: [], balance: status.balance, transactions: currentState.transactions + status.transactions, canLoadMore: status.nextOffset != nil, isLoading: false)) self.updateState(StarsContext.State(flags: [], balance: status.balance, transactions: currentState.transactions + status.transactions, canLoadMore: status.nextOffset != nil, isLoading: false))
@ -327,6 +336,22 @@ public final class StarsContext {
} }
} }
var peerId: EnginePeer.Id {
var peerId: EnginePeer.Id?
self.impl.syncWith { impl in
peerId = impl.peerId
}
return peerId!
}
var currentState: StarsContext.State? {
var state: StarsContext.State?
self.impl.syncWith { impl in
state = impl._state
}
return state
}
public func add(balance: Int64) { public func add(balance: Int64) {
self.impl.with { self.impl.with {
$0.add(balance: balance) $0.add(balance: balance)
@ -352,6 +377,170 @@ public final class StarsContext {
} }
} }
private final class StarsTransactionsContextImpl {
private let account: Account
private let peerId: EnginePeer.Id
private let subject: StarsTransactionsContext.Subject
private var _state: StarsTransactionsContext.State
private let _statePromise = Promise<StarsTransactionsContext.State>()
var state: Signal<StarsTransactionsContext.State, NoError> {
return self._statePromise.get()
}
private var nextOffset: String? = ""
private let disposable = MetaDisposable()
private var stateDisposable: Disposable?
init(account: Account, starsContext: StarsContext, subject: StarsTransactionsContext.Subject) {
assert(Queue.mainQueue().isCurrent())
self.account = account
self.peerId = starsContext.peerId
self.subject = subject
let currentTransactions = starsContext.currentState?.transactions ?? []
let initialTransactions: [StarsContext.State.Transaction]
switch subject {
case .all:
initialTransactions = currentTransactions
case .incoming:
initialTransactions = currentTransactions.filter { $0.count > 0 }
case .outgoing:
initialTransactions = currentTransactions.filter { $0.count < 0 }
}
self._state = StarsTransactionsContext.State(transactions: initialTransactions, canLoadMore: true, isLoading: false)
self._statePromise.set(.single(self._state))
self.stateDisposable = (starsContext.state
|> deliverOnMainQueue).start(next: { [weak self] state in
guard let self, let state else {
return
}
let currentTransactions = state.transactions
let filteredTransactions: [StarsContext.State.Transaction]
switch subject {
case .all:
filteredTransactions = currentTransactions
case .incoming:
filteredTransactions = currentTransactions.filter { $0.count > 0 }
case .outgoing:
filteredTransactions = currentTransactions.filter { $0.count < 0 }
}
if filteredTransactions != initialTransactions {
var existingIds = Set<String>()
for transaction in self._state.transactions {
existingIds.insert(transaction.id)
}
var updatedState = self._state
for transaction in filteredTransactions.reversed() {
if !existingIds.contains(transaction.id) {
updatedState.transactions.insert(transaction, at: 0)
}
}
self.updateState(updatedState)
}
})
}
deinit {
assert(Queue.mainQueue().isCurrent())
self.disposable.dispose()
self.stateDisposable?.dispose()
}
func loadMore(reload: Bool = false) {
assert(Queue.mainQueue().isCurrent())
if reload {
self.nextOffset = ""
}
guard !self._state.isLoading, let nextOffset = self.nextOffset else {
return
}
var updatedState = self._state
updatedState.isLoading = true
self.updateState(updatedState)
self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, subject: self.subject, offset: nextOffset)
|> deliverOnMainQueue).start(next: { [weak self] status in
guard let self else {
return
}
self.nextOffset = status.nextOffset
var updatedState = self._state
updatedState.transactions = nextOffset.isEmpty ? status.transactions : updatedState.transactions + status.transactions
updatedState.isLoading = false
updatedState.canLoadMore = self.nextOffset != nil
self.updateState(updatedState)
}))
}
private func updateState(_ state: StarsTransactionsContext.State) {
self._state = state
self._statePromise.set(.single(state))
}
}
public final class StarsTransactionsContext {
public struct State: Equatable {
public var transactions: [StarsContext.State.Transaction]
public var canLoadMore: Bool
public var isLoading: Bool
init(transactions: [StarsContext.State.Transaction], canLoadMore: Bool, isLoading: Bool) {
self.transactions = transactions
self.canLoadMore = canLoadMore
self.isLoading = isLoading
}
}
fileprivate let impl: QueueLocalObject<StarsTransactionsContextImpl>
public enum Subject {
case all
case incoming
case outgoing
}
public var state: Signal<StarsTransactionsContext.State, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.state.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
public func reload() {
self.impl.with {
$0.loadMore(reload: true)
}
}
public func loadMore() {
self.impl.with {
$0.loadMore()
}
}
init(account: Account, starsContext: StarsContext, subject: Subject) {
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
return StarsTransactionsContextImpl(account: account, starsContext: starsContext, subject: subject)
})
}
}
func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: BotPaymentInvoiceSource) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> { func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: BotPaymentInvoiceSource) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
return account.postbox.transaction { transaction -> Api.InputInvoice? in return account.postbox.transaction { transaction -> Api.InputInvoice? in
return _internal_parseInputInvoice(transaction: transaction, source: source) return _internal_parseInputInvoice(transaction: transaction, source: source)

View File

@ -73,7 +73,12 @@ public extension TelegramEngine {
public func peerStarsContext(peerId: EnginePeer.Id) -> StarsContext { public func peerStarsContext(peerId: EnginePeer.Id) -> StarsContext {
return StarsContext(account: self.account, peerId: peerId) return StarsContext(account: self.account, peerId: peerId)
} }
public func peerStarsTransactionsContext(starsContext: StarsContext, subject: StarsTransactionsContext.Subject) -> StarsTransactionsContext {
return StarsTransactionsContext(account: self.account, starsContext: starsContext, subject: subject)
}
public func sendStarsPaymentForm(formId: Int64, source: BotPaymentInvoiceSource) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> { public func sendStarsPaymentForm(formId: Int64, source: BotPaymentInvoiceSource) -> Signal<SendBotPaymentResult, SendBotPaymentFormError> {
return _internal_sendStarsPaymentForm(account: self.account, formId: formId, source: source) return _internal_sendStarsPaymentForm(account: self.account, formId: formId, source: source)
} }

View File

@ -325,6 +325,7 @@ public final class GiftAvatarComponent: Component {
imageNode = current imageNode = current
} else { } else {
imageNode = TransformImageNode() imageNode = TransformImageNode()
imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
self.addSubview(imageNode.view) self.addSubview(imageNode.view)
self.imageNode = imageNode self.imageNode = imageNode
@ -335,7 +336,7 @@ public final class GiftAvatarComponent: Component {
let imageSize = CGSize(width: component.avatarSize, height: component.avatarSize) let imageSize = CGSize(width: component.avatarSize, height: component.avatarSize)
imageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - imageSize.width) / 2.0), y: 113.0 - imageSize.height / 2.0), size: imageSize) imageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - imageSize.width) / 2.0), y: 113.0 - imageSize.height / 2.0), size: imageSize)
imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: imageSize.width / 2.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: imageSize.width / 2.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))()
self.avatarNode.isHidden = true self.avatarNode.isHidden = true
} else if let starsPeer = component.starsPeer { } else if let starsPeer = component.starsPeer {

View File

@ -0,0 +1,28 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "StarsImageComponent",
module_name = "StarsImageComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/Components/ViewControllerComponent",
"//submodules/TelegramPresentationData",
"//submodules/PhotoResources",
"//submodules/AvatarNode",
"//submodules/AccountContext",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,456 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import TelegramCore
import ComponentFlow
import TelegramPresentationData
import PhotoResources
import AvatarNode
import AccountContext
final class StarsParticlesView: UIView {
private struct Particle {
var trackIndex: Int
var position: CGPoint
var scale: CGFloat
var alpha: CGFloat
var direction: CGPoint
var velocity: CGFloat
var color: UIColor
var currentTime: CGFloat
var lifeTime: CGFloat
init(
trackIndex: Int,
position: CGPoint,
scale: CGFloat,
alpha: CGFloat,
direction: CGPoint,
velocity: CGFloat,
color: UIColor,
currentTime: CGFloat,
lifeTime: CGFloat
) {
self.trackIndex = trackIndex
self.position = position
self.scale = scale
self.alpha = alpha
self.direction = direction
self.velocity = velocity
self.color = color
self.currentTime = currentTime
self.lifeTime = lifeTime
}
mutating func update(deltaTime: CGFloat) {
var position = self.position
position.x += self.direction.x * self.velocity * deltaTime
position.y += self.direction.y * self.velocity * deltaTime
self.position = position
self.currentTime += deltaTime
}
}
private final class ParticleSet {
private let size: CGSize
private let large: Bool
private(set) var particles: [Particle] = []
init(size: CGSize, large: Bool, preAdvance: Bool) {
self.size = size
self.large = large
self.generateParticles(preAdvance: preAdvance)
}
private func generateParticles(preAdvance: Bool) {
let maxDirections = self.large ? 8 : 80
if self.particles.count < maxDirections {
var allTrackIndices: [Int] = Array(repeating: 0, count: maxDirections)
for i in 0 ..< maxDirections {
allTrackIndices[i] = i
}
var takenIndexCount = 0
for particle in self.particles {
allTrackIndices[particle.trackIndex] = -1
takenIndexCount += 1
}
var availableTrackIndices: [Int] = []
availableTrackIndices.reserveCapacity(maxDirections - takenIndexCount)
for index in allTrackIndices {
if index != -1 {
availableTrackIndices.append(index)
}
}
if !availableTrackIndices.isEmpty {
availableTrackIndices.shuffle()
for takeIndex in availableTrackIndices {
let directionIndex = takeIndex
var angle = (CGFloat(directionIndex % maxDirections) / CGFloat(maxDirections)) * CGFloat.pi * 2.0
var alpha = 1.0
var lifeTimeMultiplier = 1.0
var isUpOrDownSemisphere = false
if angle > CGFloat.pi / 7.0 && angle < CGFloat.pi - CGFloat.pi / 7.0 {
isUpOrDownSemisphere = true
} else if !"".isEmpty, angle > CGFloat.pi + CGFloat.pi / 7.0 && angle < 2.0 * CGFloat.pi - CGFloat.pi / 7.0 {
isUpOrDownSemisphere = true
}
if isUpOrDownSemisphere {
if CGFloat.random(in: 0.0 ... 1.0) < 0.2 {
lifeTimeMultiplier = 0.3
} else {
angle += CGFloat.random(in: 0.0 ... 1.0) > 0.5 ? CGFloat.pi / 1.6 : -CGFloat.pi / 1.6
angle += CGFloat.random(in: -0.2 ... 0.2)
lifeTimeMultiplier = 0.5
}
if self.large {
alpha = 0.0
}
}
if self.large {
angle += CGFloat.random(in: -0.5 ... 0.5)
}
let direction = CGPoint(x: cos(angle), y: sin(angle))
let velocity = self.large ? CGFloat.random(in: 15.0 ..< 20.0) : CGFloat.random(in: 20.0 ..< 35.0)
let scale = self.large ? CGFloat.random(in: 0.65 ... 0.9) : CGFloat.random(in: 0.65 ... 1.0) * 0.75
let lifeTime = (self.large ? CGFloat.random(in: 2.0 ... 3.5) : CGFloat.random(in: 0.7 ... 3.0))
var position = CGPoint(x: self.size.width / 2.0, y: self.size.height / 2.0)
var initialOffset: CGFloat = 0.5
if preAdvance {
initialOffset = CGFloat.random(in: 0.5 ... 1.0)
} else {
let p = CGFloat.random(in: 0.0 ... 1.0)
if p < 0.5 {
initialOffset = CGFloat.random(in: 0.65 ... 1.0)
} else {
initialOffset = 0.5
}
}
position.x += direction.x * initialOffset * 105.0
position.y += direction.y * initialOffset * 105.0
let largeColors: [UInt32] = [0xff9145, 0xfec007, 0xed9303]
let smallColors: [UInt32] = [0xfecc14, 0xf7ab04, 0xff9145, 0xfdda21]
let particle = Particle(
trackIndex: directionIndex,
position: position,
scale: scale,
alpha: alpha,
direction: direction,
velocity: velocity,
color: UIColor(rgb: (self.large ? largeColors : smallColors).randomElement()!),
currentTime: 0.0,
lifeTime: lifeTime * lifeTimeMultiplier
)
self.particles.append(particle)
}
}
}
}
func update(deltaTime: CGFloat) {
for i in (0 ..< self.particles.count).reversed() {
self.particles[i].update(deltaTime: deltaTime)
if self.particles[i].currentTime > self.particles[i].lifeTime {
self.particles.remove(at: i)
}
}
self.generateParticles(preAdvance: false)
}
}
private var displayLink: SharedDisplayLinkDriver.Link?
private var particleSet: ParticleSet?
private let particleImage: UIImage
private var particleLayers: [SimpleLayer] = []
private var size: CGSize?
private let large: Bool
init(size: CGSize, large: Bool) {
if large {
self.particleImage = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/PremiumIcon"), color: .white)!.withRenderingMode(.alwaysTemplate)
} else {
self.particleImage = generateTintedImage(image: UIImage(bundleImageName: "Premium/Stars/Particle"), color: .white)!.withRenderingMode(.alwaysTemplate)
}
self.large = large
super.init(frame: .zero)
self.particleSet = ParticleSet(size: size, large: large, preAdvance: true)
self.displayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] delta in
self?.update(deltaTime: CGFloat(delta))
})
}
required init?(coder: NSCoder) {
preconditionFailure()
}
fileprivate func update(size: CGSize) {
self.size = size
}
private func update(deltaTime: CGFloat) {
guard let particleSet = self.particleSet else {
return
}
particleSet.update(deltaTime: deltaTime)
for i in 0 ..< particleSet.particles.count {
let particle = particleSet.particles[i]
let particleLayer: SimpleLayer
if i < self.particleLayers.count {
particleLayer = self.particleLayers[i]
particleLayer.isHidden = false
} else {
particleLayer = SimpleLayer()
particleLayer.contents = self.particleImage.cgImage
particleLayer.bounds = CGRect(origin: CGPoint(), size: particleImage.size)
self.particleLayers.append(particleLayer)
self.layer.addSublayer(particleLayer)
}
particleLayer.layerTintColor = particle.color.cgColor
particleLayer.position = particle.position
particleLayer.opacity = Float(particle.alpha)
let particleScale = min(1.0, particle.currentTime / 0.3) * min(1.0, (particle.lifeTime - particle.currentTime) / 0.2) * particle.scale
particleLayer.transform = CATransform3DMakeScale(particleScale, particleScale, 1.0)
}
if particleSet.particles.count < self.particleLayers.count {
for i in particleSet.particles.count ..< self.particleLayers.count {
self.particleLayers[i].isHidden = true
}
}
}
}
public final class StarsImageComponent: Component {
public enum Subject: Equatable {
case none
case photo(TelegramMediaWebFile)
case transactionPeer(StarsContext.State.Transaction.Peer)
}
public let context: AccountContext
public let subject: Subject
public let theme: PresentationTheme
public let diameter: CGFloat
public init(
context: AccountContext,
subject: Subject,
theme: PresentationTheme,
diameter: CGFloat
) {
self.context = context
self.subject = subject
self.theme = theme
self.diameter = diameter
}
public static func ==(lhs: StarsImageComponent, rhs: StarsImageComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.subject != rhs.subject {
return false
}
if lhs.diameter != rhs.diameter {
return false
}
return true
}
public final class View: UIView {
private var component: StarsImageComponent?
private var smallParticlesView: StarsParticlesView?
private var largeParticlesView: StarsParticlesView?
private var imageNode: TransformImageNode?
private var avatarNode: ImageNode?
private var iconBackgroundView: UIImageView?
private var iconView: UIImageView?
private let fetchDisposable = MetaDisposable()
public override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
preconditionFailure()
}
deinit {
self.fetchDisposable.dispose()
}
func update(component: StarsImageComponent, availableSize: CGSize, transition: Transition) -> CGSize {
self.component = component
let smallParticlesView: StarsParticlesView
if let current = self.smallParticlesView {
smallParticlesView = current
} else {
smallParticlesView = StarsParticlesView(size: availableSize, large: false)
self.addSubview(smallParticlesView)
self.smallParticlesView = smallParticlesView
}
smallParticlesView.update(size: availableSize)
smallParticlesView.frame = CGRect(origin: .zero, size: availableSize)
let largeParticlesView: StarsParticlesView
if let current = self.largeParticlesView {
largeParticlesView = current
} else {
largeParticlesView = StarsParticlesView(size: availableSize, large: true)
self.addSubview(largeParticlesView)
self.largeParticlesView = largeParticlesView
}
largeParticlesView.update(size: availableSize)
largeParticlesView.frame = CGRect(origin: .zero, size: availableSize)
let imageSize = CGSize(width: component.diameter, height: component.diameter)
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - imageSize.height) / 2.0)), size: imageSize)
switch component.subject {
case .none:
break
case let .photo(photo):
let imageNode: TransformImageNode
if let current = self.imageNode {
imageNode = current
} else {
imageNode = TransformImageNode()
imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
self.addSubview(imageNode.view)
self.imageNode = imageNode
imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo))
self.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: component.context.account, userLocation: .other, image: photo).startStrict())
}
imageNode.frame = imageFrame
imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: imageSize.width / 2.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))()
case let .transactionPeer(peer):
if case let .peer(peer) = peer {
let avatarNode: ImageNode
if let current = self.avatarNode {
avatarNode = current
} else {
avatarNode = ImageNode()
avatarNode.displaysAsynchronously = false
self.addSubview(avatarNode.view)
self.avatarNode = avatarNode
avatarNode.setSignal(peerAvatarCompleteImage(account: component.context.account, peer: peer, size: imageSize, font: avatarPlaceholderFont(size: 43.0), fullSize: true))
}
avatarNode.frame = imageFrame
} else {
let iconBackgroundView: UIImageView
let iconView: UIImageView
if let currentBackground = self.iconBackgroundView, let current = self.iconView {
iconBackgroundView = currentBackground
iconView = current
} else {
iconBackgroundView = UIImageView()
iconView = UIImageView()
self.addSubview(iconBackgroundView)
self.addSubview(iconView)
self.iconBackgroundView = iconBackgroundView
self.iconView = iconView
}
var iconInset: CGFloat = 9.0
var iconOffset: CGFloat = 0.0
switch peer {
case .appStore:
iconBackgroundView.image = generateGradientFilledCircleImage(
diameter: imageSize.width,
colors: [
UIColor(rgb: 0x2a9ef1).cgColor,
UIColor(rgb: 0x72d5fd).cgColor
],
direction: .mirroredDiagonal
)
iconView.image = UIImage(bundleImageName: "Premium/Stars/Apple")
case .playMarket:
iconBackgroundView.image = generateGradientFilledCircleImage(
diameter: imageSize.width,
colors: [
UIColor(rgb: 0x54cb68).cgColor,
UIColor(rgb: 0xa0de7e).cgColor
],
direction: .mirroredDiagonal
)
iconView.image = UIImage(bundleImageName: "Premium/Stars/Google")
case .fragment:
iconBackgroundView.image = generateFilledCircleImage(
diameter: imageSize.width,
color: UIColor(rgb: 0x1b1f24)
)
iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
iconOffset = 5.0
case .premiumBot:
iconInset = 15.0
iconBackgroundView.image = generateGradientFilledCircleImage(
diameter: imageSize.width,
colors: [
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x8d77ff).cgColor,
UIColor(rgb: 0xb56eec).cgColor,
UIColor(rgb: 0xb56eec).cgColor
],
direction: .mirroredDiagonal
)
iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
case .peer, .unsupported:
iconInset = 15.0
iconBackgroundView.image = generateGradientFilledCircleImage(
diameter: imageSize.width,
colors: [
UIColor(rgb: 0xb1b1b1).cgColor,
UIColor(rgb: 0xcdcdcd).cgColor
],
direction: .mirroredDiagonal
)
iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
}
iconBackgroundView.frame = imageFrame
iconView.frame = imageFrame.insetBy(dx: iconInset, dy: iconInset).offsetBy(dx: 0.0, dy: iconOffset)
}
}
return availableSize
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}

View File

@ -39,6 +39,7 @@ swift_library(
"//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/AvatarNode", "//submodules/AvatarNode",
"//submodules/PhotoResources", "//submodules/PhotoResources",
"//submodules/TelegramUI/Components/Stars/StarsImageComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -19,7 +19,7 @@ import AvatarNode
import TextFormat import TextFormat
import TelegramStringFormatting import TelegramStringFormatting
import UndoUI import UndoUI
import PremiumStarComponent import StarsImageComponent
private final class StarsTransactionSheetContent: CombinedComponent { private final class StarsTransactionSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -117,7 +117,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
static var body: Body { static var body: Body {
let closeButton = Child(Button.self) let closeButton = Child(Button.self)
let title = Child(MultilineTextComponent.self) let title = Child(MultilineTextComponent.self)
let star = Child(GiftAvatarComponent.self) let star = Child(StarsImageComponent.self)
let amount = Child(BalancedTextComponent.self) let amount = Child(BalancedTextComponent.self)
let amountStar = Child(BundleIconComponent.self) let amountStar = Child(BundleIconComponent.self)
let description = Child(MultilineTextComponent.self) let description = Child(MultilineTextComponent.self)
@ -249,18 +249,20 @@ private final class StarsTransactionSheetContent: CombinedComponent {
transition: .immediate transition: .immediate
) )
let imageSubject: StarsImageComponent.Subject
if let photo {
imageSubject = .photo(photo)
} else if let transactionPeer {
imageSubject = .transactionPeer(transactionPeer)
} else {
imageSubject = .none
}
let star = star.update( let star = star.update(
component: GiftAvatarComponent( component: StarsImageComponent(
context: component.context, context: component.context,
subject: imageSubject,
theme: theme, theme: theme,
peers: toPeer.flatMap { [$0] } ?? [], diameter: 90.0
photo: photo,
starsPeer: transactionPeer,
isVisible: true,
hasIdleAnimations: true,
hasScaleAnimation: false,
avatarSize: 90.0,
color: UIColor(rgb: 0xf7ab04)
), ),
availableSize: CGSize(width: context.availableSize.width, height: 200.0), availableSize: CGSize(width: context.availableSize.width, height: 200.0),
transition: .immediate transition: .immediate

View File

@ -18,50 +18,18 @@ import PhotoResources
final class StarsTransactionsListPanelComponent: Component { final class StarsTransactionsListPanelComponent: Component {
typealias EnvironmentType = StarsTransactionsPanelEnvironment typealias EnvironmentType = StarsTransactionsPanelEnvironment
final class Item: Equatable {
let transaction: StarsContext.State.Transaction
init(
transaction: StarsContext.State.Transaction
) {
self.transaction = transaction
}
static func ==(lhs: Item, rhs: Item) -> Bool {
if lhs.transaction != rhs.transaction {
return false
}
return true
}
}
final class Items: Equatable {
let items: [Item]
init(items: [Item]) {
self.items = items
}
static func ==(lhs: Items, rhs: Items) -> Bool {
if lhs === rhs {
return true
}
return lhs.items == rhs.items
}
}
let context: AccountContext let context: AccountContext
let items: Items? let transactionsContext: StarsTransactionsContext
let action: (StarsContext.State.Transaction) -> Void let action: (StarsContext.State.Transaction) -> Void
init( init(
context: AccountContext, context: AccountContext,
items: Items?, transactionsContext: StarsTransactionsContext,
action: @escaping (StarsContext.State.Transaction) -> Void action: @escaping (StarsContext.State.Transaction) -> Void
) { ) {
self.context = context self.context = context
self.items = items self.transactionsContext = transactionsContext
self.action = action self.action = action
} }
@ -69,9 +37,6 @@ final class StarsTransactionsListPanelComponent: Component {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.items != rhs.items {
return false
}
return true return true
} }
@ -137,6 +102,10 @@ final class StarsTransactionsListPanelComponent: Component {
private var environment: StarsTransactionsPanelEnvironment? private var environment: StarsTransactionsPanelEnvironment?
private var itemLayout: ItemLayout? private var itemLayout: ItemLayout?
private var items: [StarsContext.State.Transaction] = []
private var itemsDisposable: Disposable?
private var currentLoadMoreId: String?
override init(frame: CGRect) { override init(frame: CGRect) {
self.scrollView = ScrollViewImpl() self.scrollView = ScrollViewImpl()
@ -164,6 +133,10 @@ final class StarsTransactionsListPanelComponent: Component {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit {
self.itemsDisposable?.dispose()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) { func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.ignoreScrolling { if !self.ignoreScrolling {
self.updateScrolling(transition: .immediate) self.updateScrolling(transition: .immediate)
@ -175,7 +148,7 @@ final class StarsTransactionsListPanelComponent: Component {
} }
private func updateScrolling(transition: Transition) { private func updateScrolling(transition: Transition) {
guard let component = self.component, let environment = self.environment, let items = component.items, let itemLayout = self.itemLayout else { guard let component = self.component, let environment = self.environment, let itemLayout = self.itemLayout else {
return return
} }
@ -184,11 +157,11 @@ final class StarsTransactionsListPanelComponent: Component {
var validIds = Set<String>() var validIds = Set<String>()
if let visibleItems = itemLayout.visibleItems(for: visibleBounds) { if let visibleItems = itemLayout.visibleItems(for: visibleBounds) {
for index in visibleItems.lowerBound ..< visibleItems.upperBound { for index in visibleItems.lowerBound ..< visibleItems.upperBound {
if index >= items.items.count { if index >= self.items.count {
continue continue
} }
let item = items.items[index] let item = self.items[index]
let id = item.transaction.id let id = item.id
validIds.insert(id) validIds.insert(id)
var itemTransition = transition var itemTransition = transition
@ -214,9 +187,9 @@ final class StarsTransactionsListPanelComponent: Component {
let itemTitle: String let itemTitle: String
let itemSubtitle: String? let itemSubtitle: String?
let itemDate: String let itemDate: String
switch item.transaction.peer { switch item.peer {
case let .peer(peer): case let .peer(peer):
if let title = item.transaction.title { if let title = item.title {
itemTitle = title itemTitle = title
itemSubtitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) itemSubtitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
} else { } else {
@ -243,15 +216,15 @@ final class StarsTransactionsListPanelComponent: Component {
let itemLabel: NSAttributedString let itemLabel: NSAttributedString
let labelString: String let labelString: String
let formattedLabel = presentationStringsFormattedNumber(abs(Int32(item.transaction.count)), environment.dateTimeFormat.groupingSeparator) let formattedLabel = presentationStringsFormattedNumber(abs(Int32(item.count)), environment.dateTimeFormat.groupingSeparator)
if item.transaction.count < 0 { if item.count < 0 {
labelString = "- \(formattedLabel)" labelString = "- \(formattedLabel)"
} else { } else {
labelString = "+ \(formattedLabel)" labelString = "+ \(formattedLabel)"
} }
itemLabel = NSAttributedString(string: labelString, font: Font.medium(fontBaseDisplaySize), textColor: labelString.hasPrefix("-") ? environment.theme.list.itemDestructiveColor : environment.theme.list.itemDisclosureActions.constructive.fillColor) itemLabel = NSAttributedString(string: labelString, font: Font.medium(fontBaseDisplaySize), textColor: labelString.hasPrefix("-") ? environment.theme.list.itemDestructiveColor : environment.theme.list.itemDisclosureActions.constructive.fillColor)
itemDate = stringForMediumCompactDate(timestamp: item.transaction.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat) itemDate = stringForMediumCompactDate(timestamp: item.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat)
var titleComponents: [AnyComponentWithIdentity<Empty>] = [] var titleComponents: [AnyComponentWithIdentity<Empty>] = []
titleComponents.append( titleComponents.append(
@ -292,14 +265,14 @@ final class StarsTransactionsListPanelComponent: Component {
theme: environment.theme, theme: environment.theme,
title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)), title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)),
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(AvatarComponent(context: component.context, theme: environment.theme, peer: item.transaction.peer, photo: item.transaction.photo))), false), leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(AvatarComponent(context: component.context, theme: environment.theme, peer: item.peer, photo: item.photo))), false),
icon: nil, icon: nil,
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(LabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))), accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(LabelComponent(text: itemLabel))), 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
} }
component.action(item.transaction) component.action(item)
} }
)), )),
environment: {}, environment: {},
@ -308,6 +281,9 @@ final class StarsTransactionsListPanelComponent: Component {
let itemFrame = itemLayout.itemFrame(for: index) let itemFrame = itemLayout.itemFrame(for: index)
if let itemComponentView = itemView.view { if let itemComponentView = itemView.view {
if itemComponentView.superview == nil { if itemComponentView.superview == nil {
if !transition.animation.isImmediate {
transition.animateAlpha(view: itemComponentView, from: 0.0, to: 1.0)
}
self.scrollView.addSubview(itemComponentView) self.scrollView.addSubview(itemComponentView)
} }
itemTransition.setFrame(view: itemComponentView, frame: itemFrame) itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
@ -338,11 +314,43 @@ final class StarsTransactionsListPanelComponent: Component {
for id in removeIds { for id in removeIds {
self.visibleItems.removeValue(forKey: id) self.visibleItems.removeValue(forKey: id)
} }
let bottomOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height)
let loadMore = bottomOffset < 100.0
if environment.isCurrent, loadMore, let lastTransaction = self.items.last {
if lastTransaction.id != self.currentLoadMoreId {
self.currentLoadMoreId = lastTransaction.id
component.transactionsContext.loadMore()
}
}
} }
private var isUpdating = false
func update(component: StarsTransactionsListPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StarsTransactionsPanelEnvironment>, transition: Transition) -> CGSize { func update(component: StarsTransactionsListPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StarsTransactionsPanelEnvironment>, transition: Transition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
self.component = component self.component = component
if self.itemsDisposable == nil {
self.itemsDisposable = (component.transactionsContext.state
|> deliverOnMainQueue).start(next: { [weak self, weak state] status in
guard let self else {
return
}
let wasEmpty = self.items.isEmpty
self.items = status.transactions
if !status.isLoading {
self.currentLoadMoreId = nil
}
if !self.isUpdating {
state?.updated(transition: wasEmpty ? .immediate : .easeInOut(duration: 0.2))
}
})
}
let environment = environment[StarsTransactionsPanelEnvironment.self].value let environment = environment[StarsTransactionsPanelEnvironment.self].value
self.environment = environment self.environment = environment
@ -392,7 +400,7 @@ final class StarsTransactionsListPanelComponent: Component {
containerInsets: environment.containerInsets, containerInsets: environment.containerInsets,
containerWidth: availableSize.width, containerWidth: availableSize.width,
itemHeight: measureItemSize.height, itemHeight: measureItemSize.height,
itemCount: component.items?.items.count ?? 0 itemCount: self.items.count
) )
self.itemLayout = itemLayout self.itemLayout = itemLayout

View File

@ -28,19 +28,22 @@ final class StarsTransactionsPanelEnvironment: Equatable {
let dateTimeFormat: PresentationDateTimeFormat let dateTimeFormat: PresentationDateTimeFormat
let containerInsets: UIEdgeInsets let containerInsets: UIEdgeInsets
let isScrollable: Bool let isScrollable: Bool
let isCurrent: Bool
init( init(
theme: PresentationTheme, theme: PresentationTheme,
strings: PresentationStrings, strings: PresentationStrings,
dateTimeFormat: PresentationDateTimeFormat, dateTimeFormat: PresentationDateTimeFormat,
containerInsets: UIEdgeInsets, containerInsets: UIEdgeInsets,
isScrollable: Bool isScrollable: Bool,
isCurrent: Bool
) { ) {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.containerInsets = containerInsets self.containerInsets = containerInsets
self.isScrollable = isScrollable self.isScrollable = isScrollable
self.isCurrent = isCurrent
} }
static func ==(lhs: StarsTransactionsPanelEnvironment, rhs: StarsTransactionsPanelEnvironment) -> Bool { static func ==(lhs: StarsTransactionsPanelEnvironment, rhs: StarsTransactionsPanelEnvironment) -> Bool {
@ -59,6 +62,9 @@ final class StarsTransactionsPanelEnvironment: Equatable {
if lhs.isScrollable != rhs.isScrollable { if lhs.isScrollable != rhs.isScrollable {
return false return false
} }
if lhs.isCurrent != rhs.isCurrent {
return false
}
return true return true
} }
} }
@ -658,15 +664,7 @@ final class StarsTransactionsPanelContainerComponent: Component {
} }
transition.setFrame(view: headerView, frame: CGRect(origin: topPanelFrame.origin.offsetBy(dx: sideInset, dy: 0.0), size: headerSize)) transition.setFrame(view: headerView, frame: CGRect(origin: topPanelFrame.origin.offsetBy(dx: sideInset, dy: 0.0), size: headerSize))
} }
let childEnvironment = StarsTransactionsPanelEnvironment(
theme: component.theme,
strings: component.strings,
dateTimeFormat: component.dateTimeFormat,
containerInsets: UIEdgeInsets(top: 0.0, left: component.insets.left, bottom: component.insets.bottom, right: component.insets.right),
isScrollable: environment.isScrollable
)
let centralPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY)) let centralPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelFrame.maxY))
if self.animatingTransition { if self.animatingTransition {
@ -739,6 +737,16 @@ final class StarsTransactionsPanelContainerComponent: Component {
panel = ComponentView() panel = ComponentView()
self.visiblePanels[panelItem.id] = panel self.visiblePanels[panelItem.id] = panel
} }
let childEnvironment = StarsTransactionsPanelEnvironment(
theme: component.theme,
strings: component.strings,
dateTimeFormat: component.dateTimeFormat,
containerInsets: UIEdgeInsets(top: 0.0, left: component.insets.left, bottom: component.insets.bottom, right: component.insets.right),
isScrollable: environment.isScrollable,
isCurrent: self.currentId == panelItem.id
)
let _ = panel.update( let _ = panel.update(
transition: panelTransition, transition: panelTransition,
component: panelItem.panel, component: panelItem.panel,

View File

@ -112,6 +112,12 @@ final class StarsTransactionsScreenComponent: Component {
private var stateDisposable: Disposable? private var stateDisposable: Disposable?
private var starsState: StarsContext.State? private var starsState: StarsContext.State?
private var previousBalance: Int64?
private var allTransactionsContext: StarsTransactionsContext?
private var incomingTransactionsContext: StarsTransactionsContext?
private var outgoingTransactionsContext: StarsTransactionsContext?
override init(frame: CGRect) { override init(frame: CGRect) {
self.headerOffsetContainer = UIView() self.headerOffsetContainer = UIView()
self.headerOffsetContainer.isUserInteractionEnabled = false self.headerOffsetContainer.isUserInteractionEnabled = false
@ -264,9 +270,7 @@ final class StarsTransactionsScreenComponent: Component {
} }
) )
} }
private var previousBalance: Int64?
private var isUpdating = false private var isUpdating = false
func update(component: StarsTransactionsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize { func update(component: StarsTransactionsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
self.isUpdating = true self.isUpdating = true
@ -294,6 +298,7 @@ final class StarsTransactionsScreenComponent: Component {
return return
} }
self.starsState = state self.starsState = state
if !self.isUpdating { if !self.isUpdating {
self.state?.updated() self.state?.updated()
} }
@ -545,56 +550,65 @@ final class StarsTransactionsScreenComponent: Component {
contentHeight += balanceSize.height contentHeight += balanceSize.height
contentHeight += 44.0 contentHeight += 44.0
let transactions = self.starsState?.transactions ?? [] let initialTransactions = self.starsState?.transactions ?? []
let allItems = StarsTransactionsListPanelComponent.Items(
items: transactions.map { StarsTransactionsListPanelComponent.Item(transaction: $0) }
)
let incomingItems = StarsTransactionsListPanelComponent.Items(
items: transactions.filter { $0.count > 0 }.map { StarsTransactionsListPanelComponent.Item(transaction: $0) }
)
let outgoingItems = StarsTransactionsListPanelComponent.Items(
items: transactions.filter { $0.count < 0 }.map { StarsTransactionsListPanelComponent.Item(transaction: $0) }
)
var panelItems: [StarsTransactionsPanelContainerComponent.Item] = [] var panelItems: [StarsTransactionsPanelContainerComponent.Item] = []
if !allItems.items.isEmpty { if !initialTransactions.isEmpty {
let allTransactionsContext: StarsTransactionsContext
if let current = self.allTransactionsContext {
allTransactionsContext = current
} else {
allTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(starsContext: component.starsContext, subject: .all)
}
let incomingTransactionsContext: StarsTransactionsContext
if let current = self.incomingTransactionsContext {
incomingTransactionsContext = current
} else {
incomingTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(starsContext: component.starsContext, subject: .incoming)
}
let outgoingTransactionsContext: StarsTransactionsContext
if let current = self.outgoingTransactionsContext {
outgoingTransactionsContext = current
} else {
outgoingTransactionsContext = component.context.engine.payments.peerStarsTransactionsContext(starsContext: component.starsContext, subject: .outgoing)
}
panelItems.append(StarsTransactionsPanelContainerComponent.Item( panelItems.append(StarsTransactionsPanelContainerComponent.Item(
id: "all", id: "all",
title: environment.strings.Stars_Intro_AllTransactions, title: environment.strings.Stars_Intro_AllTransactions,
panel: AnyComponent(StarsTransactionsListPanelComponent( panel: AnyComponent(StarsTransactionsListPanelComponent(
context: component.context, context: component.context,
items: allItems, transactionsContext: allTransactionsContext,
action: { transaction in action: { transaction in
component.openTransaction(transaction) component.openTransaction(transaction)
} }
)) ))
)) ))
if !outgoingItems.items.isEmpty { panelItems.append(StarsTransactionsPanelContainerComponent.Item(
panelItems.append(StarsTransactionsPanelContainerComponent.Item( id: "incoming",
id: "incoming", title: environment.strings.Stars_Intro_Incoming,
title: environment.strings.Stars_Intro_Incoming, panel: AnyComponent(StarsTransactionsListPanelComponent(
panel: AnyComponent(StarsTransactionsListPanelComponent( context: component.context,
context: component.context, transactionsContext: incomingTransactionsContext,
items: incomingItems, action: { transaction in
action: { transaction in component.openTransaction(transaction)
component.openTransaction(transaction) }
}
))
)) ))
))
panelItems.append(StarsTransactionsPanelContainerComponent.Item(
id: "outgoing", panelItems.append(StarsTransactionsPanelContainerComponent.Item(
title: environment.strings.Stars_Intro_Outgoing, id: "outgoing",
panel: AnyComponent(StarsTransactionsListPanelComponent( title: environment.strings.Stars_Intro_Outgoing,
context: component.context, panel: AnyComponent(StarsTransactionsListPanelComponent(
items: outgoingItems, context: component.context,
action: { transaction in transactionsContext: outgoingTransactionsContext,
component.openTransaction(transaction) action: { transaction in
} component.openTransaction(transaction)
)) }
)) ))
} ))
} }
var panelTransition = transition var panelTransition = transition
@ -742,10 +756,6 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
} }
self.starsContext.load(force: false) self.starsContext.load(force: false)
Queue.mainQueue().after(0.5, {
self.starsContext.loadMore()
})
} }
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {

View File

@ -31,7 +31,7 @@ swift_library(
"//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/TelegramUI/Components/Premium/PremiumStarComponent", "//submodules/TelegramUI/Components/Stars/StarsImageComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -13,11 +13,11 @@ import BalancedTextComponent
import MultilineTextComponent import MultilineTextComponent
import BundleIconComponent import BundleIconComponent
import ButtonComponent import ButtonComponent
import PremiumStarComponent
import ItemListUI import ItemListUI
import UndoUI import UndoUI
import AccountContext import AccountContext
import PresentationDataUtils import PresentationDataUtils
import StarsImageComponent
private final class SheetContent: CombinedComponent { private final class SheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -196,7 +196,7 @@ private final class SheetContent: CombinedComponent {
static var body: Body { static var body: Body {
let background = Child(RoundedRectangle.self) let background = Child(RoundedRectangle.self)
let star = Child(GiftAvatarComponent.self) let star = Child(StarsImageComponent.self)
let closeButton = Child(Button.self) let closeButton = Child(Button.self)
let title = Child(Text.self) let title = Child(Text.self)
let text = Child(BalancedTextComponent.self) let text = Child(BalancedTextComponent.self)
@ -228,24 +228,25 @@ private final class SheetContent: CombinedComponent {
) )
if let peer = state.peer { if let peer = state.peer {
let subject: StarsImageComponent.Subject
if let photo = component.invoice.photo {
subject = .photo(photo)
} else {
subject = .transactionPeer(.peer(peer))
}
let star = star.update( let star = star.update(
component: GiftAvatarComponent( component: StarsImageComponent(
context: context.component.context, context: component.context,
theme: environment.theme, subject: subject,
peers: [peer], theme: theme,
photo: component.invoice.photo, diameter: 90.0
isVisible: true,
hasIdleAnimations: true,
hasScaleAnimation: false,
avatarSize: 90.0,
color: UIColor(rgb: 0xf7ab04)
), ),
availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0),
transition: context.transition transition: context.transition
) )
context.add(star context.add(star
.position(CGPoint(x: context.availableSize.width / 2.0, y: 0.0 + star.size.height / 2.0 - 30.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 - 27.0))
) )
} }
@ -342,7 +343,7 @@ private final class SheetContent: CombinedComponent {
transition: .immediate transition: .immediate
) )
let balanceIcon = balanceIcon.update( let balanceIcon = balanceIcon.update(
component: BundleIconComponent(name: "Premium/Stars/StarLarge", tintColor: nil), component: BundleIconComponent(name: "Premium/Stars/StarSmall", tintColor: nil),
availableSize: context.availableSize, availableSize: context.availableSize,
transition: .immediate transition: .immediate
) )
@ -352,10 +353,10 @@ private final class SheetContent: CombinedComponent {
.position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceTitle.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height / 2.0)) .position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceTitle.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height / 2.0))
) )
context.add(balanceIcon context.add(balanceIcon
.position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceIcon.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 - UIScreenPixel)) .position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceIcon.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 + 1.0 + UIScreenPixel))
) )
context.add(balanceValue context.add(balanceValue
.position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceIcon.size.width + 3.0 + balanceValue.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0)) .position(CGPoint(x: 16.0 + environment.safeInsets.left + balanceIcon.size.width + 3.0 + balanceValue.size.width / 2.0, y: topBalanceOriginY + balanceTitle.size.height + balanceValue.size.height / 2.0 + 2.0 - UIScreenPixel))
) )
if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme { if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme {
@ -416,7 +417,7 @@ private final class SheetContent: CombinedComponent {
let resultController = UndoOverlayController( let resultController = UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .image( content: .image(
image: UIImage(bundleImageName: "Premium/Stars/StarMedium")!, image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!,
title: presentationData.strings.Stars_Transfer_PurchasedTitle, title: presentationData.strings.Stars_Transfer_PurchasedTitle,
text: presentationData.strings.Stars_Transfer_PurchasedText(invoice.title, botTitle, presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string, text: presentationData.strings.Stars_Transfer_PurchasedText(invoice.title, botTitle, presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string,
round: false, round: false,

View File

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "particle.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B