Various improvements

This commit is contained in:
Ilya Laktyushin
2025-09-29 10:55:21 +04:00
parent d126717ec1
commit aa15e6c5fe
4 changed files with 151 additions and 16 deletions

View File

@@ -1410,6 +1410,16 @@ public struct StarGiftUpgradePreview: Equatable {
public let attributes: [StarGift.UniqueGift.Attribute] public let attributes: [StarGift.UniqueGift.Attribute]
public let prices: [Price] public let prices: [Price]
public let nextPrices: [Price] public let nextPrices: [Price]
public init(attributes: [StarGift.UniqueGift.Attribute], prices: [Price], nextPrices: [Price]) {
self.attributes = attributes
self.prices = prices
self.nextPrices = nextPrices
}
public func withAttributes(_ attributes: [StarGift.UniqueGift.Attribute]) -> StarGiftUpgradePreview {
return StarGiftUpgradePreview(attributes: attributes, prices: self.prices, nextPrices: self.nextPrices)
}
} }
func _internal_starGiftUpgradePreview(account: Account, giftId: Int64) -> Signal<StarGiftUpgradePreview?, NoError> { func _internal_starGiftUpgradePreview(account: Account, giftId: Int64) -> Signal<StarGiftUpgradePreview?, NoError> {

View File

@@ -14,6 +14,7 @@ swift_library(
"//submodules/Display", "//submodules/Display",
"//submodules/ComponentFlow", "//submodules/ComponentFlow",
"//submodules/TelegramPresentationData", "//submodules/TelegramPresentationData",
"//submodules/Components/BundleIconComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@@ -3,6 +3,7 @@ import UIKit
import Display import Display
import ComponentFlow import ComponentFlow
import TelegramPresentationData import TelegramPresentationData
import BundleIconComponent
extension ComponentTransition { extension ComponentTransition {
func animateBlur(layer: CALayer, from: CGFloat, to: CGFloat, delay: Double = 0.0, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { func animateBlur(layer: CALayer, from: CGFloat, to: CGFloat, delay: Double = 0.0, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
@@ -26,6 +27,7 @@ public final class AnimatedTextComponent: Component {
public enum Content: Equatable { public enum Content: Equatable {
case text(String) case text(String)
case number(Int, minDigits: Int) case number(Int, minDigits: Int)
case icon(String, offset: CGPoint)
} }
public var id: AnyHashable public var id: AnyHashable
@@ -147,6 +149,9 @@ public final class AnimatedTextComponent: Component {
} else { } else {
itemText = valueText.map(String.init) itemText = valueText.map(String.init)
} }
case let .icon(iconName, _):
let characterKey = CharacterKey(itemId: item.id, index: 0, value: iconName)
validKeys.append(characterKey)
} }
var index = 0 var index = 0
for character in itemText { for character in itemText {
@@ -181,13 +186,24 @@ public final class AnimatedTextComponent: Component {
} }
for item in component.items { for item in component.items {
var itemText: [String] = [] enum AnimatedTextCharacter {
case text(String)
case icon(String, CGPoint)
var value: String {
switch self {
case let .text(value), let .icon(value, _):
return value
}
}
}
var itemText: [AnimatedTextCharacter] = []
switch item.content { switch item.content {
case let .text(text): case let .text(text):
if item.isUnbreakable { if item.isUnbreakable {
itemText = [text] itemText = [.text(text)]
} else { } else {
itemText = text.map(String.init) itemText = text.map { .text(String($0)) }
} }
case let .number(value, minDigits): case let .number(value, minDigits):
var valueText: String = "\(value)" var valueText: String = "\(value)"
@@ -196,14 +212,16 @@ public final class AnimatedTextComponent: Component {
} }
if item.isUnbreakable { if item.isUnbreakable {
itemText = [valueText] itemText = [.text(valueText)]
} else { } else {
itemText = valueText.map(String.init) itemText = valueText.map { .text(String($0)) }
} }
case let .icon(iconName, offset):
itemText = [.icon(iconName, offset)]
} }
var index = 0 var index = 0
for character in itemText { for character in itemText {
let characterKey = CharacterKey(itemId: item.id, index: index, value: character) let characterKey = CharacterKey(itemId: item.id, index: index, value: character.value)
index += 1 index += 1
var characterTransition = transition var characterTransition = transition
@@ -216,17 +234,30 @@ public final class AnimatedTextComponent: Component {
self.characters[characterKey] = characterView self.characters[characterKey] = characterView
} }
let characterSize = characterView.update( let characterComponent: AnyComponent<Empty>
transition: characterTransition, var characterOffset: CGPoint = .zero
component: AnyComponent(Text( switch character {
text: String(character), case let .text(text):
characterComponent = AnyComponent(Text(
text: String(text),
font: component.font, font: component.font,
color: component.color color: component.color
)), ))
case let .icon(iconName, offset):
characterComponent = AnyComponent(BundleIconComponent(
name: iconName,
tintColor: component.color
))
characterOffset = offset
}
let characterSize = characterView.update(
transition: characterTransition,
component: characterComponent,
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width, height: 100.0) containerSize: CGSize(width: availableSize.width, height: 100.0)
) )
let characterFrame = CGRect(origin: CGPoint(x: size.width, y: 0.0), size: characterSize) let characterFrame = CGRect(origin: CGPoint(x: size.width + characterOffset.x, y: characterOffset.y), size: characterSize)
if let characterComponentView = characterView.view { if let characterComponentView = characterView.view {
var animateIn = false var animateIn = false
if characterComponentView.superview == nil { if characterComponentView.superview == nil {
@@ -358,6 +389,8 @@ public extension AnimatedTextComponent {
isUnbreakable = true isUnbreakable = true
case .number: case .number:
isUnbreakable = false isUnbreakable = false
case .icon:
isUnbreakable = true
} }
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_item_\(range.index)"), isUnbreakable: isUnbreakable, content: value)) textItems.append(AnimatedTextComponent.Item(id: AnyHashable("\(id)_item_\(range.index)"), isUnbreakable: isUnbreakable, content: value))
} }

View File

@@ -1549,9 +1549,11 @@ private final class GiftViewSheetContent: CombinedComponent {
private(set) var nextUpgradePrice: StarGiftUpgradePreview.Price? private(set) var nextUpgradePrice: StarGiftUpgradePreview.Price?
func upgradePreviewTimerTick() { func upgradePreviewTimerTick() {
guard let upgradePreview = self.upgradePreview else { guard let upgradePreview = self.upgradePreview, let gift = self.subject.arguments?.gift, case let .generic(gift) = gift else {
return return
} }
let context = self.context
var transition: ComponentTransition = .immediate
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if let currentPrice = self.effectiveUpgradePrice { if let currentPrice = self.effectiveUpgradePrice {
if let price = upgradePreview.nextPrices.reversed().first(where: { currentTime >= $0.date }) { if let price = upgradePreview.nextPrices.reversed().first(where: { currentTime >= $0.date }) {
@@ -1559,7 +1561,20 @@ private final class GiftViewSheetContent: CombinedComponent {
self.effectiveUpgradePrice = price self.effectiveUpgradePrice = price
if let nextPrice = upgradePreview.nextPrices.first(where: { $0.stars < price.stars }) { if let nextPrice = upgradePreview.nextPrices.first(where: { $0.stars < price.stars }) {
self.nextUpgradePrice = nextPrice self.nextUpgradePrice = nextPrice
} else {
transition = .spring(duration: 0.4)
self.nextUpgradePrice = nil
} }
if upgradePreview.nextPrices[upgradePreview.nextPrices.count - 2] == price {
self.upgradePreviewDisposable.add((context.engine.payments.starGiftUpgradePreview(giftId: gift.id)
|> deliverOnMainQueue).start(next: { [weak self] nextUpgradePreview in
guard let self, let nextUpgradePreview else {
return
}
self.upgradePreview = nextUpgradePreview.withAttributes(upgradePreview.attributes)
}))
}
self.fetchUpgradeForm() self.fetchUpgradeForm()
} }
} else { } else {
@@ -1573,7 +1588,7 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
} }
self.updated() self.updated(transition: transition)
} }
func requestUpgradePreview() { func requestUpgradePreview() {
@@ -4483,7 +4498,15 @@ private final class GiftViewSheetContent: CombinedComponent {
var buttonTitleItems: [AnyComponentWithIdentity<Empty>] = [] var buttonTitleItems: [AnyComponentWithIdentity<Empty>] = []
var upgradeString = strings.Gift_Upgrade_Upgrade var upgradeString = strings.Gift_Upgrade_Upgrade
if !incoming { if !incoming {
if let gift = state.starGiftsMap[giftId], let upgradeStars = gift.upgradeStars { let upgradeStars: Int64?
if let stars = state.effectiveUpgradePrice?.stars {
upgradeStars = stars
} else if let gift = state.starGiftsMap[giftId], let stars = gift.upgradeStars {
upgradeStars = stars
} else {
upgradeStars = nil
}
if let upgradeStars {
let priceString = presentationStringsFormattedNumber(Int32(clamping: upgradeStars), environment.dateTimeFormat.groupingSeparator) let priceString = presentationStringsFormattedNumber(Int32(clamping: upgradeStars), environment.dateTimeFormat.groupingSeparator)
upgradeString = strings.Gift_Upgrade_GiftUpgrade(" # \(priceString)").string upgradeString = strings.Gift_Upgrade_GiftUpgrade(" # \(priceString)").string
} }
@@ -4507,7 +4530,75 @@ private final class GiftViewSheetContent: CombinedComponent {
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let upgradeTimeout = nextUpgradePrice.date - currentTime let upgradeTimeout = nextUpgradePrice.date - currentTime
buttonTitleItems.append(AnyComponentWithIdentity(id: "static_label", component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))))) if let hashIndex = buttonTitle.firstIndex(of: "#") {
var buttonAnimatedTitleItems: [AnimatedTextComponent.Item] = []
var prefix = String(buttonTitle[..<hashIndex])
if !prefix.isEmpty {
prefix.removeLast()
buttonAnimatedTitleItems.append(
AnimatedTextComponent.Item(
id: AnyHashable(buttonAnimatedTitleItems.count),
content: .text(prefix)
)
)
}
buttonAnimatedTitleItems.append(
AnimatedTextComponent.Item(
id: AnyHashable(buttonAnimatedTitleItems.count),
content: .icon("Item List/PremiumIcon", offset: CGPoint(x: 1.0, y: 2.0 + UIScreenPixel))
)
)
let suffixStart = buttonTitle.index(after: hashIndex)
let suffix = buttonTitle[suffixStart...]
var i = suffix.startIndex
while i < suffix.endIndex {
if suffix[i].isNumber {
var j = i
while j < suffix.endIndex, suffix[j].isNumber {
j = suffix.index(after: j)
}
if let value = Int(suffix[i..<j]) {
buttonAnimatedTitleItems.append(
AnimatedTextComponent.Item(
id: AnyHashable(buttonAnimatedTitleItems.count),
content: .number(value, minDigits: 1)
)
)
}
i = j
} else {
var j = i
while j < suffix.endIndex, !suffix[j].isNumber {
j = suffix.index(after: j)
}
let textRun = String(suffix[i..<j])
if !textRun.isEmpty {
buttonAnimatedTitleItems.append(
AnimatedTextComponent.Item(
id: AnyHashable(buttonAnimatedTitleItems.count),
content: .text(textRun)
)
)
}
i = j
}
}
buttonTitleItems.append(AnyComponentWithIdentity(id: "animated_label", component: AnyComponent(AnimatedTextComponent(
font: Font.with(size: 17.0, weight: .semibold, traits: .monospacedNumbers),
color: theme.list.itemCheckColors.foregroundColor,
items: buttonAnimatedTitleItems,
noDelay: true,
blur: true
))))
} else {
buttonTitleItems.append(AnyComponentWithIdentity(id: "static_label", component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))))
}
let minutes = Int(upgradeTimeout / 60) let minutes = Int(upgradeTimeout / 60)
let seconds = Int(upgradeTimeout % 60) let seconds = Int(upgradeTimeout % 60)