From 4b785835ee4bb4ba680915cf32d59e442037ab66 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 27 May 2024 13:38:47 +0400 Subject: [PATCH 1/4] Various improvements --- .../Sources/PhotoResources.swift | 16 +- .../TelegramEngine/Payments/Stars.swift | 201 +++++++- .../Payments/TelegramEnginePayments.swift | 7 +- .../Sources/GiftAvatarComponent.swift | 3 +- .../Stars/StarsImageComponent/BUILD | 28 ++ .../Sources/StarsImageComponent.swift | 456 ++++++++++++++++++ .../Stars/StarsTransactionsScreen/BUILD | 1 + .../Sources/StarsTransactionScreen.swift | 24 +- .../StarsTransactionsListPanelComponent.swift | 108 +++-- ...sTransactionsPanelContainerComponent.swift | 28 +- .../Sources/StarsTransactionsScreen.swift | 96 ++-- .../Stars/StarsTransferScreen/BUILD | 2 +- .../Sources/StarsTransferScreen.swift | 35 +- .../Stars/Particle.imageset/Contents.json | 24 + .../Stars/Particle.imageset/particle.png | Bin 0 -> 701 bytes 15 files changed, 881 insertions(+), 148 deletions(-) create mode 100644 submodules/TelegramUI/Components/Stars/StarsImageComponent/BUILD create mode 100644 submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/particle.png diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 8507e3456b..84a72e4897 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -2803,14 +2803,14 @@ public func chatWebFileImage(account: Account, file: TelegramMediaWebFile) -> Si c.setBlendMode(.normal) } - } else { - context.withFlippedContext { c in - c.setBlendMode(.copy) - c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) - c.fill(arguments.drawingRect) - - c.setBlendMode(.normal) - } + } + } else { + context.withFlippedContext { c in + c.setBlendMode(.copy) + c.setFillColor((arguments.emptyColor ?? UIColor.white).cgColor) + c.fill(arguments.drawingRect) + + c.setBlendMode(.normal) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index eff0bce584..9070d1fb06 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -72,7 +72,7 @@ struct InternalStarsStatus { let nextOffset: String? } -func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, offset: String?) -> Signal { +private func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, subject: StarsTransactionsContext.Subject, offset: String?) -> Signal { return account.postbox.transaction { transaction -> Peer? in return transaction.getPeer(peerId) } |> mapToSignal { peer -> Signal in @@ -82,7 +82,16 @@ func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, offset let signal: Signal 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 { 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 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() var state: Signal { return self._statePromise.get() @@ -160,7 +169,7 @@ private final class StarsContextImpl { } 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 if let self { 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.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 if let self { 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) { self.impl.with { $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() + var state: Signal { + 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() + 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 + + public enum Subject { + case all + case incoming + case outgoing + } + + public var state: Signal { + 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 { return account.postbox.transaction { transaction -> Api.InputInvoice? in return _internal_parseInputInvoice(transaction: transaction, source: source) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index 23b0b369d4..fa68190c7c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -73,7 +73,12 @@ public extension TelegramEngine { public func peerStarsContext(peerId: EnginePeer.Id) -> StarsContext { 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 { return _internal_sendStarsPaymentForm(account: self.account, formId: formId, source: source) } diff --git a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift index d53649dbcc..42472817e7 100644 --- a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift +++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift @@ -325,6 +325,7 @@ public final class GiftAvatarComponent: Component { imageNode = current } else { imageNode = TransformImageNode() + imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] self.addSubview(imageNode.view) self.imageNode = imageNode @@ -335,7 +336,7 @@ public final class GiftAvatarComponent: Component { 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.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 } else if let starsPeer = component.starsPeer { diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/BUILD b/submodules/TelegramUI/Components/Stars/StarsImageComponent/BUILD new file mode 100644 index 0000000000..37eebaff13 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/BUILD @@ -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", + ], +) diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift new file mode 100644 index 0000000000..7ab97d7886 --- /dev/null +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift @@ -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, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD index a8c55ae20a..518c2783e2 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD @@ -39,6 +39,7 @@ swift_library( "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/AvatarNode", "//submodules/PhotoResources", + "//submodules/TelegramUI/Components/Stars/StarsImageComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift index ed1b5bb082..39b85e84d8 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift @@ -19,7 +19,7 @@ import AvatarNode import TextFormat import TelegramStringFormatting import UndoUI -import PremiumStarComponent +import StarsImageComponent private final class StarsTransactionSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -117,7 +117,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { static var body: Body { let closeButton = Child(Button.self) let title = Child(MultilineTextComponent.self) - let star = Child(GiftAvatarComponent.self) + let star = Child(StarsImageComponent.self) let amount = Child(BalancedTextComponent.self) let amountStar = Child(BundleIconComponent.self) let description = Child(MultilineTextComponent.self) @@ -249,18 +249,20 @@ private final class StarsTransactionSheetContent: CombinedComponent { 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( - component: GiftAvatarComponent( + component: StarsImageComponent( context: component.context, + subject: imageSubject, theme: theme, - peers: toPeer.flatMap { [$0] } ?? [], - photo: photo, - starsPeer: transactionPeer, - isVisible: true, - hasIdleAnimations: true, - hasScaleAnimation: false, - avatarSize: 90.0, - color: UIColor(rgb: 0xf7ab04) + diameter: 90.0 ), availableSize: CGSize(width: context.availableSize.width, height: 200.0), transition: .immediate diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift index 3b4031b2c2..09cb1227ff 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift @@ -18,50 +18,18 @@ import PhotoResources final class StarsTransactionsListPanelComponent: Component { 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 items: Items? + let transactionsContext: StarsTransactionsContext let action: (StarsContext.State.Transaction) -> Void init( context: AccountContext, - items: Items?, + transactionsContext: StarsTransactionsContext, action: @escaping (StarsContext.State.Transaction) -> Void ) { self.context = context - self.items = items + self.transactionsContext = transactionsContext self.action = action } @@ -69,9 +37,6 @@ final class StarsTransactionsListPanelComponent: Component { if lhs.context !== rhs.context { return false } - if lhs.items != rhs.items { - return false - } return true } @@ -137,6 +102,10 @@ final class StarsTransactionsListPanelComponent: Component { private var environment: StarsTransactionsPanelEnvironment? private var itemLayout: ItemLayout? + private var items: [StarsContext.State.Transaction] = [] + private var itemsDisposable: Disposable? + private var currentLoadMoreId: String? + override init(frame: CGRect) { self.scrollView = ScrollViewImpl() @@ -164,6 +133,10 @@ final class StarsTransactionsListPanelComponent: Component { fatalError("init(coder:) has not been implemented") } + deinit { + self.itemsDisposable?.dispose() + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.ignoreScrolling { self.updateScrolling(transition: .immediate) @@ -175,7 +148,7 @@ final class StarsTransactionsListPanelComponent: Component { } 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 } @@ -184,11 +157,11 @@ final class StarsTransactionsListPanelComponent: Component { var validIds = Set() if let visibleItems = itemLayout.visibleItems(for: visibleBounds) { for index in visibleItems.lowerBound ..< visibleItems.upperBound { - if index >= items.items.count { + if index >= self.items.count { continue } - let item = items.items[index] - let id = item.transaction.id + let item = self.items[index] + let id = item.id validIds.insert(id) var itemTransition = transition @@ -214,9 +187,9 @@ final class StarsTransactionsListPanelComponent: Component { let itemTitle: String let itemSubtitle: String? let itemDate: String - switch item.transaction.peer { + switch item.peer { case let .peer(peer): - if let title = item.transaction.title { + if let title = item.title { itemTitle = title itemSubtitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) } else { @@ -243,15 +216,15 @@ final class StarsTransactionsListPanelComponent: Component { let itemLabel: NSAttributedString let labelString: String - let formattedLabel = presentationStringsFormattedNumber(abs(Int32(item.transaction.count)), environment.dateTimeFormat.groupingSeparator) - if item.transaction.count < 0 { + let formattedLabel = presentationStringsFormattedNumber(abs(Int32(item.count)), environment.dateTimeFormat.groupingSeparator) + if item.count < 0 { labelString = "- \(formattedLabel)" } else { labelString = "+ \(formattedLabel)" } 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] = [] titleComponents.append( @@ -292,14 +265,14 @@ final class StarsTransactionsListPanelComponent: Component { theme: environment.theme, 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), - 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, 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 guard let self, let component = self.component else { return } - component.action(item.transaction) + component.action(item) } )), environment: {}, @@ -308,6 +281,9 @@ final class StarsTransactionsListPanelComponent: Component { let itemFrame = itemLayout.itemFrame(for: index) if let itemComponentView = itemView.view { if itemComponentView.superview == nil { + if !transition.animation.isImmediate { + transition.animateAlpha(view: itemComponentView, from: 0.0, to: 1.0) + } self.scrollView.addSubview(itemComponentView) } itemTransition.setFrame(view: itemComponentView, frame: itemFrame) @@ -338,11 +314,43 @@ final class StarsTransactionsListPanelComponent: Component { for id in removeIds { 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, transition: Transition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + 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 self.environment = environment @@ -392,7 +400,7 @@ final class StarsTransactionsListPanelComponent: Component { containerInsets: environment.containerInsets, containerWidth: availableSize.width, itemHeight: measureItemSize.height, - itemCount: component.items?.items.count ?? 0 + itemCount: self.items.count ) self.itemLayout = itemLayout diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift index 0176aca27d..2311dd02f2 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsPanelContainerComponent.swift @@ -28,19 +28,22 @@ final class StarsTransactionsPanelEnvironment: Equatable { let dateTimeFormat: PresentationDateTimeFormat let containerInsets: UIEdgeInsets let isScrollable: Bool + let isCurrent: Bool init( theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, containerInsets: UIEdgeInsets, - isScrollable: Bool + isScrollable: Bool, + isCurrent: Bool ) { self.theme = theme self.strings = strings self.dateTimeFormat = dateTimeFormat self.containerInsets = containerInsets self.isScrollable = isScrollable + self.isCurrent = isCurrent } static func ==(lhs: StarsTransactionsPanelEnvironment, rhs: StarsTransactionsPanelEnvironment) -> Bool { @@ -59,6 +62,9 @@ final class StarsTransactionsPanelEnvironment: Equatable { if lhs.isScrollable != rhs.isScrollable { return false } + if lhs.isCurrent != rhs.isCurrent { + return false + } 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)) } - - 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)) if self.animatingTransition { @@ -739,6 +737,16 @@ final class StarsTransactionsPanelContainerComponent: Component { panel = ComponentView() 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( transition: panelTransition, component: panelItem.panel, diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index bcd827e3e3..42dd11ef19 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -112,6 +112,12 @@ final class StarsTransactionsScreenComponent: Component { private var stateDisposable: Disposable? 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) { self.headerOffsetContainer = UIView() self.headerOffsetContainer.isUserInteractionEnabled = false @@ -264,9 +270,7 @@ final class StarsTransactionsScreenComponent: Component { } ) } - - private var previousBalance: Int64? - + private var isUpdating = false func update(component: StarsTransactionsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { self.isUpdating = true @@ -294,6 +298,7 @@ final class StarsTransactionsScreenComponent: Component { return } self.starsState = state + if !self.isUpdating { self.state?.updated() } @@ -545,56 +550,65 @@ final class StarsTransactionsScreenComponent: Component { contentHeight += balanceSize.height contentHeight += 44.0 - let transactions = 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) } - ) - + let initialTransactions = self.starsState?.transactions ?? [] 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( id: "all", title: environment.strings.Stars_Intro_AllTransactions, panel: AnyComponent(StarsTransactionsListPanelComponent( context: component.context, - items: allItems, + transactionsContext: allTransactionsContext, action: { transaction in component.openTransaction(transaction) } )) )) - if !outgoingItems.items.isEmpty { - panelItems.append(StarsTransactionsPanelContainerComponent.Item( - id: "incoming", - title: environment.strings.Stars_Intro_Incoming, - panel: AnyComponent(StarsTransactionsListPanelComponent( - context: component.context, - items: incomingItems, - action: { transaction in - component.openTransaction(transaction) - } - )) + panelItems.append(StarsTransactionsPanelContainerComponent.Item( + id: "incoming", + title: environment.strings.Stars_Intro_Incoming, + panel: AnyComponent(StarsTransactionsListPanelComponent( + context: component.context, + transactionsContext: incomingTransactionsContext, + action: { transaction in + component.openTransaction(transaction) + } )) - - panelItems.append(StarsTransactionsPanelContainerComponent.Item( - id: "outgoing", - title: environment.strings.Stars_Intro_Outgoing, - panel: AnyComponent(StarsTransactionsListPanelComponent( - context: component.context, - items: outgoingItems, - action: { transaction in - component.openTransaction(transaction) - } - )) + )) + + panelItems.append(StarsTransactionsPanelContainerComponent.Item( + id: "outgoing", + title: environment.strings.Stars_Intro_Outgoing, + panel: AnyComponent(StarsTransactionsListPanelComponent( + context: component.context, + transactionsContext: outgoingTransactionsContext, + action: { transaction in + component.openTransaction(transaction) + } )) - } + )) } var panelTransition = transition @@ -742,10 +756,6 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { } self.starsContext.load(force: false) - - Queue.mainQueue().after(0.5, { - self.starsContext.loadMore() - }) } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD index 87f4b01dd8..d19426478d 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD @@ -31,7 +31,7 @@ swift_library( "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListActionItemComponent", - "//submodules/TelegramUI/Components/Premium/PremiumStarComponent", + "//submodules/TelegramUI/Components/Stars/StarsImageComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index 703a3bf35b..7a3e586442 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -13,11 +13,11 @@ import BalancedTextComponent import MultilineTextComponent import BundleIconComponent import ButtonComponent -import PremiumStarComponent import ItemListUI import UndoUI import AccountContext import PresentationDataUtils +import StarsImageComponent private final class SheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -196,7 +196,7 @@ private final class SheetContent: CombinedComponent { static var body: Body { let background = Child(RoundedRectangle.self) - let star = Child(GiftAvatarComponent.self) + let star = Child(StarsImageComponent.self) let closeButton = Child(Button.self) let title = Child(Text.self) let text = Child(BalancedTextComponent.self) @@ -228,24 +228,25 @@ private final class SheetContent: CombinedComponent { ) 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( - component: GiftAvatarComponent( - context: context.component.context, - theme: environment.theme, - peers: [peer], - photo: component.invoice.photo, - isVisible: true, - hasIdleAnimations: true, - hasScaleAnimation: false, - avatarSize: 90.0, - color: UIColor(rgb: 0xf7ab04) + component: StarsImageComponent( + context: component.context, + subject: subject, + theme: theme, + diameter: 90.0 ), availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), transition: context.transition ) 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 ) let balanceIcon = balanceIcon.update( - component: BundleIconComponent(name: "Premium/Stars/StarLarge", tintColor: nil), + component: BundleIconComponent(name: "Premium/Stars/StarSmall", tintColor: nil), availableSize: context.availableSize, 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)) ) 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 - .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 { @@ -416,7 +417,7 @@ private final class SheetContent: CombinedComponent { let resultController = UndoOverlayController( presentationData: presentationData, content: .image( - image: UIImage(bundleImageName: "Premium/Stars/StarMedium")!, + image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!, 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, round: false, diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/Contents.json new file mode 100644 index 0000000000..2fc87745c7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/Contents.json @@ -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" + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/particle.png b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/particle.png new file mode 100644 index 0000000000000000000000000000000000000000..bd4f1d2524fe4eb1cc59d4af6c936ecccb161255 GIT binary patch literal 701 zcmV;u0z&0$O#;w3e5x8u^Qh3Zey0Hhc5aD`~f}z zuYu>l7Vr*uiB+y31_I`=ci0AShW$Hw7Q3s9**W9HF3<%!z-MfP^ zF{pYc{|8oTz(poAd4T1Ch3;b&qG+LU+olWyI~6Uoe%K^F$^INz$l8tUxR8AwubA8( zwgHv`@3HFC&Dv>}>=&T#ySVCFsD@4STi6I*!%F2$;F@Q^6W}fI3adUpN6x@G7yDRs z<#_6xn~BM%coM$|^gYJvel#cqbOL>EVv~(qz-DYR#S6_W@Eu$K=h4T?@}bLvqd+kN zec+=f{*}ndgYKQ!XO&bzq!O^|OXMxZ0mfw^Nd*F}fbEiYR44^>O4?DO6woedM@7}G j#SnVH3ik5 Date: Mon, 27 May 2024 13:59:55 +0400 Subject: [PATCH 2/4] Various improvements --- .../Sources/StarsTransactionsListPanelComponent.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift index 09cb1227ff..edc487f092 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift @@ -272,7 +272,9 @@ final class StarsTransactionsListPanelComponent: Component { guard let self, let component = self.component else { return } - component.action(item) + if !item.id.hasPrefix("tmp_") { + component.action(item) + } } )), environment: {}, @@ -341,12 +343,14 @@ final class StarsTransactionsListPanelComponent: Component { return } let wasEmpty = self.items.isEmpty + let hadTemporaryTransactions = self.items.contains(where: { $0.id.hasPrefix("tmp_") }) + self.items = status.transactions if !status.isLoading { self.currentLoadMoreId = nil } if !self.isUpdating { - state?.updated(transition: wasEmpty ? .immediate : .easeInOut(duration: 0.2)) + state?.updated(transition: wasEmpty || hadTemporaryTransactions ? .immediate : .easeInOut(duration: 0.2)) } }) } From f0fa9461cd2f1622b095b8b9b2a551823c4e737f Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 27 May 2024 15:51:33 +0400 Subject: [PATCH 3/4] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 +- submodules/PremiumUI/Resources/star2.scn | Bin 144889 -> 145346 bytes .../Sources/CreateGiveawayHeaderItem.swift | 1 + .../Sources/PremiumGiftCodeScreen.swift | 1 + .../Sources/PremiumIntroScreen.swift | 1 + .../TelegramEngine/Payments/Stars.swift | 7 +- .../Sources/PremiumStarComponent.swift | 79 ++++++++++++++++-- .../Sources/StarsPurchaseScreen.swift | 36 ++++---- .../Sources/StarsTransactionScreen.swift | 2 +- .../Sources/StarsTransactionsScreen.swift | 4 +- 10 files changed, 104 insertions(+), 29 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 589288e5d4..b73ecd50be 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12297,7 +12297,7 @@ Sorry for the inconvenience."; "Stars.Transfer.Balance" = "Balance"; -"Stars.Transfer.Unavailable" = "Unavailable"; +"Stars.Transfer.Unavailable" = "Sorry, no star purchases are available from your country."; "Settings.Stars" = "Your Stars"; diff --git a/submodules/PremiumUI/Resources/star2.scn b/submodules/PremiumUI/Resources/star2.scn index 0a493929a1028b87a759689bfd386a2e38059895..2ed1379f05cde0f43c5799215902a75918ad55fb 100644 GIT binary patch delta 11328 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 delta 10818 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 diff --git a/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift b/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift index 8585e0f23b..6317511c4d 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayHeaderItem.swift @@ -197,6 +197,7 @@ class CreateGiveawayHeaderItemNode: ItemListControllerHeaderItemNode { self.backgroundNode.update(size: CGSize(width: layout.size.width, height: navigationBarHeight), transition: transition) let component = AnyComponent(PremiumStarComponent( + theme: self.item.theme, isIntro: true, isVisible: true, hasIdleAnimations: true, diff --git a/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift index fb48f4a8c9..8869e2d91d 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftCodeScreen.swift @@ -267,6 +267,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent { let star = star.update( component: PremiumStarComponent( + theme: theme, isIntro: false, isVisible: true, hasIdleAnimations: true, diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 643acb0adf..7243125fe4 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -3210,6 +3210,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } else { header = star.update( component: PremiumStarComponent( + theme: environment.theme, isIntro: isIntro, isVisible: starIsVisible, hasIdleAnimations: state.hasIdleAnimations, diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 9070d1fb06..3104f479c3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -183,7 +183,7 @@ private final class StarsContextImpl { return } var transactions = state.transactions - transactions.insert(.init(id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil), at: 0) + transactions.insert(.init(id: "tmp_\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil), at: 0) self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: state.balance + balance, transactions: transactions, canLoadMore: state.canLoadMore, isLoading: state.isLoading)) } @@ -433,10 +433,13 @@ private final class StarsTransactionsContextImpl { if filteredTransactions != initialTransactions { var existingIds = Set() for transaction in self._state.transactions { - existingIds.insert(transaction.id) + if !transaction.id.hasPrefix("tmp_") { + existingIds.insert(transaction.id) + } } var updatedState = self._state + updatedState.transactions.removeAll(where: { $0.id.hasPrefix("tmp_") }) for transaction in filteredTransactions.reversed() { if !existingIds.contains(transaction.id) { updatedState.transactions.insert(transaction, at: 0) diff --git a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift index 48b2be4a04..5460e41b6c 100644 --- a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift +++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/PremiumStarComponent.swift @@ -7,6 +7,7 @@ import SceneKit import GZip import AppBundle import LegacyComponents +import TelegramPresentationData private let sceneVersion: Int = 7 @@ -69,25 +70,31 @@ public func loadCompressedScene(name: String, version: Int) -> SCNScene? { } public final class PremiumStarComponent: Component { + let theme: PresentationTheme let isIntro: Bool let isVisible: Bool let hasIdleAnimations: Bool let colors: [UIColor]? + let particleColor: UIColor? public init( + theme: PresentationTheme, isIntro: Bool, isVisible: Bool, hasIdleAnimations: Bool, - colors: [UIColor]? = nil + colors: [UIColor]? = nil, + particleColor: UIColor? = nil ) { + self.theme = theme self.isIntro = isIntro self.isVisible = isVisible self.hasIdleAnimations = hasIdleAnimations self.colors = colors + self.particleColor = particleColor } public static func ==(lhs: PremiumStarComponent, rhs: PremiumStarComponent) -> Bool { - return lhs.isIntro == rhs.isIntro && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations && lhs.colors == rhs.colors + return lhs.theme === rhs.theme && lhs.isIntro == rhs.isIntro && lhs.isVisible == rhs.isVisible && lhs.hasIdleAnimations == rhs.hasIdleAnimations && lhs.colors == rhs.colors && lhs.particleColor == rhs.particleColor } public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView { @@ -293,9 +300,10 @@ public final class PremiumStarComponent: Component { self.sceneView.scene = scene self.sceneView.delegate = self - if let node = scene.rootNode.childNode(withName: "star", recursively: false), let colors = self.component?.colors, let color = colors.first { + if let component = self.component, let node = scene.rootNode.childNode(withName: "star", recursively: false), let colors = + component.colors { node.geometry?.materials.first?.diffuse.contents = generateDiffuseTexture(colors: colors) - + let names: [String] = [ "particles_left", "particles_right", @@ -304,10 +312,63 @@ public final class PremiumStarComponent: Component { "particles_center" ] + let starNames: [String] = [ + "coins_left", + "coins_right" + ] + + if let particleColor = component.particleColor { + for name in starNames { + if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first { + particleSystem.particleIntensity = 1.0 + particleSystem.particleIntensityVariation = 0.05 + particleSystem.particleColor = particleColor + particleSystem.particleColorVariation = SCNVector4Make(0.07, 0.0, 0.1, 0.0) + node.isHidden = false + + if let propertyControllers = particleSystem.propertyControllers, let sizeController = propertyControllers[.size], let colorController = propertyControllers[.color] { + let animation = CAKeyframeAnimation() + if let existing = colorController.animation as? CAKeyframeAnimation { + animation.keyTimes = existing.keyTimes + animation.values = existing.values?.compactMap { ($0 as? UIColor)?.alpha } ?? [] + } else { + animation.values = [ 0.0, 1.0, 1.0, 0.0 ] + } + let opacityController = SCNParticlePropertyController(animation: animation) + particleSystem.propertyControllers = [ + .size: sizeController, + .opacity: opacityController + ] + } + } + } + } + for name in names { - if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first, color.rgb != 0x6a94ff { - particleSystem.particleColor = color - particleSystem.particleColorVariation = SCNVector4Make(0, 0, 0, 0) + if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first { + if let particleColor = component.particleColor { + particleSystem.particleIntensity = min(1.0, 2.0 * particleSystem.particleIntensity) + particleSystem.particleIntensityVariation = 0.05 + particleSystem.particleColor = particleColor + particleSystem.particleColorVariation = SCNVector4Make(0.1, 0.0, 0.12, 0.0) + } else { + particleSystem.particleColorVariation = SCNVector4Make(0.12, 0.03, 0.035, 0.0) + } + + if let propertyControllers = particleSystem.propertyControllers, let sizeController = propertyControllers[.size], let colorController = propertyControllers[.color] { + let animation = CAKeyframeAnimation() + if let existing = colorController.animation as? CAKeyframeAnimation { + animation.keyTimes = existing.keyTimes + animation.values = existing.values?.compactMap { ($0 as? UIColor)?.alpha } ?? [] + } else { + animation.values = [ 0.0, 1.0, 1.0, 0.0 ] + } + let opacityController = SCNParticlePropertyController(animation: animation) + particleSystem.propertyControllers = [ + .size: sizeController, + .opacity: opacityController + ] + } } } } @@ -600,6 +661,10 @@ public final class PremiumStarComponent: Component { self.setup() + if let _ = component.particleColor { + self.sceneView.backgroundColor = component.theme.list.blocksBackgroundColor + } + self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0)) if self.sceneView.superview == self { self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0) diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index 7576a44836..c028bf2aa3 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -167,7 +167,7 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { static var body: Body { let overscroll = Child(Rectangle.self) - let fade = Child(RoundedRectangle.self) +// let fade = Child(RoundedRectangle.self) let text = Child(BalancedTextComponent.self) let list = Child(VStack.self) let termsText = Child(BalancedTextComponent.self) @@ -203,21 +203,21 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { .position(CGPoint(x: overscroll.size.width / 2.0, y: -overscroll.size.height / 2.0)) ) - let fade = fade.update( - component: RoundedRectangle( - colors: [ - topBackgroundColor, - bottomBackgroundColor - ], - cornerRadius: 0.0, - gradientDirection: .vertical - ), - availableSize: CGSize(width: availableWidth, height: 300), - transition: context.transition - ) - context.add(fade - .position(CGPoint(x: fade.size.width / 2.0, y: fade.size.height / 2.0)) - ) +// let fade = fade.update( +// component: RoundedRectangle( +// colors: [ +// topBackgroundColor, +// bottomBackgroundColor +// ], +// cornerRadius: 0.0, +// gradientDirection: .vertical +// ), +// availableSize: CGSize(width: availableWidth, height: 300), +// transition: context.transition +// ) +// context.add(fade +// .position(CGPoint(x: fade.size.width / 2.0, y: fade.size.height / 2.0)) +// ) size.height += 183.0 + 10.0 + environment.navigationHeight - 56.0 @@ -724,6 +724,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { let header = star.update( component: PremiumStarComponent( + theme: environment.theme, isIntro: true, isVisible: starIsVisible, hasIdleAnimations: state.hasIdleAnimations, @@ -732,7 +733,8 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { UIColor(rgb: 0xf09903), UIColor(rgb: 0xf9b004), UIColor(rgb: 0xfdd219) - ] + ], + particleColor: UIColor(rgb: 0xf9b004) ), availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0), transition: context.transition diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift index 39b85e84d8..31d6bf2960 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionScreen.swift @@ -428,7 +428,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { ) context.add(star - .position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 - 32.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: star.size.height / 2.0 - 19.0)) ) var originY: CGFloat = 0.0 diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 42dd11ef19..fd926aae93 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -383,6 +383,7 @@ final class StarsTransactionsScreenComponent: Component { let starSize = self.starView.update( transition: .immediate, component: AnyComponent(PremiumStarComponent( + theme: environment.theme, isIntro: true, isVisible: true, hasIdleAnimations: true, @@ -391,7 +392,8 @@ final class StarsTransactionsScreenComponent: Component { UIColor(rgb: 0xf09903), UIColor(rgb: 0xf9b004), UIColor(rgb: 0xfdd219) - ] + ], + particleColor: UIColor(rgb: 0xf9b004) )), environment: {}, containerSize: CGSize(width: min(414.0, availableSize.width), height: 220.0) From fe57f8f338cf9548de2a2c1b97f090fb4c6edcee Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 27 May 2024 16:22:04 +0400 Subject: [PATCH 4/4] Various fixes --- .../TelegramEngine/Payments/Stars.swift | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 3104f479c3..cc8a2cd84a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -72,12 +72,18 @@ struct InternalStarsStatus { let nextOffset: String? } -private func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, subject: StarsTransactionsContext.Subject, offset: String?) -> Signal { +private enum RequestStarsStateError { + case generic +} + +private func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id, subject: StarsTransactionsContext.Subject, offset: String?) -> Signal { return account.postbox.transaction { transaction -> Peer? in return transaction.getPeer(peerId) - } |> mapToSignal { peer -> Signal in + } + |> castError(RequestStarsStateError.self) + |> mapToSignal { peer -> Signal in guard let peer, let inputPeer = apiInputPeer(peer) else { - return .never() + return .fail(.generic) } let signal: Signal @@ -98,7 +104,8 @@ private func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id return signal |> retryRequest - |> mapToSignal { result -> Signal in + |> castError(RequestStarsStateError.self) + |> mapToSignal { result -> Signal in return account.postbox.transaction { transaction -> InternalStarsStatus in switch result { case let .starsStatus(_, balance, history, nextOffset, chats, users): @@ -114,6 +121,7 @@ private func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id return InternalStarsStatus(balance: balance, transactions: parsedTransactions, nextOffset: nextOffset) } } + |> castError(RequestStarsStateError.self) } } } @@ -171,10 +179,18 @@ private final class StarsContextImpl { self.disposable.set((_internal_requestStarsState(account: self.account, peerId: self.peerId, subject: .all, offset: nil) |> deliverOnMainQueue).start(next: { [weak self] status in - if let self { - self.updateState(StarsContext.State(flags: [], balance: status.balance, transactions: status.transactions, canLoadMore: status.nextOffset != nil, isLoading: false)) - self.nextOffset = status.nextOffset + guard let self else { + return } + self.updateState(StarsContext.State(flags: [], balance: status.balance, transactions: status.transactions, canLoadMore: status.nextOffset != nil, isLoading: false)) + self.nextOffset = status.nextOffset + }, error: { [weak self] _ in + guard let self else { + return + } + Queue.mainQueue().after(2.5, { + self.load(force: true) + }) })) }