Merge commit '17140bf9616ce2aca148d07c36b72577012aa3ab'

This commit is contained in:
Isaac 2025-07-28 11:55:59 +02:00
commit 200b86a384
9 changed files with 186 additions and 66 deletions

View File

@ -34,6 +34,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
public let handleSpoilers: Bool public let handleSpoilers: Bool
public let manualVisibilityControl: Bool public let manualVisibilityControl: Bool
public let resetAnimationsOnVisibilityChange: Bool public let resetAnimationsOnVisibilityChange: Bool
public let displaysAsynchronously: Bool
public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)?
public let tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? public let tapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
public let longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? public let longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
@ -58,6 +59,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
handleSpoilers: Bool = false, handleSpoilers: Bool = false,
manualVisibilityControl: Bool = false, manualVisibilityControl: Bool = false,
resetAnimationsOnVisibilityChange: Bool = false, resetAnimationsOnVisibilityChange: Bool = false,
displaysAsynchronously: Bool = true,
highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil, highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil,
tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil, tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil,
longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil
@ -82,6 +84,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
self.handleSpoilers = handleSpoilers self.handleSpoilers = handleSpoilers
self.manualVisibilityControl = manualVisibilityControl self.manualVisibilityControl = manualVisibilityControl
self.resetAnimationsOnVisibilityChange = resetAnimationsOnVisibilityChange self.resetAnimationsOnVisibilityChange = resetAnimationsOnVisibilityChange
self.displaysAsynchronously = displaysAsynchronously
self.tapAction = tapAction self.tapAction = tapAction
self.longTapAction = longTapAction self.longTapAction = longTapAction
} }
@ -120,6 +123,9 @@ public final class MultilineTextWithEntitiesComponent: Component {
if lhs.resetAnimationsOnVisibilityChange != rhs.resetAnimationsOnVisibilityChange { if lhs.resetAnimationsOnVisibilityChange != rhs.resetAnimationsOnVisibilityChange {
return false return false
} }
if lhs.displaysAsynchronously != rhs.displaysAsynchronously {
return false
}
if let lhsTextShadowColor = lhs.textShadowColor, let rhsTextShadowColor = rhs.textShadowColor { if let lhsTextShadowColor = lhs.textShadowColor, let rhsTextShadowColor = rhs.textShadowColor {
if !lhsTextShadowColor.isEqual(rhsTextShadowColor) { if !lhsTextShadowColor.isEqual(rhsTextShadowColor) {
return false return false
@ -161,6 +167,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
public override init(frame: CGRect) { public override init(frame: CGRect) {
self.textNode = ImmediateTextNodeWithEntities() self.textNode = ImmediateTextNodeWithEntities()
super.init(frame: frame) super.init(frame: frame)
self.addSubview(self.textNode.view) self.addSubview(self.textNode.view)
@ -175,6 +182,8 @@ public final class MultilineTextWithEntitiesComponent: Component {
} }
public func update(component: MultilineTextWithEntitiesComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { public func update(component: MultilineTextWithEntitiesComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
self.textNode.displaysAsynchronously = component.displaysAsynchronously
let attributedString: NSAttributedString let attributedString: NSAttributedString
switch component.text { switch component.text {
case let .plain(string): case let .plain(string):

View File

@ -618,7 +618,7 @@ private func autosaveLabelAndValue(presentationData: PresentationData, settings:
return (label, value) return (label, value)
} }
private func dataAndStorageControllerEntries(state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData, defaultWebBrowser: String, contentSettingsConfiguration: ContentSettingsConfiguration?, networkUsage: Int64, storageUsage: Int64, mediaAutoSaveSettings: MediaAutoSaveSettings, autosaveExceptionPeers: [EnginePeer.Id: EnginePeer?], mediaSettings: MediaDisplaySettings, showSensitiveContentSetting: Bool) -> [DataAndStorageEntry] { private func dataAndStorageControllerEntries(context: AccountContext, state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData, defaultWebBrowser: String, contentSettingsConfiguration: ContentSettingsConfiguration?, networkUsage: Int64, storageUsage: Int64, mediaAutoSaveSettings: MediaAutoSaveSettings, autosaveExceptionPeers: [EnginePeer.Id: EnginePeer?], mediaSettings: MediaDisplaySettings, showSensitiveContentSetting: Bool) -> [DataAndStorageEntry] {
var entries: [DataAndStorageEntry] = [] var entries: [DataAndStorageEntry] = []
entries.append(.storageUsage(presentationData.theme, presentationData.strings.ChatSettings_Cache, dataSizeString(storageUsage, formatting: DataSizeStringFormatting(presentationData: presentationData)))) entries.append(.storageUsage(presentationData.theme, presentationData.strings.ChatSettings_Cache, dataSizeString(storageUsage, formatting: DataSizeStringFormatting(presentationData: presentationData))))
@ -657,7 +657,7 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat
entries.append(.raiseToListen(presentationData.theme, presentationData.strings.Settings_RaiseToListen, data.mediaInputSettings.enableRaiseToSpeak)) entries.append(.raiseToListen(presentationData.theme, presentationData.strings.Settings_RaiseToListen, data.mediaInputSettings.enableRaiseToSpeak))
entries.append(.raiseToListenInfo(presentationData.theme, presentationData.strings.Settings_RaiseToListenInfo)) entries.append(.raiseToListenInfo(presentationData.theme, presentationData.strings.Settings_RaiseToListenInfo))
if let contentSettingsConfiguration = contentSettingsConfiguration, contentSettingsConfiguration.canAdjustSensitiveContent && showSensitiveContentSetting { if let contentSettingsConfiguration = contentSettingsConfiguration, contentSettingsConfiguration.canAdjustSensitiveContent && (showSensitiveContentSetting || requireAgeVerification(context: context)) {
entries.append(.sensitiveContent(presentationData.strings.Settings_SensitiveContent, contentSettingsConfiguration.sensitiveContentEnabled)) entries.append(.sensitiveContent(presentationData.strings.Settings_SensitiveContent, contentSettingsConfiguration.sensitiveContentEnabled))
entries.append(.sensitiveContentInfo(presentationData.strings.Settings_SensitiveContentInfo)) entries.append(.sensitiveContentInfo(presentationData.strings.Settings_SensitiveContentInfo))
} }
@ -685,6 +685,7 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
var pushControllerImpl: ((ViewController) -> Void)? var pushControllerImpl: ((ViewController) -> Void)?
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var presentAgeVerificationImpl: ((@escaping () -> Void) -> Void)?
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
@ -914,14 +915,18 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
updateSensitiveContentDisposable.set(updateRemoteContentSettingsConfiguration(postbox: context.account.postbox, network: context.account.network, sensitiveContentEnabled: value).start()) updateSensitiveContentDisposable.set(updateRemoteContentSettingsConfiguration(postbox: context.account.postbox, network: context.account.network, sensitiveContentEnabled: value).start())
} }
if value { if value {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } if requireAgeVerification(context: context) {
let alertController = textAlertController(context: context, title: presentationData.strings.SensitiveContent_Enable_Title, text: presentationData.strings.SensitiveContent_Enable_Text, actions: [ presentAgeVerificationImpl?(update)
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), } else {
TextAlertAction(type: .defaultAction, title: presentationData.strings.SensitiveContent_Enable_Confirm, action: { let presentationData = context.sharedContext.currentPresentationData.with { $0 }
update() let alertController = textAlertController(context: context, title: presentationData.strings.SensitiveContent_Enable_Title, text: presentationData.strings.SensitiveContent_Enable_Text, actions: [
}) TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
]) TextAlertAction(type: .defaultAction, title: presentationData.strings.SensitiveContent_Enable_Confirm, action: {
presentControllerImpl?(alertController, nil) update()
})
])
presentControllerImpl?(alertController, nil)
}
} else { } else {
update() update()
} }
@ -983,7 +988,7 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
let showSensitiveContentSetting = canAdjustSensitiveContent.with { $0 } ?? false let showSensitiveContentSetting = canAdjustSensitiveContent.with { $0 } ?? false
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataAndStorageControllerEntries(state: state, data: dataAndStorageData, presentationData: presentationData, defaultWebBrowser: defaultWebBrowser, contentSettingsConfiguration: contentSettingsConfiguration, networkUsage: usageSignal.network, storageUsage: usageSignal.storage, mediaAutoSaveSettings: mediaAutoSaveSettings, autosaveExceptionPeers: autosaveExceptionPeers, mediaSettings: mediaSettings, showSensitiveContentSetting: showSensitiveContentSetting), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: animateChanges) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataAndStorageControllerEntries(context: context, state: state, data: dataAndStorageData, presentationData: presentationData, defaultWebBrowser: defaultWebBrowser, contentSettingsConfiguration: contentSettingsConfiguration, networkUsage: usageSignal.network, storageUsage: usageSignal.storage, mediaAutoSaveSettings: mediaAutoSaveSettings, autosaveExceptionPeers: autosaveExceptionPeers, mediaSettings: mediaSettings, showSensitiveContentSetting: showSensitiveContentSetting), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: animateChanges)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {
@ -999,6 +1004,14 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
presentControllerImpl = { [weak controller] c, a in presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a) controller?.present(c, in: .window(.root), with: a)
} }
presentAgeVerificationImpl = { [weak controller] update in
guard let controller else {
return
}
presentAgeVerification(context: context, parentController: controller, completion: {
update()
})
}
return controller return controller
} }

View File

@ -4,6 +4,7 @@ import SwiftSignalKit
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import ComponentFlow import ComponentFlow
import ComponentDisplayAdapters
import Postbox import Postbox
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
@ -323,28 +324,41 @@ public class ChatMessagePaymentAlertController: AlertController {
private weak var parentNavigationController: NavigationController? private weak var parentNavigationController: NavigationController?
private let chatPeerId: EnginePeer.Id private let chatPeerId: EnginePeer.Id
private let showBalance: Bool private let showBalance: Bool
private let animateBalanceOverlay: Bool
private var didUpdateCurrency = false
public var currency: CurrencyAmount.Currency { public var currency: CurrencyAmount.Currency {
didSet { didSet {
self.didUpdateCurrency = true
if let layout = self.validLayout { if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .immediate) self.containerLayoutUpdated(layout, transition: .animated(duration: 0.25, curve: .easeInOut))
} }
} }
} }
private let balance = ComponentView<Empty>() private let balance = ComponentView<Empty>()
private var didAppear = false private var didAppear = false
private var validLayout: ContainerViewLayout? private var validLayout: ContainerViewLayout?
public init(context: AccountContext?, presentationData: PresentationData, contentNode: AlertContentNode, navigationController: NavigationController?, chatPeerId: EnginePeer.Id, showBalance: Bool = true, currency: CurrencyAmount.Currency = .stars) { public init(
context: AccountContext?,
presentationData: PresentationData,
contentNode: AlertContentNode,
navigationController: NavigationController?,
chatPeerId: EnginePeer.Id,
showBalance: Bool = true,
currency: CurrencyAmount.Currency = .stars,
animateBalanceOverlay: Bool = true
) {
self.context = context self.context = context
self.presentationData = presentationData self.presentationData = presentationData
self.parentNavigationController = navigationController self.parentNavigationController = navigationController
self.chatPeerId = chatPeerId self.chatPeerId = chatPeerId
self.showBalance = showBalance self.showBalance = showBalance
self.currency = currency self.currency = currency
self.animateBalanceOverlay = animateBalanceOverlay
super.init(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) super.init(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
@ -361,9 +375,18 @@ public class ChatMessagePaymentAlertController: AlertController {
} }
private func animateOut() { private func animateOut() {
if let view = self.balance.view { if !self.animateBalanceOverlay {
view.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false) if self.currency == .ton && self.didUpdateCurrency {
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) self.currency = .stars
}
Queue.mainQueue().after(0.39, {
})
} else {
if let view = self.balance.view {
view.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false)
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
}
} }
} }
@ -389,8 +412,13 @@ public class ChatMessagePaymentAlertController: AlertController {
if let context = self.context, let _ = self.parentNavigationController, self.showBalance { if let context = self.context, let _ = self.parentNavigationController, self.showBalance {
let insets = layout.insets(options: .statusBar) let insets = layout.insets(options: .statusBar)
var balanceTransition = ComponentTransition(transition)
if self.balance.view == nil {
balanceTransition = .immediate
}
let balanceSize = self.balance.update( let balanceSize = self.balance.update(
transition: .immediate, transition: balanceTransition,
component: AnyComponent( component: AnyComponent(
StarsBalanceOverlayComponent( StarsBalanceOverlayComponent(
context: context, context: context,
@ -401,20 +429,28 @@ public class ChatMessagePaymentAlertController: AlertController {
guard let self, let starsContext = context.starsContext, let navigationController = self.parentNavigationController else { guard let self, let starsContext = context.starsContext, let navigationController = self.parentNavigationController else {
return return
} }
switch self.currency {
case .stars:
let _ = (context.engine.payments.starsTopUpOptions()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { options in
let controller = context.sharedContext.makeStarsPurchaseScreen(
context: context,
starsContext: starsContext,
options: options,
purpose: .generic,
completion: { _ in }
)
navigationController.pushViewController(controller)
})
case .ton:
var fragmentUrl = "https://fragment.com/ads/topup"
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String {
fragmentUrl = value
}
context.sharedContext.applicationBindings.openUrl(fragmentUrl)
}
self.dismissAnimated() self.dismissAnimated()
let _ = (context.engine.payments.starsTopUpOptions()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { options in
let controller = context.sharedContext.makeStarsPurchaseScreen(
context: context,
starsContext: starsContext,
options: options,
purpose: .generic,
completion: { _ in }
)
navigationController.pushViewController(controller)
})
} }
) )
), ),
@ -425,11 +461,13 @@ public class ChatMessagePaymentAlertController: AlertController {
if view.superview == nil { if view.superview == nil {
self.view.addSubview(view) self.view.addSubview(view)
view.layer.animatePosition(from: CGPoint(x: 0.0, y: -64.0), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) if self.animateBalanceOverlay {
view.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil) view.layer.animatePosition(from: CGPoint(x: 0.0, y: -64.0), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) view.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
} }
view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - balanceSize.width) / 2.0), y: insets.top + 5.0), size: balanceSize) balanceTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - balanceSize.width) / 2.0), y: insets.top + 5.0), size: balanceSize))
} }
} }
} }

View File

@ -162,9 +162,11 @@ final class GiftStoreScreenComponent: Component {
} }
private func updateScrolling(interactive: Bool = false, transition: ComponentTransition) { private func updateScrolling(interactive: Bool = false, transition: ComponentTransition) {
guard let environment = self.environment, let component = self.component, self.state?.starGiftsState?.dataState != .loading else { guard let environment = self.environment, let component = self.component else {
return return
} }
//, self.state?.starGiftsState?.dataState != .loading
let availableWidth = self.scrollView.bounds.width let availableWidth = self.scrollView.bounds.width
let availableHeight = self.scrollView.bounds.height let availableHeight = self.scrollView.bounds.height
@ -456,7 +458,7 @@ final class GiftStoreScreenComponent: Component {
} }
let bottomContentOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height) let bottomContentOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height)
if interactive, bottomContentOffset < 1000.0 { if interactive, bottomContentOffset < 800.0 {
self.state?.starGiftsContext.loadMore() self.state?.starGiftsContext.loadMore()
} }
} }
@ -1142,7 +1144,7 @@ final class GiftStoreScreenComponent: Component {
context: component.context, context: component.context,
colors: FilterSelectorComponent.Colors( colors: FilterSelectorComponent.Colors(
foreground: theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.65), foreground: theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.65),
background: theme.list.itemSecondaryTextColor.mixedWith(theme.list.blocksBackgroundColor, alpha: 0.85) background: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15)
), ),
items: filterItems, items: filterItems,
selectedItemId: self.selectedFilterId selectedItemId: self.selectedFilterId

View File

@ -446,7 +446,8 @@ public func giftPurchaseAlertController(
gift: StarGift.UniqueGift, gift: StarGift.UniqueGift,
peer: EnginePeer, peer: EnginePeer,
navigationController: NavigationController?, navigationController: NavigationController?,
commit: @escaping (CurrencyAmount.Currency) -> Void commit: @escaping (CurrencyAmount.Currency) -> Void,
dismissed: @escaping () -> Void
) -> AlertController { ) -> AlertController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings let strings = presentationData.strings
@ -463,7 +464,19 @@ public func giftPurchaseAlertController(
contentNode = GiftPurchaseAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), presentationTheme: presentationData.theme, strings: strings, gift: gift, peer: peer, actions: actions) contentNode = GiftPurchaseAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), presentationTheme: presentationData.theme, strings: strings, gift: gift, peer: peer, actions: actions)
let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: navigationController, chatPeerId: context.account.peerId, showBalance: true, currency: gift.resellForTonOnly ? .ton : .stars) let controller = ChatMessagePaymentAlertController(
context: context,
presentationData: presentationData,
contentNode: contentNode!,
navigationController: navigationController,
chatPeerId: context.account.peerId,
showBalance: true,
currency: gift.resellForTonOnly ? .ton : .stars,
animateBalanceOverlay: false
)
controller.dismissed = { _ in
dismissed()
}
dismissImpl = { [weak controller] animated in dismissImpl = { [weak controller] animated in
if animated { if animated {

View File

@ -1367,8 +1367,9 @@ private final class GiftViewSheetContent: CombinedComponent {
) )
} }
if let buyForm = self.buyForm, let price = buyForm.invoice.prices.first?.amount {
if buyForm.invoice.currency == "XTR", let starsContext = context.starsContext, let starsState = context.starsContext?.currentState, starsState.balance < StarsAmount(value: price, nanos: 0) { if let _ = self.buyForm {
if resellAmount.currency == .stars, let starsContext = context.starsContext, let starsState = context.starsContext?.currentState, starsState.balance < resellAmount.amount {
if self.options.isEmpty { if self.options.isEmpty {
self.inProgress = true self.inProgress = true
self.updated() self.updated()
@ -1384,7 +1385,7 @@ private final class GiftViewSheetContent: CombinedComponent {
context: context, context: context,
starsContext: starsContext, starsContext: starsContext,
options: options ?? [], options: options ?? [],
purpose: .buyStarGift(requiredStars: price), purpose: .buyStarGift(requiredStars: resellAmount.amount.value),
completion: { [weak self, weak starsContext] stars in completion: { [weak self, weak starsContext] stars in
guard let self, let starsContext else { guard let self, let starsContext else {
return return
@ -1402,7 +1403,7 @@ private final class GiftViewSheetContent: CombinedComponent {
guard let self, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { guard let self, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else {
return return
} }
if starsState.balance < StarsAmount(value: price, nanos: 0) { if starsState.balance < resellAmount.amount {
self.inProgress = false self.inProgress = false
self.updated() self.updated()
@ -1416,11 +1417,11 @@ private final class GiftViewSheetContent: CombinedComponent {
) )
controller.push(purchaseController) controller.push(purchaseController)
}) })
} else if buyForm.invoice.currency == "TON", let tonState = context.tonContext?.currentState, tonState.balance < StarsAmount(value: price, nanos: 0) { } else if resellAmount.currency == .ton, let tonState = context.tonContext?.currentState, tonState.balance < resellAmount.amount {
guard let controller = self.getController() else { guard let controller = self.getController() else {
return return
} }
let needed = StarsAmount(value: price, nanos: 0) - tonState.balance let needed = resellAmount.amount - tonState.balance
var fragmentUrl = "https://fragment.com/ads/topup" var fragmentUrl = "https://fragment.com/ads/topup"
if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String { if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String {
fragmentUrl = value fragmentUrl = value
@ -1463,9 +1464,18 @@ private final class GiftViewSheetContent: CombinedComponent {
navigationController: controller.navigationController as? NavigationController, navigationController: controller.navigationController as? NavigationController,
commit: { currency in commit: { currency in
action(currency) action(currency)
},
dismissed: { [weak controller] in
if let balanceView = controller?.balanceOverlay.view {
balanceView.isHidden = false
}
} }
) )
controller.present(alertController, in: .window(.root)) controller.present(alertController, in: .window(.root))
if let balanceView = controller.balanceOverlay.view {
balanceView.isHidden = true
}
} }
}) })
} }
@ -3835,7 +3845,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
} }
fileprivate var balanceCurrency: CurrencyAmount.Currency fileprivate var balanceCurrency: CurrencyAmount.Currency
private let balanceOverlay = ComponentView<Empty>() fileprivate let balanceOverlay = ComponentView<Empty>()
fileprivate let updateSavedToProfile: ((StarGiftReference, Bool) -> Void)? fileprivate let updateSavedToProfile: ((StarGiftReference, Bool) -> Void)?
fileprivate let convertToStars: (() -> Void)? fileprivate let convertToStars: (() -> Void)?
@ -4007,20 +4017,28 @@ public class GiftViewScreen: ViewControllerComponentContainer {
guard let self, let starsContext = context.starsContext, let navigationController = self.navigationController as? NavigationController else { guard let self, let starsContext = context.starsContext, let navigationController = self.navigationController as? NavigationController else {
return return
} }
switch self.balanceCurrency {
case .stars:
let _ = (context.engine.payments.starsTopUpOptions()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { options in
let controller = context.sharedContext.makeStarsPurchaseScreen(
context: context,
starsContext: starsContext,
options: options,
purpose: .generic,
completion: { _ in }
)
navigationController.pushViewController(controller)
})
case .ton:
var fragmentUrl = "https://fragment.com/ads/topup"
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String {
fragmentUrl = value
}
context.sharedContext.applicationBindings.openUrl(fragmentUrl)
}
self.dismissAnimated() self.dismissAnimated()
let _ = (context.engine.payments.starsTopUpOptions()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { options in
let controller = context.sharedContext.makeStarsPurchaseScreen(
context: context,
starsContext: starsContext,
options: options,
purpose: .generic,
completion: { _ in }
)
navigationController.pushViewController(controller)
})
} }
) )
), ),

View File

@ -16,6 +16,7 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit", "//submodules/SSignalKit/SwiftSignalKit",
"//submodules/AccountContext", "//submodules/AccountContext",
"//submodules/ComponentFlow", "//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/MultilineTextComponent", "//submodules/Components/MultilineTextComponent",
"//submodules/Components/MultilineTextWithEntitiesComponent", "//submodules/Components/MultilineTextWithEntitiesComponent",
"//submodules/TelegramPresentationData", "//submodules/TelegramPresentationData",

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit import UIKit
import Display import Display
import ComponentFlow import ComponentFlow
import ComponentDisplayAdapters
import SwiftSignalKit import SwiftSignalKit
import TelegramCore import TelegramCore
import AccountContext import AccountContext
@ -172,7 +173,13 @@ public final class StarsBalanceOverlayComponent: Component {
if previousComponent?.currency != component.currency { if previousComponent?.currency != component.currency {
if let textView = self.text.view { if let textView = self.text.view {
textView.removeFromSuperview() if !transition.animation.isImmediate {
transition.setAlpha(view: textView, alpha: 0.0, completion: { _ in
textView.removeFromSuperview()
})
} else {
textView.removeFromSuperview()
}
} }
self.text = ComponentView() self.text = ComponentView()
} }
@ -185,7 +192,8 @@ public final class StarsBalanceOverlayComponent: Component {
animationCache: component.context.animationCache, animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer, animationRenderer: component.context.animationRenderer,
placeholderColor: .white, placeholderColor: .white,
text: .plain(attributedText) text: .plain(attributedText),
displaysAsynchronously: false
) )
), ),
environment: {}, environment: {},
@ -219,7 +227,12 @@ public final class StarsBalanceOverlayComponent: Component {
if let actionView = self.action.view { if let actionView = self.action.view {
if actionView.superview == nil { if actionView.superview == nil {
actionView.alpha = 1.0
self.backgroundView.addSubview(actionView) self.backgroundView.addSubview(actionView)
if !transition.animation.isImmediate {
transition.animateAlpha(view: actionView, from: 0.0, to: 1.0)
}
} }
actionView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - actionSize.width) / 2.0), y: 29.0), size: actionSize) actionView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - actionSize.width) / 2.0), y: 29.0), size: actionSize)
} }
@ -227,19 +240,30 @@ public final class StarsBalanceOverlayComponent: Component {
size = CGSize(width: textSize.width + 40.0, height: 35.0) size = CGSize(width: textSize.width + 40.0, height: 35.0)
if let actionView = self.action.view, actionView.superview != nil { if let actionView = self.action.view, actionView.superview != nil {
actionView.removeFromSuperview() if !transition.animation.isImmediate {
transition.setAlpha(view: actionView, alpha: 0.0, completion: { _ in
actionView.removeFromSuperview()
})
} else {
actionView.removeFromSuperview()
}
} }
} }
if let textView = self.text.view { if let textView = self.text.view {
if textView.superview == nil { if textView.superview == nil {
textView.alpha = 1.0
self.backgroundView.addSubview(textView) self.backgroundView.addSubview(textView)
if !transition.animation.isImmediate {
transition.animateAlpha(view: textView, from: 0.0, to: 1.0)
}
} }
textView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: 10.0), size: textSize) textView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: 10.0), size: textSize)
} }
self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.opaqueBackgroundColor, transition: .immediate) self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.opaqueBackgroundColor, transition: .immediate)
self.backgroundView.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate) self.backgroundView.update(size: size, cornerRadius: size.height / 2.0, transition: transition.containedViewLayoutTransition)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - size.width) / 2.0), y: 0.0), size: size)) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - size.width) / 2.0), y: 0.0), size: size))
return CGSize(width: availableSize.width, height: size.height) return CGSize(width: availableSize.width, height: size.height)

View File

@ -842,7 +842,8 @@ private final class ItemComponent: CombinedComponent {
animationCache: component.context?.animationCache, animationCache: component.context?.animationCache,
animationRenderer: component.context?.animationRenderer, animationRenderer: component.context?.animationRenderer,
placeholderColor: .white, placeholderColor: .white,
text: .plain(attributedTitle) text: .plain(attributedTitle),
displaysAsynchronously: false
), ),
availableSize: context.availableSize, availableSize: context.availableSize,
transition: .immediate transition: .immediate
@ -864,7 +865,8 @@ private final class ItemComponent: CombinedComponent {
animationCache: nil, animationCache: nil,
animationRenderer: nil, animationRenderer: nil,
placeholderColor: .white, placeholderColor: .white,
text: .plain(selectedAttributedTitle) text: .plain(selectedAttributedTitle),
displaysAsynchronously: false
), ),
availableSize: context.availableSize, availableSize: context.availableSize,
transition: .immediate transition: .immediate