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 0000000000..bd4f1d2524 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/Particle.imageset/particle.png differ