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" = "Fact Check";
"Message.FactCheck.WhatIsThis" = "what's this?"; "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."; "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"; "FactCheck.Title" = "Fact Check";

View File

@ -63,7 +63,9 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode {
super.init() super.init()
self.addSubnode(self.searchBar) if hasCurrentChat {
self.addSubnode(self.searchBar)
}
self.searchBar.cancel = { [weak self] in self.searchBar.cancel = { [weak self] in
self?.searchBar.deactivate(clear: false) 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) { func updateTheme(_ theme: PresentationTheme) {
self.theme = theme self.theme = theme
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: self.strings) self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: self.strings)
@ -89,7 +95,11 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode {
} }
override var nominalHeight: CGFloat { 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)? private var validLayout: (CGSize, CGFloat, CGFloat)?
@ -119,7 +129,7 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode {
), ),
customLayout: TabSelectorComponent.CustomLayout( customLayout: TabSelectorComponent.CustomLayout(
font: Font.medium(14.0), font: Font.medium(14.0),
spacing: 24.0, spacing: self.hasCurrentChat ? 24.0 : 8.0,
lineSelection: true lineSelection: true
), ),
items: items, items: items,
@ -135,7 +145,13 @@ final class HashtagSearchNavigationContentNode: NavigationBarContentNode {
environment: {}, environment: {},
containerSize: CGSize(width: size.width, height: 44.0) 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 let tabSelectorView = self.tabSelector.view {
if tabSelectorView.superview == nil { if tabSelectorView.superview == nil {
self.view.addSubview(tabSelectorView) self.view.addSubview(tabSelectorView)

View File

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

View File

@ -42,8 +42,11 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
private var titleBadgeButton: HighlightTrackingButtonNode? private var titleBadgeButton: HighlightTrackingButtonNode?
private let textClippingNode: ASDisplayNode private let textClippingNode: ASDisplayNode
private let textNode: TextNode private let textNode: TextNode
private let additionalTextNode: TextNode
private var linkHighlightingNode: LinkHighlightingNode? private var linkHighlightingNode: LinkHighlightingNode?
private let lineNode: ASDisplayNode
private var maskView: UIImageView? private var maskView: UIImageView?
private var maskOverlayView: UIView? private var maskOverlayView: UIView?
@ -55,13 +58,17 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
private var isExpanded: Bool = false private var isExpanded: Bool = false
private var appliedIsExpanded: Bool = false private var appliedIsExpanded: Bool = false
private var countryName: String?
required public init() { required public init() {
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleBadgeLabel = TextNode() self.titleBadgeLabel = TextNode()
self.textClippingNode = ASDisplayNode() self.textClippingNode = ASDisplayNode()
self.textNode = TextNode() self.textNode = TextNode()
self.additionalTextNode = TextNode()
self.expandIcon = ASImageNode() self.expandIcon = ASImageNode()
self.statusNode = ChatMessageDateAndStatusNode() self.statusNode = ChatMessageDateAndStatusNode()
self.lineNode = ASDisplayNode()
super.init() super.init()
@ -80,6 +87,14 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
self.textNode.displaysAsynchronously = false self.textNode.displaysAsynchronously = false
self.textClippingNode.addSubnode(self.textNode) 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.isUserInteractionEnabled = false
self.titleBadgeLabel.contentMode = .topLeft self.titleBadgeLabel.contentMode = .topLeft
self.titleBadgeLabel.contentsScale = UIScreenScale self.titleBadgeLabel.contentsScale = UIScreenScale
@ -106,24 +121,10 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
} }
@objc private func badgePressed() { @objc private func badgePressed() {
guard let item = self.item else { guard let item = self.item, let countryName = self.countryName else {
return 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) 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 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 { if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
var concealed = true return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: false)))
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)))
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { } else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false)) return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false))
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { } 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 titleLayout = TextNode.asyncLayout(self.titleNode)
let titleBadgeLayout = TextNode.asyncLayout(self.titleBadgeLabel) let titleBadgeLayout = TextNode.asyncLayout(self.titleBadgeLabel)
let textLayout = TextNode.asyncLayout(self.textNode) let textLayout = TextNode.asyncLayout(self.textNode)
let additionalTextLayout = TextNode.asyncLayout(self.additionalTextNode)
let measureTextLayout = TextNode.asyncLayout(nil) let measureTextLayout = TextNode.asyncLayout(nil)
let statusLayout = self.statusNode.asyncLayout() let statusLayout = self.statusNode.asyncLayout()
let currentIsExpanded = self.isExpanded let currentIsExpanded = self.isExpanded
let currentCountryName = self.countryName
return { item, layoutConstants, _, _, _, _ in return { item, layoutConstants, _, _, _, _ in
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) 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 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 (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 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)) 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 (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 canExpand = false
var clippedTextHeight: CGFloat = textLayout.size.height var clippedTextHeight: CGFloat = textLayout.size.height
if textLayout.numberOfLines > 4 { 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 canExpand = true
if !currentIsExpanded { 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) 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)) 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) 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))? var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))?
if let statusType = statusType { if let statusType = statusType {
var isReplyThread = false var isReplyThread = false
@ -377,6 +397,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue { if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0) suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0)
} }
suggestedBoundingWidth = max(suggestedBoundingWidth, additionalTextFrameWithoutInsets.width)
let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
suggestedBoundingWidth += (sideInsets - 2.0) * 2.0 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) 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 { if let statusSizeAndApply = statusSizeAndApply {
boundingSize.height += statusSizeAndApply.0.height boundingSize.height += statusSizeAndApply.0.height
} }
@ -400,7 +427,8 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
let themeUpdated = strongSelf.item?.presentationData.theme.theme !== item.presentationData.theme.theme let themeUpdated = strongSelf.item?.presentationData.theme.theme !== item.presentationData.theme.theme
strongSelf.item = item strongSelf.item = item
strongSelf.countryName = countryName
let backgroundView: MessageInlineBlockBackgroundView let backgroundView: MessageInlineBlockBackgroundView
if let current = strongSelf.backgroundView { if let current = strongSelf.backgroundView {
backgroundView = current backgroundView = current
@ -410,6 +438,10 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
strongSelf.backgroundView = backgroundView strongSelf.backgroundView = backgroundView
} }
if themeUpdated {
strongSelf.lineNode.backgroundColor = mainColor.withAlphaComponent(0.15)
}
var isExpandedUpdated = false var isExpandedUpdated = false
if strongSelf.appliedIsExpanded != currentIsExpanded { if strongSelf.appliedIsExpanded != currentIsExpanded {
strongSelf.appliedIsExpanded = currentIsExpanded strongSelf.appliedIsExpanded = currentIsExpanded
@ -464,57 +496,12 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
let _ = textApply() let _ = textApply()
strongSelf.textNode.frame = CGRect(origin: .zero, size: textFrame.size) 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 _ = additionalTextApply()
let wasHidden = strongSelf.expandIcon.isHidden strongSelf.additionalTextNode.frame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.height - textInsets.bottom + textSpacing + 1.0), size: additionalTextFrame.size)
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 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 var titleLineWidth: CGFloat = 0.0
if let firstLine = titleLayout.linesRects().first { if let firstLine = titleLayout.linesRects().first {
titleLineWidth = firstLine.width 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) 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 { if isFirstTime {
strongSelf.textClippingNode.frame = clippingTextFrame 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) 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 { if let statusSizeAndApply = statusSizeAndApply {
strongSelf.statusNode.reactionSelected = { [weak strongSelf] _, value, sourceView in strongSelf.statusNode.reactionSelected = { [weak strongSelf] _, value, sourceView in
guard let strongSelf, let item = strongSelf.item else { guard let strongSelf, let item = strongSelf.item else {
@ -594,7 +630,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value) 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 { if isFirstTime {
strongSelf.statusNode.frame = statusFrame strongSelf.statusNode.frame = statusFrame
} else { } else {

View File

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

View File

@ -29,19 +29,22 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let action: () -> Void let action: () -> Void
let cancel: (Bool) -> Void let cancel: (Bool) -> Void
let openPeer: (EnginePeer) -> Void let openPeer: (EnginePeer) -> Void
let copyTransactionId: () -> Void
init( init(
context: AccountContext, context: AccountContext,
subject: StarsTransactionScreen.Subject, subject: StarsTransactionScreen.Subject,
action: @escaping () -> Void, action: @escaping () -> Void,
cancel: @escaping (Bool) -> Void, cancel: @escaping (Bool) -> Void,
openPeer: @escaping (EnginePeer) -> Void openPeer: @escaping (EnginePeer) -> Void,
copyTransactionId: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
self.action = action self.action = action
self.cancel = cancel self.cancel = cancel
self.openPeer = openPeer self.openPeer = openPeer
self.copyTransactionId = copyTransactionId
} }
static func ==(lhs: StarsTransactionSheetContent, rhs: StarsTransactionSheetContent) -> Bool { static func ==(lhs: StarsTransactionSheetContent, rhs: StarsTransactionSheetContent) -> Bool {
@ -163,7 +166,21 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let gloss = false let gloss = false
switch subject { switch subject {
case let .transaction(transaction): 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 { if transaction.count < 0 {
descriptionText = "- \(transaction.count * -1) ⭐️" descriptionText = "- \(transaction.count * -1) ⭐️"
} else { } else {
@ -185,7 +202,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
component: MultilineTextComponent( component: MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: titleText, string: titleText,
font: Font.semibold(24.0), font: Font.bold(25.0),
textColor: theme.actionSheet.primaryTextColor, textColor: theme.actionSheet.primaryTextColor,
paragraphAlignment: .center paragraphAlignment: .center
)), )),
@ -211,16 +228,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
transition: .immediate 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( let description = description.update(
component: BalancedTextComponent( 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, horizontalAlignment: .center,
maximumNumberOfLines: 0, maximumNumberOfLines: 0,
lineSpacing: 0.2 lineSpacing: 0.2
@ -240,7 +250,13 @@ private final class StarsTransactionSheetContent: CombinedComponent {
title: strings.Stars_Transaction_Date, title: strings.Stars_Transaction_Date,
component: AnyComponent( component: AnyComponent(
Button( Button(
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: toPeer)), content: AnyComponent(
PeerCellComponent(
context: component.context,
textColor: tableLinkColor,
peer: toPeer
)
),
action: { action: {
if toPeer.id != accountContext.account.peerId { if toPeer.id != accountContext.account.peerId {
component.openPeer(toPeer) component.openPeer(toPeer)
@ -258,12 +274,16 @@ private final class StarsTransactionSheetContent: CombinedComponent {
id: "transaction", id: "transaction",
title: strings.Stars_Transaction_Id, title: strings.Stars_Transaction_Id,
component: AnyComponent( component: AnyComponent(
MultilineTextComponent( TransactionCellComponent(
text: .plain(NSAttributedString(string: transactionId, font: Font.monospace(15.0), textColor: tableTextColor)), textColor: tableTextColor,
truncationType: .end, accentColor: tableLinkColor,
maximumNumberOfLines: 0 transactionId: transactionId,
copy: {
component.copyTransactionId()
}
) )
) ),
insets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 5.0)
)) ))
tableItems.append(.init( tableItems.append(.init(
@ -283,6 +303,13 @@ private final class StarsTransactionSheetContent: CombinedComponent {
transition: .immediate 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( let additional = additional.update(
component: BalancedTextComponent( component: BalancedTextComponent(
text: .markdown(text: additionalText, attributes: markdownAttributes), text: .markdown(text: additionalText, attributes: markdownAttributes),
@ -319,16 +346,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
animationName: nil, animationName: nil,
iconPosition: .left, iconPosition: .left,
isLoading: state.inProgress, isLoading: state.inProgress,
action: { [weak state] in action: {
if gloss { component.cancel(true)
component.action()
if let state {
state.inProgress = true
state.updated()
}
} else {
component.cancel(true)
}
} }
), ),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
@ -336,7 +355,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
) )
context.add(title 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 context.add(star
@ -344,12 +363,12 @@ private final class StarsTransactionSheetContent: CombinedComponent {
) )
var originY: CGFloat = 0.0 var originY: CGFloat = 0.0
originY += star.size.height - 32.0 originY += star.size.height - 23.0
context.add(description context.add(description
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + description.size.height / 2.0)) .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 context.add(table
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0)) .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 subject: StarsTransactionScreen.Subject
let action: () -> Void let action: () -> Void
let openPeer: (EnginePeer) -> Void let openPeer: (EnginePeer) -> Void
let copyTransactionId: () -> Void
init( init(
context: AccountContext, context: AccountContext,
subject: StarsTransactionScreen.Subject, subject: StarsTransactionScreen.Subject,
action: @escaping () -> Void, action: @escaping () -> Void,
openPeer: @escaping (EnginePeer) -> Void openPeer: @escaping (EnginePeer) -> Void,
copyTransactionId: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
self.action = action self.action = action
self.openPeer = openPeer self.openPeer = openPeer
self.copyTransactionId = copyTransactionId
} }
static func ==(lhs: StarsTransactionSheetComponent, rhs: StarsTransactionSheetComponent) -> Bool { static func ==(lhs: StarsTransactionSheetComponent, rhs: StarsTransactionSheetComponent) -> Bool {
@ -411,6 +433,8 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
let sheet = Child(SheetComponent<EnvironmentType>.self) let sheet = Child(SheetComponent<EnvironmentType>.self)
let animateOut = StoredActionSlot(Action<Void>.self) let animateOut = StoredActionSlot(Action<Void>.self)
let sheetExternalState = SheetComponent<EnvironmentType>.ExternalState()
return { context in return { context in
let environment = context.environment[EnvironmentType.self] let environment = context.environment[EnvironmentType.self]
let controller = environment.controller let controller = environment.controller
@ -423,20 +447,23 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
action: context.component.action, action: context.component.action,
cancel: { animate in cancel: { animate in
if animate { if animate {
animateOut.invoke(Action { _ in if let controller = controller() as? StarsTransactionScreen {
if let controller = controller() { controller.dismissAllTooltips()
controller.dismiss(completion: nil) animateOut.invoke(Action { [weak controller] _ in
} controller?.dismiss(completion: nil)
}) })
}
} else if let controller = controller() { } else if let controller = controller() {
controller.dismiss(animated: false, completion: nil) controller.dismiss(animated: false, completion: nil)
} }
}, },
openPeer: context.component.openPeer openPeer: context.component.openPeer,
copyTransactionId: context.component.copyTransactionId
)), )),
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
followContentSizeChanges: true, followContentSizeChanges: true,
clipsContent: true, clipsContent: true,
externalState: sheetExternalState,
animateOut: animateOut animateOut: animateOut
), ),
environment: { environment: {
@ -448,13 +475,15 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
regularMetricsSize: CGSize(width: 430.0, height: 900.0), regularMetricsSize: CGSize(width: 430.0, height: 900.0),
dismiss: { animated in dismiss: { animated in
if animated { if animated {
animateOut.invoke(Action { _ in if let controller = controller() as? StarsTransactionScreen {
if let controller = controller() { controller.dismissAllTooltips()
animateOut.invoke(Action { _ in
controller.dismiss(completion: nil) controller.dismiss(completion: nil)
} })
}) }
} else { } else {
if let controller = controller() { if let controller = controller() as? StarsTransactionScreen {
controller.dismissAllTooltips()
controller.dismiss(completion: nil) 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)) .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 return context.availableSize
} }
} }
@ -493,6 +538,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
self.context = context self.context = context
var openPeerImpl: ((EnginePeer) -> Void)? var openPeerImpl: ((EnginePeer) -> Void)?
var copyTransactionIdImpl: (() -> Void)?
super.init( super.init(
context: context, context: context,
component: StarsTransactionSheetComponent( component: StarsTransactionSheetComponent(
@ -501,6 +547,9 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
action: action, action: action,
openPeer: { peerId in openPeer: { peerId in
openPeerImpl?(peerId) openPeerImpl?(peerId)
},
copyTransactionId: {
copyTransactionIdImpl?()
} }
), ),
navigationBarAppearance: .none, navigationBarAppearance: .none,
@ -509,11 +558,14 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
) )
self.navigationPresentation = .flatModal self.navigationPresentation = .flatModal
self.automaticallyControlPresentationContextLayout = false
openPeerImpl = { [weak self] peer in openPeerImpl = { [weak self] peer in
guard let self, let navigationController = self.navigationController as? NavigationController else { guard let self, let navigationController = self.navigationController as? NavigationController else {
return return
} }
self.dismissAllTooltips()
let _ = (context.engine.data.get( let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id) 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)) 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) { required public init(coder aDecoder: NSCoder) {
@ -546,6 +608,8 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
} }
public func dismissAnimated() { public func dismissAnimated() {
self.dismissAllTooltips()
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View { if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
view.dismissAnimated() view.dismissAnimated()
} }
@ -571,11 +635,13 @@ private final class TableComponent: CombinedComponent {
public let id: AnyHashable public let id: AnyHashable
public let title: String public let title: String
public let component: AnyComponent<Empty> 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.id = AnyHashable(id)
self.title = title self.title = title
self.component = component self.component = component
self.insets = insets
} }
public static func == (lhs: Item, rhs: Item) -> Bool { public static func == (lhs: Item, rhs: Item) -> Bool {
@ -588,6 +654,9 @@ private final class TableComponent: CombinedComponent {
if lhs.component != rhs.component { if lhs.component != rhs.component {
return false return false
} }
if lhs.insets != rhs.insets {
return false
}
return true return true
} }
} }
@ -637,7 +706,7 @@ private final class TableComponent: CombinedComponent {
var leftColumnWidth: CGFloat = 0.0 var leftColumnWidth: CGFloat = 0.0
var updatedTitleChildren: [_UpdatedChildComponent] = [] var updatedTitleChildren: [_UpdatedChildComponent] = []
var updatedValueChildren: [_UpdatedChildComponent] = [] var updatedValueChildren: [(_UpdatedChildComponent, UIEdgeInsets)] = []
var updatedBorderChildren: [_UpdatedChildComponent] = [] var updatedBorderChildren: [_UpdatedChildComponent] = []
for item in context.component.items { for item in context.component.items {
@ -664,12 +733,19 @@ private final class TableComponent: CombinedComponent {
for item in context.component.items { for item in context.component.items {
let titleChild = updatedTitleChildren[i] 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( let valueChild = valueChildren[item.id].update(
component: item.component, 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 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) let rowHeight = max(40.0, max(titleChild.size.height, valueChild.size.height) + verticalPadding * 2.0)
rowHeights[i] = rowHeight rowHeights[i] = rowHeight
@ -742,11 +818,11 @@ private final class TableComponent: CombinedComponent {
i = 0 i = 0
var originY: CGFloat = 0.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 rowHeight = rowHeights[i] ?? 0.0
let titleFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: titleChild.size) 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 context.add(titleChild
.position(titleFrame.center) .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? { private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in 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", "xcode": "15.2",
"bazel": "7.1.1", "bazel": "7.1.1",
"macos": "13.0" "macos": "13.0"