mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
acf32bead8
commit
4b785835ee
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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",
|
||||||
|
],
|
||||||
|
)
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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) {
|
||||||
|
@ -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",
|
||||||
|
@ -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,
|
||||||
|
24
submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/Contents.json
vendored
Normal file
24
submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/Contents.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/particle.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/particle.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 701 B |
Loading…
x
Reference in New Issue
Block a user