Various fixes

This commit is contained in:
Ilya Laktyushin 2025-02-14 21:44:19 +04:00
parent fd38d2ea9b
commit c0184d4114
7 changed files with 177 additions and 140 deletions

View File

@ -13801,3 +13801,10 @@ Sorry for the inconvenience.";
"Notification.StarsGift.TransferToChannel" = "%@ transferred a unique collectible to %@"; "Notification.StarsGift.TransferToChannel" = "%@ transferred a unique collectible to %@";
"Notification.StarsGift.TransferToChannelYou" = "You transferred a unique collectible to %@"; "Notification.StarsGift.TransferToChannelYou" = "You transferred a unique collectible to %@";
"Gift.Convert.Success.ChannelText" = "**%1$@** were sent to channel's balance.";
"Gift.Convert.Success.ChannelText.Stars_1" = "%@ Star";
"Gift.Convert.Success.ChannelText.Stars_any" = "%@ Stars";
"Stars.Transfer.Terms" = "By purchasing you agree to the [Terms of Service]().";
"Stars.Transfer.Terms_URL" = "https://telegram.org/tos/stars";

View File

@ -962,7 +962,11 @@
[_coverImageCache setImage:image forKey:itemId attributes:NULL]; [_coverImageCache setImage:image forKey:itemId attributes:NULL];
_coverImagePipe.sink([TGMediaImageUpdate imageUpdateWithItem:item representation:image]); _coverImagePipe.sink([TGMediaImageUpdate imageUpdateWithItem:item representation:image]);
[_coverPositions setObject:position forKey:itemId]; if (position != nil) {
[_coverPositions setObject:position forKey:itemId];
} else {
[_coverPositions removeObjectForKey:itemId];
}
} }
- (void)setFullSizeImage:(UIImage *)image forItem:(id<TGMediaEditableItem>)item - (void)setFullSizeImage:(UIImage *)image forItem:(id<TGMediaEditableItem>)item

View File

@ -355,13 +355,13 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
let signal: Signal<([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)?, NoError> let signal: Signal<([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)?, NoError>
if !text.isEmpty { if !text.isEmpty {
let context = self.context let context = self.context
let stickers: Signal<[(String?, FoundStickerItem)], NoError> = Signal { subscriber in let stickers: Signal<([(String?, FoundStickerItem)], Bool), NoError> = Signal { subscriber in
var signals: Signal<[Signal<(String?, [FoundStickerItem]), NoError>], NoError> = .single([]) var signals: Signal<[Signal<(String?, [FoundStickerItem], Bool), NoError>], NoError> = .single([])
let query = text.trimmingCharacters(in: .whitespacesAndNewlines) let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
if query.isSingleEmoji { if query.isSingleEmoji {
signals = .single([context.engine.stickers.searchStickers(query: nil, emoticon: [text.basicEmoji.0]) signals = .single([context.engine.stickers.searchStickers(query: nil, emoticon: [text.basicEmoji.0])
|> map { (nil, $0.items) }]) |> map { (nil, $0.items, $0.isFinalResult) }])
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" { } else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
if !languageCode.lowercased().hasPrefix("en") { if !languageCode.lowercased().hasPrefix("en") {
@ -377,10 +377,10 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
} }
} }
signals = signal signals = signal
|> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in |> map { keywords -> [Signal<(String?, [FoundStickerItem], Bool), NoError>] in
let emoticon = keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 } let emoticon = keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 }
return [context.engine.stickers.searchStickers(query: query, emoticon: emoticon, inputLanguageCode: languageCode) return [context.engine.stickers.searchStickers(query: query, emoticon: emoticon, inputLanguageCode: languageCode)
|> map { (nil, $0.items) }] |> map { (nil, $0.items, $0.isFinalResult) }]
} }
} }
@ -389,12 +389,16 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
return combineLatest(signals) return combineLatest(signals)
}).start(next: { results in }).start(next: { results in
var result: [(String?, FoundStickerItem)] = [] var result: [(String?, FoundStickerItem)] = []
for (emoji, stickers) in results { var allAreFinal = true
for (emoji, stickers, isFinal) in results {
for sticker in stickers { for sticker in stickers {
result.append((emoji, sticker)) result.append((emoji, sticker))
} }
if !isFinal {
allAreFinal = false
}
} }
subscriber.putNext(result) subscriber.putNext((result, allAreFinal))
}, completed: { }, completed: {
// subscriber.putCompletion() // subscriber.putCompletion()
}) })
@ -456,7 +460,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
signal = combineLatest(stickers, packs) signal = combineLatest(stickers, packs)
|> map { stickers, packs -> ([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)? in |> map { stickers, packs -> ([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)? in
return (stickers, packs.0, packs.1, packs.2) return (stickers.0, packs.0, packs.1 && stickers.1, packs.2)
} }
self.updateActivity?(true) self.updateActivity?(true)
} else { } else {

View File

@ -344,98 +344,93 @@ final class GiftSetupScreenComponent: Component {
let entities = generateChatInputTextEntities(self.textInputState.text) 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 source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, includeUpgrade: self.includeUpgrade, peerId: peerId, giftId: starGift.id, text: self.textInputState.text.string, entities: entities)
let inputData = BotCheckoutController.InputData.fetch(context: component.context, source: source)
|> map(Optional.init)
|> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
return .single(nil)
}
let completion = component.completion let completion = component.completion
let _ = (inputData let signal = BotCheckoutController.InputData.fetch(context: component.context, source: source)
|> deliverOnMainQueue).startStandalone(next: { [weak self] inputData in |> `catch` { _ -> Signal<BotCheckoutController.InputData, SendBotPaymentFormError> in
guard let inputData else { return .fail(.generic)
}
|> mapToSignal { inputData -> Signal<SendBotPaymentResult, SendBotPaymentFormError> in
return component.context.engine.payments.sendStarsPaymentForm(formId: inputData.form.id, source: source)
}
|> deliverOnMainQueue
let _ = signal.start(next: { [weak self] result in
guard let self, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else {
return return
} }
let _ = (component.context.engine.payments.sendStarsPaymentForm(formId: inputData.form.id, source: source)
|> deliverOnMainQueue).start(next: { [weak self] result in if peerId.namespace == Namespaces.Peer.CloudChannel {
if let self, peerId.namespace == Namespaces.Peer.CloudChannel, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController { var controllers = navigationController.viewControllers
var controllers = navigationController.viewControllers controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) }
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } navigationController.setViewControllers(controllers, animated: true)
navigationController.setViewControllers(controllers, animated: true)
let tooltipController = UndoOverlayController(
presentationData: presentationData,
content: .sticker(
context: context,
file: starGift.file,
loop: true,
title: nil,
text: presentationData.strings.Gift_Send_Success(self.peerMap[peerId]?.compactDisplayTitle ?? "", presentationData.strings.Gift_Send_Success_Stars(Int32(starGift.price))).string,
undoText: nil,
customAction: nil
),
action: { _ in return true }
)
(navigationController.viewControllers.last as? ViewController)?.present(tooltipController, in: .current)
navigationController.view.addSubview(ConfettiView(frame: navigationController.view.bounds))
}
if let completion { let tooltipController = UndoOverlayController(
completion() presentationData: presentationData,
content: .sticker(
if let self, let controller = self.environment?.controller() { context: context,
controller.dismiss() file: starGift.file,
} loop: true,
} else { title: nil,
guard let self, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else { text: presentationData.strings.Gift_Send_Success(self.peerMap[peerId]?.compactDisplayTitle ?? "", presentationData.strings.Gift_Send_Success_Stars(Int32(starGift.price))).string,
return undoText: nil,
} customAction: nil
),
if peerId.namespace != Namespaces.Peer.CloudChannel { action: { _ in return true }
var controllers = navigationController.viewControllers )
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) } (navigationController.viewControllers.last as? ViewController)?.present(tooltipController, in: .current)
var foundController = false
for controller in controllers.reversed() { navigationController.view.addSubview(ConfettiView(frame: navigationController.view.bounds))
if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { } else if peerId.namespace == Namespaces.Peer.CloudUser {
chatController.hintPlayNextOutgoingGift() var controllers = navigationController.viewControllers
foundController = true controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) }
break var foundController = false
} for controller in controllers.reversed() {
} if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation {
if !foundController { chatController.hintPlayNextOutgoingGift()
let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) foundController = true
chatController.hintPlayNextOutgoingGift() break
controllers.append(chatController)
}
navigationController.setViewControllers(controllers, animated: true)
} }
} }
if !foundController {
starsContext.load(force: true) let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil)
}, error: { [weak self] error in chatController.hintPlayNextOutgoingGift()
guard let self, let controller = self.environment?.controller() else { controllers.append(chatController)
return
} }
navigationController.setViewControllers(controllers, animated: true)
}
if let completion {
completion()
self.inProgress = false if let controller = self.environment?.controller() {
self.state?.updated() controller.dismiss()
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
var errorText: String?
switch error {
case .starGiftOutOfStock:
errorText = presentationData.strings.Gift_Send_ErrorOutOfStock
default:
errorText = presentationData.strings.Gift_Send_ErrorUnknown
} }
}
if let errorText = errorText {
let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) starsContext.load(force: true)
controller.present(alertController, in: .window(.root)) }, error: { [weak self] error in
} guard let self, let controller = self.environment?.controller() else {
}) return
}
self.inProgress = false
self.state?.updated()
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
var errorText: String?
switch error {
case .starGiftOutOfStock:
errorText = presentationData.strings.Gift_Send_ErrorOutOfStock
default:
errorText = presentationData.strings.Gift_Send_ErrorUnknown
}
if let errorText = errorText {
let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
controller.present(alertController, in: .window(.root))
}
}) })
} }

View File

@ -3112,10 +3112,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
} }
self.entitiesView.canInteract = { [weak self] in self.entitiesView.canInteract = { [weak self] in
if let self, let controller = self.controller { if let self, let controller = self.controller {
return !controller.node.recording.isActive if controller.node.recording.isActive {
} else { return false
return true } else if case .avatarEditor = controller.mode, self.drawingScreen == nil {
return false
}
} }
return true
} }
self.availableReactionsDisposable = (allowedStoryReactions(context: controller.context) self.availableReactionsDisposable = (allowedStoryReactions(context: controller.context)
@ -3252,11 +3255,12 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
if let mediaEntityView = self.entitiesView.add(mediaEntity, announce: false) as? DrawingMediaEntityView { if let mediaEntityView = self.entitiesView.add(mediaEntity, announce: false) as? DrawingMediaEntityView {
self.entitiesView.sendSubviewToBack(mediaEntityView) self.entitiesView.sendSubviewToBack(mediaEntityView)
mediaEntityView.updated = { [weak self, weak mediaEntity] in mediaEntityView.updated = { [weak self, weak mediaEntity] in
if let self, let mediaEntity { if let self, let mediaEditor = self.mediaEditor, let mediaEntity {
let rotation = mediaEntity.rotation - initialRotation let rotation = mediaEntity.rotation - initialRotation
let position = CGPoint(x: mediaEntity.position.x - initialPosition.x, y: mediaEntity.position.y - initialPosition.y) let position = CGPoint(x: mediaEntity.position.x - initialPosition.x, y: mediaEntity.position.y - initialPosition.y)
let scale = mediaEntity.scale / initialScale let scale = mediaEntity.scale / initialScale
self.mediaEditor?.setCrop(offset: position, scale: scale, rotation: rotation, mirroring: false) let mirroring = mediaEditor.values.cropMirroring
mediaEditor.setCrop(offset: position, scale: scale, rotation: rotation, mirroring: mirroring)
self.updateMaskDrawingView(position: position, scale: scale, rotation: rotation) self.updateMaskDrawingView(position: position, scale: scale, rotation: rotation)
} }
@ -3465,6 +3469,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
} }
return false return false
}) as? DrawingStickerEntityView { }) as? DrawingStickerEntityView {
#if DEBUG
if let data = result.dayImage.pngData() {
let path = NSTemporaryDirectory() + "\(Int(Date().timeIntervalSince1970)).png"
try? data.write(to: URL(fileURLWithPath: path))
}
#endif
existingEntityView.isNightTheme = isNightTheme existingEntityView.isNightTheme = isNightTheme
let messageEntity = existingEntityView.entity as! DrawingStickerEntity let messageEntity = existingEntityView.entity as! DrawingStickerEntity
messageEntity.renderImage = result.dayImage messageEntity.renderImage = result.dayImage
@ -5613,6 +5624,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
self.previousDrawingData = self.drawingView.drawingData self.previousDrawingData = self.drawingView.drawingData
self.previousDrawingEntities = self.entitiesView.entities self.previousDrawingEntities = self.entitiesView.entities
self.cropScrollView?.isUserInteractionEnabled = false
self.interaction?.deactivate() self.interaction?.deactivate()
let controller = DrawingScreen( let controller = DrawingScreen(
context: self.context, context: self.context,
@ -5668,6 +5681,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
self.previousDrawingData = nil self.previousDrawingData = nil
self.previousDrawingEntities = nil self.previousDrawingEntities = nil
self.cropScrollView?.isUserInteractionEnabled = true
} }
controller.requestApply = { [weak controller, weak self] in controller.requestApply = { [weak controller, weak self] in
guard let self else { guard let self else {
@ -5690,6 +5705,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
self.interaction?.activate() self.interaction?.activate()
self.entitiesView.selectEntity(nil) self.entitiesView.selectEntity(nil)
self.cropScrollView?.isUserInteractionEnabled = true
} }
self.controller?.present(controller, in: .current) self.controller?.present(controller, in: .current)
self.animateOutToTool(tool: mode) self.animateOutToTool(tool: mode)
@ -7594,6 +7611,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
let values = mediaEditor.values.withUpdatedCoverDimensions(dimensions) let values = mediaEditor.values.withUpdatedCoverDimensions(dimensions)
makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: image, dimensions: storyDimensions, outputDimensions: dimensions.aspectFitted(CGSize(width: 1080, height: 1080)), values: values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: image, dimensions: storyDimensions, outputDimensions: dimensions.aspectFitted(CGSize(width: 1080, height: 1080)), values: values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in
if let self, let resultImage { if let self, let resultImage {
#if DEBUG
if let data = resultImage.jpegData(compressionQuality: 0.7) {
let path = NSTemporaryDirectory() + "\(Int(Date().timeIntervalSince1970)).jpg"
try? data.write(to: URL(fileURLWithPath: path))
}
#endif
self.completion(MediaEditorScreenImpl.Result(media: .image(image: resultImage, dimensions: PixelDimensions(resultImage.size))), { [weak self] finished in self.completion(MediaEditorScreenImpl.Result(media: .image(image: resultImage, dimensions: PixelDimensions(resultImage.size))), { [weak self] finished in
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss() self?.dismiss()

View File

@ -141,11 +141,11 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
} }
public func scrollViewDidScroll(_ scrollView: UIScrollView) { public func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateScrolling(transition: .immediate) self.updateScrolling(interactive: true, transition: .immediate)
} }
private var notify = false private var notify = false
func updateScrolling(transition: ComponentTransition) { func updateScrolling(interactive: Bool = false, transition: ComponentTransition) {
if let starsProducts = self.starsProducts, let params = self.currentParams { if let starsProducts = self.starsProducts, let params = self.currentParams {
let optionSpacing: CGFloat = 10.0 let optionSpacing: CGFloat = 10.0
let itemsSideInset = params.sideInset + 16.0 let itemsSideInset = params.sideInset + 16.0
@ -644,7 +644,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
} }
let bottomContentOffset = max(0.0, self.scrollNode.view.contentSize.height - self.scrollNode.view.contentOffset.y - self.scrollNode.view.frame.height) let bottomContentOffset = max(0.0, self.scrollNode.view.contentSize.height - self.scrollNode.view.contentOffset.y - self.scrollNode.view.frame.height)
if bottomContentOffset < 200.0 { if interactive, bottomContentOffset < 200.0 {
self.profileGifts.loadMore() self.profileGifts.loadMore()
} }
} }

View File

@ -293,9 +293,11 @@ private final class SheetContent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0))
) )
var isExtendedMedia = false
let subject: StarsImageComponent.Subject let subject: StarsImageComponent.Subject
if !component.extendedMedia.isEmpty { if !component.extendedMedia.isEmpty {
subject = .extendedMedia(component.extendedMedia) subject = .extendedMedia(component.extendedMedia)
isExtendedMedia = true
} else if let peer = state.botPeer { } else if let peer = state.botPeer {
if let photo = component.invoice.photo { if let photo = component.invoice.photo {
subject = .photo(photo) subject = .photo(photo)
@ -381,7 +383,7 @@ private final class SheetContent: CombinedComponent {
contentSize.height += title.size.height contentSize.height += title.size.height
contentSize.height += 13.0 contentSize.height += 13.0
if isBot, let peer = state.botPeer { if isBot && !isExtendedMedia, let peer = state.botPeer {
contentSize.height -= 3.0 contentSize.height -= 3.0
let peerShortcut = peerShortcut.update( let peerShortcut = peerShortcut.update(
component: PremiumPeerShortcutComponent( component: PremiumPeerShortcutComponent(
@ -650,48 +652,49 @@ private final class SheetContent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0))
) )
contentSize.height += button.size.height contentSize.height += button.size.height
if isSubscription {
contentSize.height += 14.0 let termsText = isSubscription ? strings.Stars_Subscription_Terms : strings.Stars_Transfer_Terms
let termsURL = isSubscription ? strings.Stars_Subscription_Terms_URL : strings.Stars_Transfer_Terms_URL
let termsTextFont = Font.regular(13.0)
let termsTextColor = theme.actionSheet.secondaryTextColor contentSize.height += 14.0
let termsLinkColor = theme.actionSheet.controlAccentColor
let termsMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: termsTextFont, textColor: termsTextColor), bold: MarkdownAttributeSet(font: termsTextFont, textColor: termsTextColor), link: MarkdownAttributeSet(font: termsTextFont, textColor: termsLinkColor), linkAttribute: { contents in let termsTextFont = Font.regular(13.0)
return (TelegramTextAttributes.URL, contents) let termsTextColor = theme.actionSheet.secondaryTextColor
}) let termsLinkColor = theme.actionSheet.controlAccentColor
let info = info.update( let termsMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: termsTextFont, textColor: termsTextColor), bold: MarkdownAttributeSet(font: termsTextFont, textColor: termsTextColor), link: MarkdownAttributeSet(font: termsTextFont, textColor: termsLinkColor), linkAttribute: { contents in
component: BalancedTextComponent( return (TelegramTextAttributes.URL, contents)
text: .markdown( })
text: strings.Stars_Subscription_Terms, let info = info.update(
attributes: termsMarkdownAttributes component: BalancedTextComponent(
), text: .markdown(
horizontalAlignment: .center, text: termsText,
maximumNumberOfLines: 0, attributes: termsMarkdownAttributes
lineSpacing: 0.2,
highlightColor: linkColor.withAlphaComponent(0.2),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { [weak controller] attributes, _ in
if let controller, let navigationController = controller.navigationController as? NavigationController {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Subscription_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
}
}
), ),
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), horizontalAlignment: .center,
transition: .immediate maximumNumberOfLines: 0,
) lineSpacing: 0.2,
context.add(info highlightColor: linkColor.withAlphaComponent(0.2),
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + info.size.height / 2.0)) highlightAction: { attributes in
) if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
contentSize.height += info.size.height return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
} return nil
}
},
tapAction: { [weak controller] attributes, _ in
if let controller, let navigationController = controller.navigationController as? NavigationController {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: termsURL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
}
}
),
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
transition: .immediate
)
context.add(info
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + info.size.height / 2.0))
)
contentSize.height += info.size.height
contentSize.height += 48.0 contentSize.height += 48.0