Various improvements

This commit is contained in:
Ilya Laktyushin 2024-05-23 19:42:13 +04:00
parent bd9c495fb4
commit 4e55e3aab0
7 changed files with 370 additions and 133 deletions

View File

@ -12217,6 +12217,7 @@ Sorry for the inconvenience.";
"Message.FactCheck" = "Fact Check";
"Message.FactCheck.WhatIsThis" = "what's this?";
"Conversation.FactCheck.InnerDescription" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country (%@) responsible for combating misinformation.";
"Conversation.FactCheck.Description" = "This clarification was provided by a fact checking agency assigned by the department of the government of your country (%@) responsible for combating misinformation.";
"FactCheck.Title" = "Fact Check";

View File

@ -63,7 +63,9 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode {
super.init()
self.addSubnode(self.searchBar)
if hasCurrentChat {
self.addSubnode(self.searchBar)
}
self.searchBar.cancel = { [weak self] in
self?.searchBar.deactivate(clear: false)
@ -79,6 +81,10 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode {
}
}
override var mode: NavigationBarContentMode {
return self.hasCurrentChat ? .replacement : .expansion
}
func updateTheme(_ theme: PresentationTheme) {
self.theme = theme
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: self.strings)
@ -89,7 +95,11 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode {
}
override var nominalHeight: CGFloat {
return 54.0 + 44.0
if self.hasCurrentChat {
return 54.0 + 44.0
} else {
return 45.0
}
}
private var validLayout: (CGSize, CGFloat, CGFloat)?
@ -119,7 +129,7 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode {
),
customLayout: TabSelectorComponent.CustomLayout(
font: Font.medium(14.0),
spacing: 24.0,
spacing: self.hasCurrentChat ? 24.0 : 8.0,
lineSelection: true
),
items: items,
@ -135,7 +145,13 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode {
environment: {},
containerSize: CGSize(width: size.width, height: 44.0)
)
let tabSelectorFrame = CGRect(origin: CGPoint(x: floor((size.width - tabSelectorSize.width) / 2.0), y: size.height - tabSelectorSize.height - 9.0), size: tabSelectorSize)
let tabSelectorFrameOriginX: CGFloat
if self.hasCurrentChat || "".isEmpty {
tabSelectorFrameOriginX = floorToScreenPixels((size.width - tabSelectorSize.width) / 2.0)
} else {
tabSelectorFrameOriginX = 4.0
}
let tabSelectorFrame = CGRect(origin: CGPoint(x: tabSelectorFrameOriginX, y: size.height - tabSelectorSize.height - 9.0), size: tabSelectorSize)
if let tabSelectorView = self.tabSelector.view {
if tabSelectorView.superview == nil {
self.view.addSubview(tabSelectorView)

View File

@ -231,9 +231,10 @@ class PremiumCoinComponent: Component {
self.sceneView.scene = scene
self.sceneView.delegate = self
self.didSetReady = true
self._ready.set(.single(true))
self.onReady()
let _ = self.sceneView.snapshot()
// self.didSetReady = true
// self._ready.set(.single(true))
// self.onReady()
}
private var didSetReady = false

View File

@ -42,8 +42,11 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
private var titleBadgeButton: HighlightTrackingButtonNode?
private let textClippingNode: ASDisplayNode
private let textNode: TextNode
private let additionalTextNode: TextNode
private var linkHighlightingNode: LinkHighlightingNode?
private let lineNode: ASDisplayNode
private var maskView: UIImageView?
private var maskOverlayView: UIView?
@ -55,13 +58,17 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
private var isExpanded: Bool = false
private var appliedIsExpanded: Bool = false
private var countryName: String?
required public init() {
self.titleNode = TextNode()
self.titleBadgeLabel = TextNode()
self.textClippingNode = ASDisplayNode()
self.textNode = TextNode()
self.additionalTextNode = TextNode()
self.expandIcon = ASImageNode()
self.statusNode = ChatMessageDateAndStatusNode()
self.lineNode = ASDisplayNode()
super.init()
@ -80,6 +87,14 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
self.textNode.displaysAsynchronously = false
self.textClippingNode.addSubnode(self.textNode)
self.additionalTextNode.isUserInteractionEnabled = false
self.additionalTextNode.contentMode = .topLeft
self.additionalTextNode.contentsScale = UIScreenScale
self.additionalTextNode.displaysAsynchronously = false
self.textClippingNode.addSubnode(self.additionalTextNode)
self.textClippingNode.addSubnode(self.lineNode)
self.titleBadgeLabel.isUserInteractionEnabled = false
self.titleBadgeLabel.contentMode = .topLeft
self.titleBadgeLabel.contentsScale = UIScreenScale
@ -106,24 +121,10 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
}
@objc private func badgePressed() {
guard let item = self.item else {
guard let item = self.item, let countryName = self.countryName else {
return
}
var countryId: String?
for attribute in item.message.attributes {
if let attribute = attribute as? FactCheckMessageAttribute, case let .Loaded(_, _, countryIdValue) = attribute.content {
countryId = countryIdValue
break
}
}
guard let countryId else {
return
}
let locale = localeWithStrings(item.presentationData.strings)
let countryName = displayCountryName(countryId, locale: locale)
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_FactCheck_Description(countryName).string, true, self.titleBadgeButton, nil)
}
@ -148,13 +149,9 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
}
let textNodeFrame = self.textClippingNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
var concealed = true
if let (attributeText, fullText) = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
}
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)))
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: false)))
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false))
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
@ -216,10 +213,12 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
let titleLayout = TextNode.asyncLayout(self.titleNode)
let titleBadgeLayout = TextNode.asyncLayout(self.titleBadgeLabel)
let textLayout = TextNode.asyncLayout(self.textNode)
let additionalTextLayout = TextNode.asyncLayout(self.additionalTextNode)
let measureTextLayout = TextNode.asyncLayout(nil)
let statusLayout = self.statusNode.asyncLayout()
let currentIsExpanded = self.isExpanded
let currentCountryName = self.countryName
return { item, layoutConstants, _, _, _, _ in
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
@ -313,15 +312,32 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
let titleBadgeString = NSAttributedString(string: item.presentationData.strings.Message_FactCheck_WhatIsThis, font: badgeFont, textColor: mainColor)
let (titleBadgeLayout, titleBadgeApply) = titleBadgeLayout(TextNodeLayoutArguments(attributedString: titleBadgeString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize))
let countryName: String
if let currentCountryName {
countryName = currentCountryName
} else {
if let attribute = item.message.factCheckAttribute, case let .Loaded(_, _, countryIdValue) = attribute.content {
let locale = localeWithStrings(item.presentationData.strings)
countryName = displayCountryName(countryIdValue, locale: locale)
} else {
countryName = ""
}
}
let finalAttributedText = stringWithAppliedEntities(rawText, entities: rawEntities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textBlockQuoteFont, message: nil) as! NSMutableAttributedString
finalAttributedText.append(NSAttributedString(string: "__", font: textFont, textColor: .clear))
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: finalAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor))
let additionalAttributedText = NSMutableAttributedString(string: item.presentationData.strings.Conversation_FactCheck_InnerDescription(countryName).string, font: badgeFont, textColor: mainColor)
additionalAttributedText.append(NSAttributedString(string: "__", font: badgeFont, textColor: .clear))
let (additionalTextLayout, additionalTextApply) = additionalTextLayout(TextNodeLayoutArguments(attributedString: additionalAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, lineSpacing: 0.0, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor))
var canExpand = false
var clippedTextHeight: CGFloat = textLayout.size.height
if textLayout.numberOfLines > 4 {
let (measuredTextLayout, _) = measureTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 3, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor))
let (measuredTextLayout, _) = measureTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 4, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor))
canExpand = true
if !currentIsExpanded {
@ -340,7 +356,11 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
let textFrame = CGRect(origin: CGPoint(x: titleFrame.origin.x, y: -textInsets.top + titleFrameWithoutInsets.height + textSpacing), size: textLayout.size)
var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: clippedTextHeight - textInsets.top - textInsets.bottom))
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
let additionalTextFrame = CGRect(origin: CGPoint(x: titleFrame.origin.x, y: textFrame.maxY), size: additionalTextLayout.size)
var additionalTextFrameWithoutInsets = CGRect(origin: CGPoint(x: additionalTextFrame.origin.x + textInsets.left, y: additionalTextFrame.origin.y + textInsets.top), size: CGSize(width: additionalTextFrame.width - textInsets.left - textInsets.right, height: additionalTextFrame.height - textInsets.top - textInsets.bottom))
additionalTextFrameWithoutInsets = additionalTextFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
if let statusType = statusType {
var isReplyThread = false
@ -377,6 +397,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0)
}
suggestedBoundingWidth = max(suggestedBoundingWidth, additionalTextFrameWithoutInsets.width)
let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
suggestedBoundingWidth += (sideInsets - 2.0) * 2.0
@ -385,7 +406,13 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right)
boundingSize = CGSize(width: boundingWidth, height: topInset + titleFrameWithoutInsets.height + textFrameWithoutInsets.size.height + textSpacing)
var contentHeight = titleFrameWithoutInsets.height + textSpacing + textFrameWithoutInsets.size.height
if canExpand && !currentIsExpanded {
} else {
contentHeight += textSpacing * 2.0 + 1.0 + additionalTextFrameWithoutInsets.height
}
contentHeight += textSpacing
boundingSize = CGSize(width: boundingWidth, height: topInset + contentHeight - textSpacing)
if let statusSizeAndApply = statusSizeAndApply {
boundingSize.height += statusSizeAndApply.0.height
}
@ -400,7 +427,8 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
let themeUpdated = strongSelf.item?.presentationData.theme.theme !== item.presentationData.theme.theme
strongSelf.item = item
strongSelf.countryName = countryName
let backgroundView: MessageInlineBlockBackgroundView
if let current = strongSelf.backgroundView {
backgroundView = current
@ -410,6 +438,10 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
strongSelf.backgroundView = backgroundView
}
if themeUpdated {
strongSelf.lineNode.backgroundColor = mainColor.withAlphaComponent(0.15)
}
var isExpandedUpdated = false
if strongSelf.appliedIsExpanded != currentIsExpanded {
strongSelf.appliedIsExpanded = currentIsExpanded
@ -464,57 +496,12 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
let _ = textApply()
strongSelf.textNode.frame = CGRect(origin: .zero, size: textFrame.size)
var clippingTextFrame = textFrame.offsetBy(dx: 0.0, dy: topInset)
clippingTextFrame.size.height = clippedTextHeight - 3.0
if canExpand {
let wasHidden = strongSelf.expandIcon.isHidden
strongSelf.expandIcon.isHidden = false
if strongSelf.maskView?.image == nil {
strongSelf.maskView?.image = generateMaskImage()
}
strongSelf.textClippingNode.view.mask = strongSelf.maskView
var expandIconFrame: CGRect = .zero
if let icon = strongSelf.expandIcon.image {
expandIconFrame = CGRect(origin: CGPoint(x: boundingWidth - icon.size.width - 19.0, y: clippingTextFrame.maxY - icon.size.height - 5.0), size: icon.size)
if wasHidden || isFirstTime {
strongSelf.expandIcon.position = expandIconFrame.center
} else {
animation.animator.updatePosition(layer: strongSelf.expandIcon.layer, position: expandIconFrame.center, completion: nil)
}
strongSelf.expandIcon.bounds = CGRect(origin: .zero, size: expandIconFrame.size)
}
let expandButtonFrame = expandIconFrame.insetBy(dx: -8.0, dy: -8.0)
let expandButton: HighlightTrackingButtonNode
if let current = strongSelf.expandButton {
expandButton = current
} else {
expandButton = HighlightTrackingButtonNode()
expandButton.addTarget(self, action: #selector(strongSelf.expandPressed), forControlEvents: .touchUpInside)
expandButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.expandIcon.layer.removeAnimation(forKey: "opacity")
strongSelf.expandIcon.alpha = 0.4
} else {
strongSelf.expandIcon.alpha = 1.0
strongSelf.expandIcon.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
strongSelf.expandButton = expandButton
strongSelf.addSubnode(expandButton)
}
expandButton.frame = expandButtonFrame
} else {
strongSelf.expandIcon.isHidden = true
strongSelf.textClippingNode.view.mask = nil
}
let _ = additionalTextApply()
strongSelf.additionalTextNode.frame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.height - textInsets.bottom + textSpacing + 1.0), size: additionalTextFrame.size)
let clippingTextFrame = CGRect(origin: textFrame.origin.offsetBy(dx: 0.0, dy: topInset), size: CGSize(width: boundingWidth, height: contentHeight - titleFrame.height + textSpacing))
var titleLineWidth: CGFloat = 0.0
if let firstLine = titleLayout.linesRects().first {
titleLineWidth = firstLine.width
@ -559,7 +546,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
titleBadgeButton.setBackgroundImage(generateFilledCircleImage(diameter: badgeBackgroundFrame.height, color: mainColor.withMultipliedAlpha(0.1))?.stretchableImage(withLeftCapWidth: Int(badgeBackgroundFrame.height / 2), topCapHeight: Int(badgeBackgroundFrame.height / 2)), for: .normal)
}
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top + topInset), size: CGSize(width: boundingWidth - backgroundInsets.left - backgroundInsets.right, height: titleFrameWithoutInsets.height + textSpacing + textFrameWithoutInsets.height + textSpacing))
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top + topInset), size: CGSize(width: boundingWidth - backgroundInsets.left - backgroundInsets.right, height: contentHeight))
if isFirstTime {
strongSelf.textClippingNode.frame = clippingTextFrame
@ -578,6 +565,55 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
}
backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: nil, thirdColor: nil, backgroundColor: nil, pattern: nil, patternTopRightPosition: nil, animation: isFirstTime ? .None : animation)
animation.animator.updateFrame(layer: strongSelf.lineNode.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: textFrame.height - textSpacing + 1.0), size: CGSize(width: backgroundFrame.width - 9.0 - 6.0, height: 1.0 - UIScreenPixel)), completion: nil)
if canExpand {
let wasHidden = strongSelf.expandIcon.isHidden
strongSelf.expandIcon.isHidden = false
if strongSelf.maskView?.image == nil {
strongSelf.maskView?.image = generateMaskImage()
}
strongSelf.textClippingNode.view.mask = strongSelf.maskView
var expandIconFrame: CGRect = .zero
if let icon = strongSelf.expandIcon.image {
expandIconFrame = CGRect(origin: CGPoint(x: boundingWidth - icon.size.width - 19.0, y: backgroundFrame.maxY - icon.size.height - 6.0), size: icon.size)
if wasHidden || isFirstTime {
strongSelf.expandIcon.position = expandIconFrame.center
} else {
animation.animator.updatePosition(layer: strongSelf.expandIcon.layer, position: expandIconFrame.center, completion: nil)
}
strongSelf.expandIcon.bounds = CGRect(origin: .zero, size: expandIconFrame.size)
}
let expandButtonFrame = expandIconFrame.insetBy(dx: -8.0, dy: -8.0)
let expandButton: HighlightTrackingButtonNode
if let current = strongSelf.expandButton {
expandButton = current
} else {
expandButton = HighlightTrackingButtonNode()
expandButton.addTarget(self, action: #selector(strongSelf.expandPressed), forControlEvents: .touchUpInside)
expandButton.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.expandIcon.layer.removeAnimation(forKey: "opacity")
strongSelf.expandIcon.alpha = 0.4
} else {
strongSelf.expandIcon.alpha = 1.0
strongSelf.expandIcon.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
strongSelf.expandButton = expandButton
strongSelf.addSubnode(expandButton)
}
expandButton.frame = expandButtonFrame
} else {
strongSelf.expandIcon.isHidden = true
strongSelf.textClippingNode.view.mask = nil
}
if let statusSizeAndApply = statusSizeAndApply {
strongSelf.statusNode.reactionSelected = { [weak strongSelf] _, value, sourceView in
guard let strongSelf, let item = strongSelf.item else {
@ -594,7 +630,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value)
}
let statusFrame = CGRect(origin: CGPoint(x: boundingWidth - layoutConstants.text.bubbleInsets.right - statusSizeAndApply.0.width, y: topInset + textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
let statusFrame = CGRect(origin: CGPoint(x: boundingWidth - layoutConstants.text.bubbleInsets.right - statusSizeAndApply.0.width, y: backgroundFrame.maxY + 4.0), size: statusSizeAndApply.0)
if isFirstTime {
strongSelf.statusNode.frame = statusFrame
} else {

View File

@ -11770,7 +11770,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
hasBirthdayToday = true
}
if hasBirthdayToday, let age = ageForBirthday(birthday), age > 0 {
if hasBirthdayToday {
Queue.mainQueue().after(0.3) {
var birthdayItemFrame: CGRect?
if let section = self.regularSections[InfoSection.peerInfo] {

View File

@ -29,19 +29,22 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let action: () -> Void
let cancel: (Bool) -> Void
let openPeer: (EnginePeer) -> Void
let copyTransactionId: () -> Void
init(
context: AccountContext,
subject: StarsTransactionScreen.Subject,
action: @escaping () -> Void,
cancel: @escaping (Bool) -> Void,
openPeer: @escaping (EnginePeer) -> Void
openPeer: @escaping (EnginePeer) -> Void,
copyTransactionId: @escaping () -> Void
) {
self.context = context
self.subject = subject
self.action = action
self.cancel = cancel
self.openPeer = openPeer
self.copyTransactionId = copyTransactionId
}
static func ==(lhs: StarsTransactionSheetContent, rhs: StarsTransactionSheetContent) -> Bool {
@ -163,7 +166,21 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let gloss = false
switch subject {
case let .transaction(transaction):
titleText = "Product Title"
switch transaction.peer {
case .peer:
titleText = "Product Title"
case .appStore:
titleText = "In-app Purchase"
case .playMarket:
titleText = "Play Market"
case .premiumBot:
titleText = "Premium Bot"
case .fragment:
titleText = "Fragment"
case .unsupported:
titleText = "Unsupported"
}
if transaction.count < 0 {
descriptionText = "- \(transaction.count * -1) ⭐️"
} else {
@ -185,7 +202,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: titleText,
font: Font.semibold(24.0),
font: Font.bold(25.0),
textColor: theme.actionSheet.primaryTextColor,
paragraphAlignment: .center
)),
@ -211,16 +228,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
transition: .immediate
)
let textFont = Font.regular(15.0)
let boldTextFont = Font.semibold(15.0)
let textColor = theme.actionSheet.primaryTextColor
let linkColor = theme.actionSheet.controlAccentColor
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
})
let description = description.update(
component: BalancedTextComponent(
text: .plain(NSAttributedString(string: descriptionText, font: boldTextFont, textColor: descriptionText.hasPrefix("-") ? theme.list.itemDestructiveColor : theme.list.itemDisclosureActions.constructive.fillColor)),
text: .plain(NSAttributedString(string: descriptionText, font: Font.semibold(17.0), textColor: descriptionText.hasPrefix("-") ? theme.list.itemDestructiveColor : theme.list.itemDisclosureActions.constructive.fillColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2
@ -240,7 +250,13 @@ private final class StarsTransactionSheetContent: CombinedComponent {
title: strings.Stars_Transaction_Date,
component: AnyComponent(
Button(
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: toPeer)),
content: AnyComponent(
PeerCellComponent(
context: component.context,
textColor: tableLinkColor,
peer: toPeer
)
),
action: {
if toPeer.id != accountContext.account.peerId {
component.openPeer(toPeer)
@ -258,12 +274,16 @@ private final class StarsTransactionSheetContent: CombinedComponent {
id: "transaction",
title: strings.Stars_Transaction_Id,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: transactionId, font: Font.monospace(15.0), textColor: tableTextColor)),
truncationType: .end,
maximumNumberOfLines: 0
TransactionCellComponent(
textColor: tableTextColor,
accentColor: tableLinkColor,
transactionId: transactionId,
copy: {
component.copyTransactionId()
}
)
)
),
insets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 5.0)
))
tableItems.append(.init(
@ -283,6 +303,13 @@ private final class StarsTransactionSheetContent: CombinedComponent {
transition: .immediate
)
let textFont = Font.regular(15.0)
let boldTextFont = Font.semibold(15.0)
let textColor = theme.actionSheet.secondaryTextColor
let linkColor = theme.actionSheet.controlAccentColor
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
})
let additional = additional.update(
component: BalancedTextComponent(
text: .markdown(text: additionalText, attributes: markdownAttributes),
@ -319,16 +346,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
animationName: nil,
iconPosition: .left,
isLoading: state.inProgress,
action: { [weak state] in
if gloss {
component.action()
if let state {
state.inProgress = true
state.updated()
}
} else {
component.cancel(true)
}
action: {
component.cancel(true)
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
@ -336,7 +355,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: 28.0 + 125.0))
.position(CGPoint(x: context.availableSize.width / 2.0, y: 31.0 + 125.0))
)
context.add(star
@ -344,12 +363,12 @@ private final class StarsTransactionSheetContent: CombinedComponent {
)
var originY: CGFloat = 0.0
originY += star.size.height - 32.0
originY += star.size.height - 23.0
context.add(description
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + description.size.height / 2.0))
)
originY += description.size.height + 21.0
originY += description.size.height + 20.0
context.add(table
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0))
@ -384,17 +403,20 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
let subject: StarsTransactionScreen.Subject
let action: () -> Void
let openPeer: (EnginePeer) -> Void
let copyTransactionId: () -> Void
init(
context: AccountContext,
subject: StarsTransactionScreen.Subject,
action: @escaping () -> Void,
openPeer: @escaping (EnginePeer) -> Void
openPeer: @escaping (EnginePeer) -> Void,
copyTransactionId: @escaping () -> Void
) {
self.context = context
self.subject = subject
self.action = action
self.openPeer = openPeer
self.copyTransactionId = copyTransactionId
}
static func ==(lhs: StarsTransactionSheetComponent, rhs: StarsTransactionSheetComponent) -> Bool {
@ -411,6 +433,8 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
let sheet = Child(SheetComponent<EnvironmentType>.self)
let animateOut = StoredActionSlot(Action<Void>.self)
let sheetExternalState = SheetComponent<EnvironmentType>.ExternalState()
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
@ -423,20 +447,23 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
action: context.component.action,
cancel: { animate in
if animate {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
if let controller = controller() as? StarsTransactionScreen {
controller.dismissAllTooltips()
animateOut.invoke(Action { [weak controller] _ in
controller?.dismiss(completion: nil)
})
}
} else if let controller = controller() {
controller.dismiss(animated: false, completion: nil)
}
},
openPeer: context.component.openPeer
openPeer: context.component.openPeer,
copyTransactionId: context.component.copyTransactionId
)),
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
followContentSizeChanges: true,
clipsContent: true,
externalState: sheetExternalState,
animateOut: animateOut
),
environment: {
@ -448,13 +475,15 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
dismiss: { animated in
if animated {
animateOut.invoke(Action { _ in
if let controller = controller() {
if let controller = controller() as? StarsTransactionScreen {
controller.dismissAllTooltips()
animateOut.invoke(Action { _ in
controller.dismiss(completion: nil)
}
})
})
}
} else {
if let controller = controller() {
if let controller = controller() as? StarsTransactionScreen {
controller.dismissAllTooltips()
controller.dismiss(completion: nil)
}
}
@ -469,6 +498,22 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
if let controller = controller(), !controller.automaticallyControlPresentationContextLayout {
let layout = ContainerViewLayout(
size: context.availableSize,
metrics: environment.metrics,
deviceMetrics: environment.deviceMetrics,
intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: max(environment.safeInsets.bottom, sheetExternalState.contentHeight), right: 0.0),
safeInsets: UIEdgeInsets(top: 0.0, left: environment.safeInsets.left, bottom: 0.0, right: environment.safeInsets.right),
additionalInsets: .zero,
statusBarHeight: environment.statusBarHeight,
inputHeight: nil,
inputHeightIsInteractivellyChanging: false,
inVoiceOver: false
)
controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition)
}
return context.availableSize
}
}
@ -493,6 +538,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
self.context = context
var openPeerImpl: ((EnginePeer) -> Void)?
var copyTransactionIdImpl: (() -> Void)?
super.init(
context: context,
component: StarsTransactionSheetComponent(
@ -501,6 +547,9 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
action: action,
openPeer: { peerId in
openPeerImpl?(peerId)
},
copyTransactionId: {
copyTransactionIdImpl?()
}
),
navigationBarAppearance: .none,
@ -509,11 +558,14 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
)
self.navigationPresentation = .flatModal
self.automaticallyControlPresentationContextLayout = false
openPeerImpl = { [weak self] peer in
guard let self, let navigationController = self.navigationController as? NavigationController else {
return
}
self.dismissAllTooltips()
let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)
)
@ -524,6 +576,16 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), subject: nil, botStart: nil, updateTextInputState: nil, keepStack: .always, useExisting: false, purposefulAction: nil, scrollToEndIfExists: false, activateMessageSearch: nil, animated: true))
})
}
copyTransactionIdImpl = { [weak self] in
guard let self else {
return
}
self.dismissAllTooltips()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Stars_Transaction_CopiedId), elevatedLayout: false, position: .bottom, action: { _ in return true }), in: .current)
}
}
required public init(coder aDecoder: NSCoder) {
@ -546,6 +608,8 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
}
public func dismissAnimated() {
self.dismissAllTooltips()
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
view.dismissAnimated()
}
@ -571,11 +635,13 @@ private final class TableComponent: CombinedComponent {
public let id: AnyHashable
public let title: String
public let component: AnyComponent<Empty>
public let insets: UIEdgeInsets?
public init<IdType: Hashable>(id: IdType, title: String, component: AnyComponent<Empty>) {
public init<IdType: Hashable>(id: IdType, title: String, component: AnyComponent<Empty>, insets: UIEdgeInsets? = nil) {
self.id = AnyHashable(id)
self.title = title
self.component = component
self.insets = insets
}
public static func == (lhs: Item, rhs: Item) -> Bool {
@ -588,6 +654,9 @@ private final class TableComponent: CombinedComponent {
if lhs.component != rhs.component {
return false
}
if lhs.insets != rhs.insets {
return false
}
return true
}
}
@ -637,7 +706,7 @@ private final class TableComponent: CombinedComponent {
var leftColumnWidth: CGFloat = 0.0
var updatedTitleChildren: [_UpdatedChildComponent] = []
var updatedValueChildren: [_UpdatedChildComponent] = []
var updatedValueChildren: [(_UpdatedChildComponent, UIEdgeInsets)] = []
var updatedBorderChildren: [_UpdatedChildComponent] = []
for item in context.component.items {
@ -664,12 +733,19 @@ private final class TableComponent: CombinedComponent {
for item in context.component.items {
let titleChild = updatedTitleChildren[i]
let insets: UIEdgeInsets
if let customInsets = item.insets {
insets = customInsets
} else {
insets = UIEdgeInsets(top: 0.0, left: horizontalPadding, bottom: 0.0, right: horizontalPadding)
}
let valueChild = valueChildren[item.id].update(
component: item.component,
availableSize: CGSize(width: rightColumnWidth - horizontalPadding * 2.0, height: context.availableSize.height),
availableSize: CGSize(width: rightColumnWidth - insets.left - insets.right, height: context.availableSize.height),
transition: context.transition
)
updatedValueChildren.append(valueChild)
updatedValueChildren.append((valueChild, insets))
let rowHeight = max(40.0, max(titleChild.size.height, valueChild.size.height) + verticalPadding * 2.0)
rowHeights[i] = rowHeight
@ -742,11 +818,11 @@ private final class TableComponent: CombinedComponent {
i = 0
var originY: CGFloat = 0.0
for (titleChild, valueChild) in zip(updatedTitleChildren, updatedValueChildren) {
for (titleChild, (valueChild, valueInsets)) in zip(updatedTitleChildren, updatedValueChildren) {
let rowHeight = rowHeights[i] ?? 0.0
let titleFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: titleChild.size)
let valueFrame = CGRect(origin: CGPoint(x: leftColumnWidth + horizontalPadding, y: originY + verticalPadding), size: valueChild.size)
let valueFrame = CGRect(origin: CGPoint(x: leftColumnWidth + valueInsets.left, y: originY + verticalPadding), size: valueChild.size)
context.add(titleChild
.position(titleFrame.center)
@ -866,6 +942,113 @@ private final class PeerCellComponent: Component {
}
}
private final class TransactionCellComponent: Component {
let textColor: UIColor
let accentColor: UIColor
let transactionId: String
let copy: () -> Void
init(textColor: UIColor, accentColor: UIColor, transactionId: String, copy: @escaping () -> Void) {
self.textColor = textColor
self.accentColor = accentColor
self.transactionId = transactionId
self.copy = copy
}
static func ==(lhs: TransactionCellComponent, rhs: TransactionCellComponent) -> Bool {
if lhs.textColor !== rhs.textColor {
return false
}
if lhs.accentColor != rhs.accentColor {
return false
}
if lhs.transactionId != rhs.transactionId {
return false
}
return true
}
final class View: UIView {
private let text = ComponentView<Empty>()
private let button = ComponentView<Empty>()
private var component: TransactionCellComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: TransactionCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
self.state = state
let spacing: CGFloat = 6.0
let buttonSize = self.button.update(
transition: .immediate,
component: AnyComponent(
Button(
content: AnyComponent(
BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: component.accentColor)
),
action: {
component.copy()
}
)
),
environment: {},
containerSize: CGSize(width: availableSize.width, height: availableSize.height)
)
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: component.transactionId, font: Font.monospace(15.0), textColor: component.textColor, paragraphAlignment: .left)),
maximumNumberOfLines: 0,
lineSpacing: 0.2
)
),
environment: {},
containerSize: CGSize(width: availableSize.width - buttonSize.width - spacing, height: availableSize.height)
)
let size = CGSize(width: textSize.width + spacing + buttonSize.width, height: textSize.height)
let buttonFrame = CGRect(origin: CGPoint(x: textSize.width + spacing, y: floorToScreenPixels((size.height - buttonSize.height) / 2.0)), size: buttonSize)
if let buttonView = self.button.view {
if buttonView.superview == nil {
self.addSubview(buttonView)
}
transition.setFrame(view: buttonView, frame: buttonFrame)
}
let textFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
if let textView = self.text.view {
if textView.superview == nil {
self.addSubview(textView)
}
transition.setFrame(view: textView, frame: textFrame)
}
return size
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in

View File

@ -1,5 +1,5 @@
{
"app": "10.12",
"app": "10.13",
"xcode": "15.2",
"bazel": "7.1.1",
"macos": "13.0"