Various improvements

This commit is contained in:
Ilya Laktyushin
2025-02-22 19:17:36 +04:00
parent eecb2ef5c0
commit 8d7f9bf372
122 changed files with 3454 additions and 1121 deletions

View File

@@ -129,6 +129,7 @@ final class GiftSetupScreenComponent: Component {
private var recenterOnTag: NSObject?
private var peerMap: [EnginePeer.Id: EnginePeer] = [:]
private var sendPaidMessageStars: StarsAmount?
private var starImage: (UIImage, PresentationTheme)?
@@ -223,24 +224,29 @@ final class GiftSetupScreenComponent: Component {
return
}
switch component.subject {
case .premium:
if self.payWithStars {
case let .premium(product):
if self.payWithStars, let starsPrice = product.starsPrice, let peer = self.peerMap[component.peerId] {
//TODO:localize
let controller = textAlertController(
context: component.context,
title: "Send a Gift",
text: "Are you sure you want to gift **Telegram Premium** to Alicia for **1500 Stars**?",
actions: [
TextAlertAction(type: .genericAction, title: "Cancel", action: {}),
TextAlertAction(type: .defaultAction, title: "Confirm", action: { [weak self] in
if let self {
self.proceedWithPremiumGift()
}
})
],
parseMarkdown: true
)
environment.controller()?.present(controller, in: .window(.root))
if let balance = component.context.starsContext?.currentState?.balance, balance.value < starsPrice {
self.proceedWithStarGift()
} else {
let priceString = presentationStringsFormattedNumber(Int32(starsPrice), environment.dateTimeFormat.groupingSeparator)
let controller = textAlertController(
context: component.context,
title: "Send a Gift",
text: "Are you sure you want to gift **Telegram Premium** to \(peer.compactDisplayTitle) for **\(priceString) Stars**?",
actions: [
TextAlertAction(type: .genericAction, title: "Cancel", action: {}),
TextAlertAction(type: .defaultAction, title: "Confirm", action: { [weak self] in
if let self {
self.proceedWithStarGift()
}
})
],
parseMarkdown: true
)
environment.controller()?.present(controller, in: .window(.root))
}
} else {
self.proceedWithPremiumGift()
}
@@ -343,7 +349,7 @@ final class GiftSetupScreenComponent: Component {
}
private func proceedWithStarGift() {
guard let component = self.component, case let .starGift(starGift) = component.subject, let starsContext = component.context.starsContext, let starsState = starsContext.currentState else {
guard let component = self.component, let starsContext = component.context.starsContext, let starsState = starsContext.currentState else {
return
}
@@ -351,9 +357,24 @@ final class GiftSetupScreenComponent: Component {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let peerId = component.peerId
var finalPrice = starGift.price
if self.includeUpgrade, let upgradeStars = starGift.upgradeStars {
finalPrice += upgradeStars
let entities = generateChatInputTextEntities(self.textInputState.text)
var finalPrice: Int64
let source: BotPaymentInvoiceSource
switch component.subject {
case let .premium(product):
if let option = product.starsGiftOption {
finalPrice = option.amount
source = .premiumGift(peerId: peerId, option: option, text: self.textInputState.text.string, entities: entities)
} else {
fatalError()
}
case let .starGift(starGift):
finalPrice = starGift.price
if self.includeUpgrade, let upgradeStars = starGift.upgradeStars {
finalPrice += upgradeStars
}
source = .starGift(hideName: self.hideName, includeUpgrade: self.includeUpgrade, peerId: peerId, giftId: starGift.id, text: self.textInputState.text.string, entities: entities)
}
let proceed = { [weak self] in
@@ -364,10 +385,6 @@ final class GiftSetupScreenComponent: Component {
self.inProgress = true
self.state?.updated()
let entities = generateChatInputTextEntities(self.textInputState.text)
let source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, includeUpgrade: self.includeUpgrade, peerId: peerId, giftId: starGift.id, text: self.textInputState.text.string, entities: entities)
let completion = component.completion
let signal = BotCheckoutController.InputData.fetch(context: component.context, source: source)
@@ -384,7 +401,7 @@ final class GiftSetupScreenComponent: Component {
return
}
if peerId.namespace == Namespaces.Peer.CloudChannel {
if peerId.namespace == Namespaces.Peer.CloudChannel, case let .starGift(starGift) = component.subject {
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) }
navigationController.setViewControllers(controllers, animated: true)
@@ -544,9 +561,10 @@ final class GiftSetupScreenComponent: Component {
let _ = (component.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId),
TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId)
TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId),
TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: component.peerId)
)
|> deliverOnMainQueue).start(next: { [weak self] peer, accountPeer in
|> deliverOnMainQueue).start(next: { [weak self] peer, accountPeer, sendPaidMessageStars in
guard let self else {
return
}
@@ -556,6 +574,7 @@ final class GiftSetupScreenComponent: Component {
if let accountPeer {
self.peerMap[accountPeer.id] = accountPeer
}
self.sendPaidMessageStars = sendPaidMessageStars
self.state?.updated()
})
@@ -661,15 +680,15 @@ final class GiftSetupScreenComponent: Component {
}
)
self.optionsDisposable = (component.context.engine.payments.starsTopUpOptions()
|> deliverOnMainQueue).start(next: { [weak self] options in
guard let self else {
return
}
self.options = options
})
if case let .starGift(gift) = component.subject {
self.optionsDisposable = (component.context.engine.payments.starsTopUpOptions()
|> deliverOnMainQueue).start(next: { [weak self] options in
guard let self else {
return
}
self.options = options
})
if let _ = gift.upgradeStars {
self.previewPromise.set(
component.context.engine.payments.starGiftUpgradePreview(giftId: gift.id)
@@ -732,9 +751,6 @@ final class GiftSetupScreenComponent: Component {
let total: Int32 = availability.total
let position = CGFloat(remains) / CGFloat(total)
let sold = total - remains
//let remainsString = presentationStringsFormattedNumber(remains, environment.dateTimeFormat.groupingSeparator)
//let soldString = presentationStringsFormattedNumber(total - remains, environment.dateTimeFormat.groupingSeparator)
//let totalString = presentationStringsFormattedNumber(total, environment.dateTimeFormat.groupingSeparator)
let remainingCountSize = self.remainingCount.update(
transition: transition,
component: AnyComponent(RemainingCountComponent(
@@ -772,50 +788,53 @@ final class GiftSetupScreenComponent: Component {
var introSectionItems: [AnyComponentWithIdentity<Empty>] = []
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 346.0, tag: self.introPlaceholderTag))))
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent(
externalState: self.textInputState,
context: component.context,
theme: environment.theme,
strings: environment.strings,
initialText: "",
resetText: self.resetText.flatMap {
return ListMultilineTextFieldItemComponent.ResetText(value: $0)
},
placeholder: environment.strings.Gift_Send_Customize_MessagePlaceholder,
autocapitalizationType: .sentences,
autocorrectionType: .yes,
returnKeyType: .done,
characterLimit: Int(giftConfiguration.maxCaptionLength),
displayCharacterLimit: true,
emptyLineHandling: .notAllowed,
formatMenuAvailability: .available([.bold, .italic, .underline, .strikethrough, .spoiler]),
updated: { _ in
},
returnKeyAction: { [weak self] in
guard let self else {
return
}
if let titleView = self.introSection.findTaggedView(tag: self.textInputTag) as? ListMultilineTextFieldItemComponent.View {
titleView.endEditing(true)
}
},
textUpdateTransition: .spring(duration: 0.4),
inputMode: self.currentInputMode,
toggleInputMode: { [weak self] in
guard let self else {
return
}
switch self.currentInputMode {
case .keyboard:
self.currentInputMode = .emoji
case .emoji:
self.currentInputMode = .keyboard
}
self.state?.updated(transition: .spring(duration: 0.4))
},
tag: self.textInputTag
))))
self.resetText = nil
if self.sendPaidMessageStars == nil {
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent(
externalState: self.textInputState,
context: component.context,
theme: environment.theme,
strings: environment.strings,
initialText: "",
resetText: self.resetText.flatMap {
return ListMultilineTextFieldItemComponent.ResetText(value: $0)
},
placeholder: environment.strings.Gift_Send_Customize_MessagePlaceholder,
autocapitalizationType: .sentences,
autocorrectionType: .yes,
returnKeyType: .done,
characterLimit: Int(giftConfiguration.maxCaptionLength),
displayCharacterLimit: true,
emptyLineHandling: .notAllowed,
formatMenuAvailability: .available([.bold, .italic, .underline, .strikethrough, .spoiler]),
updated: { _ in
},
returnKeyAction: { [weak self] in
guard let self else {
return
}
if let titleView = self.introSection.findTaggedView(tag: self.textInputTag) as? ListMultilineTextFieldItemComponent.View {
titleView.endEditing(true)
}
},
textUpdateTransition: .spring(duration: 0.4),
inputMode: self.currentInputMode,
toggleInputMode: { [weak self] in
guard let self else {
return
}
switch self.currentInputMode {
case .keyboard:
self.currentInputMode = .emoji
case .emoji:
self.currentInputMode = .keyboard
}
self.state?.updated(transition: .spring(duration: 0.4))
},
tag: self.textInputTag
))))
self.resetText = nil
}
let footerAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor),
@@ -838,19 +857,7 @@ final class GiftSetupScreenComponent: Component {
maximumNumberOfLines: 0
))
case .starGift:
//TODO:unmock
//TODO:localize
if self.textInputState.hasText {
introFooter = AnyComponent(MultilineTextComponent(
text: .markdown(
text: "**\(peerName)** charges **250 Stars** for each message. That price has been added to the cost of the gift.",
attributes: footerAttributes
),
maximumNumberOfLines: 0
))
} else {
introFooter = nil
}
introFooter = nil
}
let introSectionSize = self.introSection.update(
@@ -900,9 +907,8 @@ final class GiftSetupScreenComponent: Component {
let subject: ChatGiftPreviewItem.Subject
switch component.subject {
case let .premium(product):
if self.payWithStars {
//TODO:unmock
subject = .premium(months: product.months, amount: 1500, currency: "XTR")
if self.payWithStars, let starsPrice = product.starsPrice {
subject = .premium(months: product.months, amount: starsPrice, currency: "XTR")
} else {
let (currency, amount) = product.storeProduct?.priceCurrencyAndAmount ?? ("USD", 1)
subject = .premium(months: product.months, amount: amount, currency: currency)
@@ -917,7 +923,6 @@ final class GiftSetupScreenComponent: Component {
peers.append(peer)
}
//TODO:unmock
let introContentSize = self.introContent.update(
transition: transition,
component: AnyComponent(
@@ -960,11 +965,12 @@ final class GiftSetupScreenComponent: Component {
}
switch component.subject {
case .premium:
//TODO:unmock
//TODO:localize
if "".isEmpty {
let starsFooterRawString = "Your balance is **# 147 988**. [Get More Stars >]()"
case let .premium(product):
if let starsPrice = product.starsPrice {
let balance = component.context.starsContext?.currentState?.balance.value ?? 0
let balanceString = presentationStringsFormattedNumber(Int32(balance), environment.dateTimeFormat.groupingSeparator)
let starsFooterRawString = environment.strings.Gift_Send_PayWithStars_Info("# \(balanceString)").string
let starsFooterText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(starsFooterRawString, attributes: footerAttributes))
if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== environment.theme {
@@ -977,7 +983,8 @@ final class GiftSetupScreenComponent: Component {
starsFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: starsFooterText.string))
}
let starsAttributedText = NSMutableAttributedString(string: "Pay with #1500", font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor)
let priceString = presentationStringsFormattedNumber(Int32(starsPrice), environment.dateTimeFormat.groupingSeparator)
let starsAttributedText = NSMutableAttributedString(string: environment.strings.Gift_Send_PayWithStars("#\(priceString)").string, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor)
let range = (starsAttributedText.string as NSString).range(of: "#")
if range.location != NSNotFound {
starsAttributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range)
@@ -997,6 +1004,7 @@ final class GiftSetupScreenComponent: Component {
text: .plain(starsFooterText),
maximumNumberOfLines: 0,
highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
@@ -1005,9 +1013,18 @@ final class GiftSetupScreenComponent: Component {
}
},
tapAction: { [weak self] _, _ in
guard let _ = self else {
guard let self, let component = self.component, let controller = self.environment?.controller(), let starsContext = component.context.starsContext else {
return
}
let _ = (self.optionsPromise.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { options in
let purchaseController = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options ?? [], purpose: .generic, completion: { stars in
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
})
controller.push(purchaseController)
})
}
)),
items: [
@@ -1243,10 +1260,9 @@ final class GiftSetupScreenComponent: Component {
let buttonString: String
switch component.subject {
case let .premium(product):
if self.payWithStars {
//TODO:unmock
let amountString = presentationStringsFormattedNumber(Int32(1500), presentationData.dateTimeFormat.groupingSeparator)
buttonString = "\(environment.strings.Gift_Send_Send) # \(amountString)"
if self.payWithStars, let starsPrice = product.starsPrice {
let amountString = presentationStringsFormattedNumber(Int32(starsPrice), presentationData.dateTimeFormat.groupingSeparator)
buttonString = "\(environment.strings.Gift_Send_Send) # \(amountString)"
} else {
let amountString = product.price
buttonString = "\(environment.strings.Gift_Send_Send) \(amountString)"
@@ -1256,13 +1272,9 @@ final class GiftSetupScreenComponent: Component {
if self.includeUpgrade, let upgradePrice = starGift.upgradeStars {
finalPrice += upgradePrice
}
//TODO:unmock
if self.textInputState.hasText {
finalPrice += 250
}
let amountString = presentationStringsFormattedNumber(Int32(finalPrice), presentationData.dateTimeFormat.groupingSeparator)
let buttonTitle = isSelfGift ? environment.strings.Gift_Send_Buy : environment.strings.Gift_Send_Send
buttonString = "\(buttonTitle) # \(amountString)"
buttonString = "\(buttonTitle) # \(amountString)"
if let availability = starGift.availability, availability.remains == 0 {
buttonIsEnabled = false
}
@@ -1272,7 +1284,8 @@ final class GiftSetupScreenComponent: Component {
if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.starImage?.0 {
buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
buttonAttributedString.addAttribute(.foregroundColor, value: environment.theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string))
buttonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: buttonAttributedString.string))
buttonAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: buttonAttributedString.string))
buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string))
}
let buttonSize = self.button.update(
@@ -1744,6 +1757,7 @@ private struct GiftConfiguration {
public struct PremiumGiftProduct: Equatable {
public let giftOption: CachedPremiumGiftOption
public let starsGiftOption: CachedPremiumGiftOption?
public let storeProduct: InAppPurchaseManager.Product?
public let discount: Int?
@@ -1759,12 +1773,18 @@ public struct PremiumGiftProduct: Equatable {
return self.storeProduct?.price ?? formatCurrencyAmount(self.giftOption.amount, currency: self.giftOption.currency)
}
public var pricePerMonth: String {
return self.storeProduct?.pricePerMonth(Int(self.months)) ?? ""
public var starsPrice: Int64? {
return self.starsGiftOption?.amount
}
public init(giftOption: CachedPremiumGiftOption, storeProduct: InAppPurchaseManager.Product?, discount: Int?) {
public init(
giftOption: CachedPremiumGiftOption,
starsGiftOption: CachedPremiumGiftOption?,
storeProduct: InAppPurchaseManager.Product?,
discount: Int?
) {
self.giftOption = giftOption
self.starsGiftOption = starsGiftOption
self.storeProduct = storeProduct
self.discount = discount
}