Various improvements

This commit is contained in:
Isaac 2026-03-18 00:27:13 +01:00
parent 67d43248da
commit a2d5c530a5
49 changed files with 4592 additions and 1028 deletions

View file

@ -1419,6 +1419,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}, displayUndo: { _ in }, displayUndo: { _ in
}, presentInputTextTranslation: { _, _ in }, presentInputTextTranslation: { _, _ in
}, sendEmoji: { _, _, _ in }, sendEmoji: { _, _, _ in
}, openAICompose: {
}, updateHistoryFilter: { _ in }, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in }, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: { }, toggleChatSidebarMode: {

View file

@ -197,6 +197,7 @@ public final class ChatPanelInterfaceInteraction {
public let displayUndo: (UndoOverlayContent) -> Void public let displayUndo: (UndoOverlayContent) -> Void
public let presentInputTextTranslation: (NSAttributedString, @escaping (NSAttributedString) -> Void) -> Void public let presentInputTextTranslation: (NSAttributedString, @escaping (NSAttributedString) -> Void) -> Void
public let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void public let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void
public let openAICompose: () -> Void
public let requestLayout: (ContainedViewLayoutTransition) -> Void public let requestLayout: (ContainedViewLayoutTransition) -> Void
public let chatController: () -> ViewController? public let chatController: () -> ViewController?
public let statuses: ChatPanelInterfaceInteractionStatuses? public let statuses: ChatPanelInterfaceInteractionStatuses?
@ -324,6 +325,7 @@ public final class ChatPanelInterfaceInteraction {
displayUndo: @escaping (UndoOverlayContent) -> Void, displayUndo: @escaping (UndoOverlayContent) -> Void,
presentInputTextTranslation: @escaping (NSAttributedString, @escaping (NSAttributedString) -> Void) -> Void, presentInputTextTranslation: @escaping (NSAttributedString, @escaping (NSAttributedString) -> Void) -> Void,
sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void, sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void,
openAICompose: @escaping () -> Void,
updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void, updateHistoryFilter: @escaping ((ChatPresentationInterfaceState.HistoryFilter?) -> ChatPresentationInterfaceState.HistoryFilter?) -> Void,
updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void, updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void,
toggleChatSidebarMode: @escaping () -> Void, toggleChatSidebarMode: @escaping () -> Void,
@ -454,6 +456,7 @@ public final class ChatPanelInterfaceInteraction {
self.displayUndo = displayUndo self.displayUndo = displayUndo
self.presentInputTextTranslation = presentInputTextTranslation self.presentInputTextTranslation = presentInputTextTranslation
self.sendEmoji = sendEmoji self.sendEmoji = sendEmoji
self.openAICompose = openAICompose
self.updateHistoryFilter = updateHistoryFilter self.updateHistoryFilter = updateHistoryFilter
self.updateChatLocationThread = updateChatLocationThread self.updateChatLocationThread = updateChatLocationThread
self.toggleChatSidebarMode = toggleChatSidebarMode self.toggleChatSidebarMode = toggleChatSidebarMode
@ -592,6 +595,7 @@ public final class ChatPanelInterfaceInteraction {
}, displayUndo: { _ in }, displayUndo: { _ in
}, presentInputTextTranslation: { _, _ in }, presentInputTextTranslation: { _, _ in
}, sendEmoji: { _, _, _ in }, sendEmoji: { _, _, _ in
}, openAICompose: {
}, updateHistoryFilter: { _ in }, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in }, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: { }, toggleChatSidebarMode: {

View file

@ -460,6 +460,12 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
topOffsetFraction = 1.0 topOffsetFraction = 1.0
} }
#if DEBUG
if "".isEmpty {
topOffsetFraction = 1.0
}
#endif
let minScale: CGFloat = itemLayout.isTablet ? 1.0 : (itemLayout.containerSize.width - 6.0 * 2.0) / itemLayout.containerSize.width let minScale: CGFloat = itemLayout.isTablet ? 1.0 : (itemLayout.containerSize.width - 6.0 * 2.0) / itemLayout.containerSize.width
let minScaledTranslation: CGFloat = itemLayout.isTablet ? 0.0 : (itemLayout.containerSize.height - itemLayout.containerSize.height * minScale) * 0.5 - 6.0 let minScaledTranslation: CGFloat = itemLayout.isTablet ? 0.0 : (itemLayout.containerSize.height - itemLayout.containerSize.height * minScale) * 0.5 - 6.0
let minScaledCornerRadius: CGFloat = itemLayout.containerCornerRadius let minScaledCornerRadius: CGFloat = itemLayout.containerCornerRadius

View file

@ -15,12 +15,21 @@ private let codeIcon: UIImage = {
}() }()
private final class TextNodeStrikethrough { private final class TextNodeStrikethrough {
enum Style {
case single
case wavy
}
let range: NSRange let range: NSRange
let frame: CGRect let frame: CGRect
let color: UIColor?
let style: Style
init(range: NSRange, frame: CGRect) { init(range: NSRange, frame: CGRect, color: UIColor?, style: Style) {
self.range = range self.range = range
self.frame = frame self.frame = frame
self.color = color
self.style = style
} }
} }
@ -205,11 +214,7 @@ public struct TextNodeCutout: Equatable {
} }
private let drawUnderlinesManually: Bool = { private let drawUnderlinesManually: Bool = {
if #available(iOS 18.0, *) { return true
return true
} else {
return false
}
}() }()
private func displayLineFrame(frame: CGRect, isRTL: Bool, boundingRect: CGRect, cutout: TextNodeCutout?) -> CGRect { private func displayLineFrame(frame: CGRect, isRTL: Bool, boundingRect: CGRect, cutout: TextNodeCutout?) -> CGRect {
@ -1568,8 +1573,8 @@ open class TextNode: ASDisplayNode, TextNodeProtocol {
size.height += line.frame.height + line.frame.height * lineSpacingFactor size.height += line.frame.height + line.frame.height * lineSpacingFactor
blockWidth = max(blockWidth, line.frame.origin.x + line.frame.width) blockWidth = max(blockWidth, line.frame.origin.x + line.frame.width)
if let range = line.range { if let lineRange = line.range {
attributedString.enumerateAttributes(in: range, options: []) { attributes, range, _ in attributedString.enumerateAttributes(in: lineRange, options: []) { attributes, range, _ in
if attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil { if attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil {
var ascent: CGFloat = 0.0 var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0 var descent: CGFloat = 0.0
@ -1600,10 +1605,11 @@ open class TextNode: ASDisplayNode, TextNodeProtocol {
addSpoiler(line: line, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) addSpoiler(line: line, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] { } else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
let clampedEnd = max(range.location, min(lineRange.location + lineRange.length, range.location + range.length))
let lowerX = floor(CTLineGetOffsetForStringIndex(line.line, range.location, nil)) let lowerX = floor(CTLineGetOffsetForStringIndex(line.line, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(line.line, range.location + range.length, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(line.line, clampedEnd, nil))
let x = lowerX < upperX ? lowerX : upperX let x = lowerX < upperX ? lowerX : upperX
line.strikethroughs.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: line.frame.height))) line.strikethroughs.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: line.frame.height), color: nil, style: .single))
} }
if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) { if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) {
@ -2041,44 +2047,47 @@ open class TextNode: ASDisplayNode, TextNodeProtocol {
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
} else if let _ = attributes[NSAttributedString.Key(rawValue: "TelegramBackground")] { } else if let _ = attributes[NSAttributedString.Key(rawValue: "TelegramBackground")] {
let clampedEnd = max(range.location, min(brokenLineRange.location + brokenLineRange.length, range.location + range.length))
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, clampedEnd, nil))
let x = lowerX < upperX ? lowerX : upperX let x = lowerX < upperX ? lowerX : upperX
backgrounds.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) backgrounds.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight), color: nil, style: .single))
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] { } else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
let clampedEnd = max(range.location, min(brokenLineRange.location + brokenLineRange.length, range.location + range.length))
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, clampedEnd, nil))
let x = lowerX < upperX ? lowerX : upperX let x = lowerX < upperX ? lowerX : upperX
strikethroughs.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) strikethroughs.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight), color: nil, style: .single))
} else if let _ = attributes[NSAttributedString.Key.underlineStyle] { } else if let underlineStyle = attributes[NSAttributedString.Key.underlineStyle] as? Int {
let clampedEnd = max(range.location, min(brokenLineRange.location + brokenLineRange.length, range.location + range.length))
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, clampedEnd, nil))
let x = lowerX < upperX ? lowerX : upperX let x = lowerX < upperX ? lowerX : upperX
underlines.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) underlines.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight), color: attributes[NSAttributedString.Key.underlineColor] as? UIColor, style: underlineStyle == NSUnderlineStyle.patternDot.rawValue ? .wavy : .single))
} else if let paragraphStyle = attributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle { } else if let paragraphStyle = attributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle {
headIndent = paragraphStyle.headIndent headIndent = paragraphStyle.headIndent
} }
if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) { if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) {
if displayEmbeddedItemsUnderSpoilers || (attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] == nil && attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] == nil) { if displayEmbeddedItemsUnderSpoilers || (attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] == nil && attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] == nil) {
var ascent: CGFloat = 0.0 var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0 var descent: CGFloat = 0.0
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil) CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
addEmbeddedItem(item: embeddedItem, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) addEmbeddedItem(item: embeddedItem, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
} }
} }
if let attachment = attributes[NSAttributedString.Key.attachment] as? UIImage { if let attachment = attributes[NSAttributedString.Key.attachment] as? UIImage {
var ascent: CGFloat = 0.0 var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0 var descent: CGFloat = 0.0
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil) CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
addAttachment(attachment: attachment, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: max(range.location, min(lineRange.location + lineRange.length, range.location + range.length)), isAtEndOfTheLine: range.location + range.length >= lineRange.location + lineRange.length - 1) addAttachment(attachment: attachment, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: max(range.location, min(lineRange.location + lineRange.length, range.location + range.length)), isAtEndOfTheLine: range.location + range.length >= lineRange.location + lineRange.length - 1)
} }
} }
} }
var lineAscent: CGFloat = 0.0 var lineAscent: CGFloat = 0.0
var lineDescent: CGFloat = 0.0 var lineDescent: CGFloat = 0.0
let lineWidth = min(lineConstrainedSize.width, ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, &lineAscent, &lineDescent, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine)))) let lineWidth = min(lineConstrainedSize.width, ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, &lineAscent, &lineDescent, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))))
@ -2168,39 +2177,42 @@ open class TextNode: ASDisplayNode, TextNodeProtocol {
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
} else if let _ = attributes[NSAttributedString.Key(rawValue: "TelegramBackground")] { } else if let _ = attributes[NSAttributedString.Key(rawValue: "TelegramBackground")] {
let clampedEnd = max(range.location, min(lineRange.location + lineRange.length, range.location + range.length))
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, clampedEnd, nil))
let x = lowerX < upperX ? lowerX : upperX let x = lowerX < upperX ? lowerX : upperX
backgrounds.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) backgrounds.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight), color: nil, style: .single))
} else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] { } else if let _ = attributes[NSAttributedString.Key.strikethroughStyle] {
let clampedEnd = max(range.location, min(lineRange.location + lineRange.length, range.location + range.length))
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, clampedEnd, nil))
let x = lowerX < upperX ? lowerX : upperX let x = lowerX < upperX ? lowerX : upperX
strikethroughs.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) strikethroughs.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight), color: nil, style: .single))
} else if let _ = attributes[NSAttributedString.Key.underlineStyle] { } else if let underlineStyle = attributes[NSAttributedString.Key.underlineStyle] as? Int {
let clampedEnd = max(range.location, min(lineRange.location + lineRange.length, range.location + range.length))
let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, clampedEnd, nil))
let x = lowerX < upperX ? lowerX : upperX let x = lowerX < upperX ? lowerX : upperX
underlines.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) underlines.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight), color: attributes[NSAttributedString.Key.underlineColor] as? UIColor, style: underlineStyle == NSUnderlineStyle.patternDot.rawValue ? .wavy : .single))
} else if let paragraphStyle = attributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle { } else if let paragraphStyle = attributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle {
headIndent = paragraphStyle.headIndent headIndent = paragraphStyle.headIndent
} }
if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) { if let embeddedItem = (attributes[NSAttributedString.Key(rawValue: "TelegramEmbeddedItem")] as? AnyHashable ?? attributes[NSAttributedString.Key(rawValue: "Attribute__EmbeddedItem")] as? AnyHashable) {
if displayEmbeddedItemsUnderSpoilers || (attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] == nil && attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] == nil) { if displayEmbeddedItemsUnderSpoilers || (attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] == nil && attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] == nil) {
var ascent: CGFloat = 0.0 var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0 var descent: CGFloat = 0.0
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil) CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
addEmbeddedItem(item: embeddedItem, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length) addEmbeddedItem(item: embeddedItem, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
} }
} }
if let attachment = attributes[NSAttributedString.Key.attachment] as? UIImage { if let attachment = attributes[NSAttributedString.Key.attachment] as? UIImage {
var ascent: CGFloat = 0.0 var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0 var descent: CGFloat = 0.0
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil) CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
addAttachment(attachment: attachment, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: max(range.location, min(lineRange.location + lineRange.length, range.location + range.length)), isAtEndOfTheLine: range.location + range.length >= lineRange.location + lineRange.length - 1) addAttachment(attachment: attachment, line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: max(range.location, min(lineRange.location + lineRange.length, range.location + range.length)), isAtEndOfTheLine: range.location + range.length >= lineRange.location + lineRange.length - 1)
} }
} }
@ -2605,22 +2617,59 @@ open class TextNode: ASDisplayNode, TextNodeProtocol {
} }
if drawUnderlinesManually { if drawUnderlinesManually {
if !line.underlines.isEmpty { for strikethrough in line.underlines {
for strikethrough in line.underlines { guard let lineRange = line.range else {
guard let lineRange = line.range else { continue
continue }
var textColor: UIColor?
layout.attributedString?.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in
if range == strikethrough.range, let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor {
textColor = color
} }
var textColor: UIColor? }
layout.attributedString?.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in switch strikethrough.style {
if range == strikethrough.range, let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor { case .single:
textColor = color if let color = strikethrough.color {
} context.setFillColor(color.cgColor)
} } else if let textColor {
if let textColor = textColor {
context.setFillColor(textColor.cgColor) context.setFillColor(textColor.cgColor)
} }
let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY) let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
context.fill(CGRect(x: frame.minX, y: frame.minY + 1.0, width: frame.width, height: 1.0)) context.fill(CGRect(x: frame.minX, y: frame.minY + 1.0, width: frame.width, height: 1.0))
case .wavy:
if let color = strikethrough.color {
context.setStrokeColor(color.cgColor)
} else if let textColor {
context.setStrokeColor(textColor.cgColor)
}
context.setLineWidth(1.33)
context.setLineCap(.round)
context.setLineJoin(.round)
let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY - 6.0)
let amplitude: CGFloat = 1.2
let period: CGFloat = 8.0
let phase: CGFloat = -0.5
let midY = frame.midY
let step: CGFloat = 1.0
context.saveGState()
context.clip(to: frame)
var x = frame.minX
context.move(to: CGPoint(x: x, y: midY + amplitude * sin(phase)))
x += step
while x <= frame.maxX + step {
let y = midY + amplitude * sin((x - frame.minX) * 2.0 * .pi / period + phase)
context.addLine(to: CGPoint(x: x, y: y))
x += step
}
context.strokePath()
context.restoreGState()
/*context.setFillColor(UIColor.red.cgColor)
let frame1 = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
context.fill(CGRect(x: frame1.minX, y: frame1.minY + 1.0, width: frame1.width, height: 1.0))*/
} }
} }
} }

View file

@ -238,7 +238,7 @@ final class TabBarControllerNode: ASDisplayNode {
let index = self.tabBarItems.firstIndex(where: { AnyHashable(ObjectIdentifier($0.item)) == itemId }) ?? 0 let index = self.tabBarItems.firstIndex(where: { AnyHashable(ObjectIdentifier($0.item)) == itemId }) ?? 0
return TabBarComponent.Item( return TabBarComponent.Item(
item: item.item, content: .tabBarItem(item.item),
action: { [weak self] isLongTap in action: { [weak self] isLongTap in
guard let self else { guard let self else {
return return

View file

@ -654,6 +654,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1280209983] = { return Api.MessageEntity.parse_messageEntityCashtag($0) } dict[1280209983] = { return Api.MessageEntity.parse_messageEntityCashtag($0) }
dict[681706865] = { return Api.MessageEntity.parse_messageEntityCode($0) } dict[681706865] = { return Api.MessageEntity.parse_messageEntityCode($0) }
dict[-925956616] = { return Api.MessageEntity.parse_messageEntityCustomEmoji($0) } dict[-925956616] = { return Api.MessageEntity.parse_messageEntityCustomEmoji($0) }
dict[106086853] = { return Api.MessageEntity.parse_messageEntityDiffDelete($0) }
dict[1903653142] = { return Api.MessageEntity.parse_messageEntityDiffInsert($0) }
dict[-960371289] = { return Api.MessageEntity.parse_messageEntityDiffReplace($0) }
dict[1692693954] = { return Api.MessageEntity.parse_messageEntityEmail($0) } dict[1692693954] = { return Api.MessageEntity.parse_messageEntityEmail($0) }
dict[-1874147385] = { return Api.MessageEntity.parse_messageEntityFormattedDate($0) } dict[-1874147385] = { return Api.MessageEntity.parse_messageEntityFormattedDate($0) }
dict[1868782349] = { return Api.MessageEntity.parse_messageEntityHashtag($0) } dict[1868782349] = { return Api.MessageEntity.parse_messageEntityHashtag($0) }
@ -1357,7 +1360,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1012971041] = { return Api.bots.ExportedBotToken.parse_exportedBotToken($0) } dict[1012971041] = { return Api.bots.ExportedBotToken.parse_exportedBotToken($0) }
dict[428978491] = { return Api.bots.PopularAppBots.parse_popularAppBots($0) } dict[428978491] = { return Api.bots.PopularAppBots.parse_popularAppBots($0) }
dict[212278628] = { return Api.bots.PreviewInfo.parse_previewInfo($0) } dict[212278628] = { return Api.bots.PreviewInfo.parse_previewInfo($0) }
dict[569994407] = { return Api.bots.RequestedButton.parse_requestedButton($0) } dict[-247743273] = { return Api.bots.RequestedButton.parse_requestedButton($0) }
dict[-309659827] = { return Api.channels.AdminLogResults.parse_adminLogResults($0) } dict[-309659827] = { return Api.channels.AdminLogResults.parse_adminLogResults($0) }
dict[-541588713] = { return Api.channels.ChannelParticipant.parse_channelParticipant($0) } dict[-541588713] = { return Api.channels.ChannelParticipant.parse_channelParticipant($0) }
dict[-1699676497] = { return Api.channels.ChannelParticipants.parse_channelParticipants($0) } dict[-1699676497] = { return Api.channels.ChannelParticipants.parse_channelParticipants($0) }
@ -1436,6 +1439,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1694474197] = { return Api.messages.Chats.parse_chats($0) } dict[1694474197] = { return Api.messages.Chats.parse_chats($0) }
dict[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($0) } dict[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($0) }
dict[-1571952873] = { return Api.messages.CheckedHistoryImportPeer.parse_checkedHistoryImportPeer($0) } dict[-1571952873] = { return Api.messages.CheckedHistoryImportPeer.parse_checkedHistoryImportPeer($0) }
dict[-1864913414] = { return Api.messages.ComposedMessageWithAI.parse_composedMessageWithAI($0) }
dict[740433629] = { return Api.messages.DhConfig.parse_dhConfig($0) } dict[740433629] = { return Api.messages.DhConfig.parse_dhConfig($0) }
dict[-1058912715] = { return Api.messages.DhConfig.parse_dhConfigNotModified($0) } dict[-1058912715] = { return Api.messages.DhConfig.parse_dhConfigNotModified($0) }
dict[718878489] = { return Api.messages.DialogFilters.parse_dialogFilters($0) } dict[718878489] = { return Api.messages.DialogFilters.parse_dialogFilters($0) }
@ -2601,6 +2605,8 @@ public extension Api {
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.messages.CheckedHistoryImportPeer: case let _1 as Api.messages.CheckedHistoryImportPeer:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.messages.ComposedMessageWithAI:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.DhConfig: case let _1 as Api.messages.DhConfig:
_1.serialize(buffer, boxed) _1.serialize(buffer, boxed)
case let _1 as Api.messages.DialogFilters: case let _1 as Api.messages.DialogFilters:

View file

@ -94,6 +94,41 @@ public extension Api {
return ("messageEntityCustomEmoji", [("offset", self.offset as Any), ("length", self.length as Any), ("documentId", self.documentId as Any)]) return ("messageEntityCustomEmoji", [("offset", self.offset as Any), ("length", self.length as Any), ("documentId", self.documentId as Any)])
} }
} }
public class Cons_messageEntityDiffDelete: TypeConstructorDescription {
public var offset: Int32
public var length: Int32
public init(offset: Int32, length: Int32) {
self.offset = offset
self.length = length
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("messageEntityDiffDelete", [("offset", self.offset as Any), ("length", self.length as Any)])
}
}
public class Cons_messageEntityDiffInsert: TypeConstructorDescription {
public var offset: Int32
public var length: Int32
public init(offset: Int32, length: Int32) {
self.offset = offset
self.length = length
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("messageEntityDiffInsert", [("offset", self.offset as Any), ("length", self.length as Any)])
}
}
public class Cons_messageEntityDiffReplace: TypeConstructorDescription {
public var offset: Int32
public var length: Int32
public var oldText: String
public init(offset: Int32, length: Int32, oldText: String) {
self.offset = offset
self.length = length
self.oldText = oldText
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("messageEntityDiffReplace", [("offset", self.offset as Any), ("length", self.length as Any), ("oldText", self.oldText as Any)])
}
}
public class Cons_messageEntityEmail: TypeConstructorDescription { public class Cons_messageEntityEmail: TypeConstructorDescription {
public var offset: Int32 public var offset: Int32
public var length: Int32 public var length: Int32
@ -266,6 +301,9 @@ public extension Api {
case messageEntityCashtag(Cons_messageEntityCashtag) case messageEntityCashtag(Cons_messageEntityCashtag)
case messageEntityCode(Cons_messageEntityCode) case messageEntityCode(Cons_messageEntityCode)
case messageEntityCustomEmoji(Cons_messageEntityCustomEmoji) case messageEntityCustomEmoji(Cons_messageEntityCustomEmoji)
case messageEntityDiffDelete(Cons_messageEntityDiffDelete)
case messageEntityDiffInsert(Cons_messageEntityDiffInsert)
case messageEntityDiffReplace(Cons_messageEntityDiffReplace)
case messageEntityEmail(Cons_messageEntityEmail) case messageEntityEmail(Cons_messageEntityEmail)
case messageEntityFormattedDate(Cons_messageEntityFormattedDate) case messageEntityFormattedDate(Cons_messageEntityFormattedDate)
case messageEntityHashtag(Cons_messageEntityHashtag) case messageEntityHashtag(Cons_messageEntityHashtag)
@ -342,6 +380,28 @@ public extension Api {
serializeInt32(_data.length, buffer: buffer, boxed: false) serializeInt32(_data.length, buffer: buffer, boxed: false)
serializeInt64(_data.documentId, buffer: buffer, boxed: false) serializeInt64(_data.documentId, buffer: buffer, boxed: false)
break break
case .messageEntityDiffDelete(let _data):
if boxed {
buffer.appendInt32(106086853)
}
serializeInt32(_data.offset, buffer: buffer, boxed: false)
serializeInt32(_data.length, buffer: buffer, boxed: false)
break
case .messageEntityDiffInsert(let _data):
if boxed {
buffer.appendInt32(1903653142)
}
serializeInt32(_data.offset, buffer: buffer, boxed: false)
serializeInt32(_data.length, buffer: buffer, boxed: false)
break
case .messageEntityDiffReplace(let _data):
if boxed {
buffer.appendInt32(-960371289)
}
serializeInt32(_data.offset, buffer: buffer, boxed: false)
serializeInt32(_data.length, buffer: buffer, boxed: false)
serializeString(_data.oldText, buffer: buffer, boxed: false)
break
case .messageEntityEmail(let _data): case .messageEntityEmail(let _data):
if boxed { if boxed {
buffer.appendInt32(1692693954) buffer.appendInt32(1692693954)
@ -466,6 +526,12 @@ public extension Api {
return ("messageEntityCode", [("offset", _data.offset as Any), ("length", _data.length as Any)]) return ("messageEntityCode", [("offset", _data.offset as Any), ("length", _data.length as Any)])
case .messageEntityCustomEmoji(let _data): case .messageEntityCustomEmoji(let _data):
return ("messageEntityCustomEmoji", [("offset", _data.offset as Any), ("length", _data.length as Any), ("documentId", _data.documentId as Any)]) return ("messageEntityCustomEmoji", [("offset", _data.offset as Any), ("length", _data.length as Any), ("documentId", _data.documentId as Any)])
case .messageEntityDiffDelete(let _data):
return ("messageEntityDiffDelete", [("offset", _data.offset as Any), ("length", _data.length as Any)])
case .messageEntityDiffInsert(let _data):
return ("messageEntityDiffInsert", [("offset", _data.offset as Any), ("length", _data.length as Any)])
case .messageEntityDiffReplace(let _data):
return ("messageEntityDiffReplace", [("offset", _data.offset as Any), ("length", _data.length as Any), ("oldText", _data.oldText as Any)])
case .messageEntityEmail(let _data): case .messageEntityEmail(let _data):
return ("messageEntityEmail", [("offset", _data.offset as Any), ("length", _data.length as Any)]) return ("messageEntityEmail", [("offset", _data.offset as Any), ("length", _data.length as Any)])
case .messageEntityFormattedDate(let _data): case .messageEntityFormattedDate(let _data):
@ -620,6 +686,51 @@ public extension Api {
return nil return nil
} }
} }
public static func parse_messageEntityDiffDelete(_ reader: BufferReader) -> MessageEntity? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageEntity.messageEntityDiffDelete(Cons_messageEntityDiffDelete(offset: _1!, length: _2!))
}
else {
return nil
}
}
public static func parse_messageEntityDiffInsert(_ reader: BufferReader) -> MessageEntity? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageEntity.messageEntityDiffInsert(Cons_messageEntityDiffInsert(offset: _1!, length: _2!))
}
else {
return nil
}
}
public static func parse_messageEntityDiffReplace(_ reader: BufferReader) -> MessageEntity? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.MessageEntity.messageEntityDiffReplace(Cons_messageEntityDiffReplace(offset: _1!, length: _2!, oldText: _3!))
}
else {
return nil
}
}
public static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { public static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _1 = reader.readInt32()

View file

@ -229,12 +229,12 @@ public extension Api.bots {
public extension Api.bots { public extension Api.bots {
enum RequestedButton: TypeConstructorDescription { enum RequestedButton: TypeConstructorDescription {
public class Cons_requestedButton: TypeConstructorDescription { public class Cons_requestedButton: TypeConstructorDescription {
public var requestId: String public var webappReqId: String
public init(requestId: String) { public init(webappReqId: String) {
self.requestId = requestId self.webappReqId = webappReqId
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
return ("requestedButton", [("requestId", self.requestId as Any)]) return ("requestedButton", [("webappReqId", self.webappReqId as Any)])
} }
} }
case requestedButton(Cons_requestedButton) case requestedButton(Cons_requestedButton)
@ -243,9 +243,9 @@ public extension Api.bots {
switch self { switch self {
case .requestedButton(let _data): case .requestedButton(let _data):
if boxed { if boxed {
buffer.appendInt32(569994407) buffer.appendInt32(-247743273)
} }
serializeString(_data.requestId, buffer: buffer, boxed: false) serializeString(_data.webappReqId, buffer: buffer, boxed: false)
break break
} }
} }
@ -253,7 +253,7 @@ public extension Api.bots {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .requestedButton(let _data): case .requestedButton(let _data):
return ("requestedButton", [("requestId", _data.requestId as Any)]) return ("requestedButton", [("webappReqId", _data.webappReqId as Any)])
} }
} }
@ -262,7 +262,7 @@ public extension Api.bots {
_1 = parseString(reader) _1 = parseString(reader)
let _c1 = _1 != nil let _c1 = _1 != nil
if _c1 { if _c1 {
return Api.bots.RequestedButton.requestedButton(Cons_requestedButton(requestId: _1!)) return Api.bots.RequestedButton.requestedButton(Cons_requestedButton(webappReqId: _1!))
} }
else { else {
return nil return nil

View file

@ -994,6 +994,70 @@ public extension Api.messages {
} }
} }
} }
public extension Api.messages {
enum ComposedMessageWithAI: TypeConstructorDescription {
public class Cons_composedMessageWithAI: TypeConstructorDescription {
public var flags: Int32
public var resultText: Api.TextWithEntities
public var diffText: Api.TextWithEntities?
public init(flags: Int32, resultText: Api.TextWithEntities, diffText: Api.TextWithEntities?) {
self.flags = flags
self.resultText = resultText
self.diffText = diffText
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("composedMessageWithAI", [("flags", self.flags as Any), ("resultText", self.resultText as Any), ("diffText", self.diffText as Any)])
}
}
case composedMessageWithAI(Cons_composedMessageWithAI)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .composedMessageWithAI(let _data):
if boxed {
buffer.appendInt32(-1864913414)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
_data.resultText.serialize(buffer, true)
if Int(_data.flags) & Int(1 << 0) != 0 {
_data.diffText!.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .composedMessageWithAI(let _data):
return ("composedMessageWithAI", [("flags", _data.flags as Any), ("resultText", _data.resultText as Any), ("diffText", _data.diffText as Any)])
}
}
public static func parse_composedMessageWithAI(_ reader: BufferReader) -> ComposedMessageWithAI? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.TextWithEntities?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
}
var _3: Api.TextWithEntities?
if Int(_1!) & Int(1 << 0) != 0 {
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
}
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.messages.ComposedMessageWithAI.composedMessageWithAI(Cons_composedMessageWithAI(flags: _1!, resultText: _2!, diffText: _3))
}
else {
return nil
}
}
}
}
public extension Api.messages { public extension Api.messages {
enum DhConfig: TypeConstructorDescription { enum DhConfig: TypeConstructorDescription {
public class Cons_dhConfig: TypeConstructorDescription { public class Cons_dhConfig: TypeConstructorDescription {
@ -1667,112 +1731,3 @@ public extension Api.messages {
} }
} }
} }
public extension Api.messages {
enum ExportedChatInvite: TypeConstructorDescription {
public class Cons_exportedChatInvite: TypeConstructorDescription {
public var invite: Api.ExportedChatInvite
public var users: [Api.User]
public init(invite: Api.ExportedChatInvite, users: [Api.User]) {
self.invite = invite
self.users = users
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("exportedChatInvite", [("invite", self.invite as Any), ("users", self.users as Any)])
}
}
public class Cons_exportedChatInviteReplaced: TypeConstructorDescription {
public var invite: Api.ExportedChatInvite
public var newInvite: Api.ExportedChatInvite
public var users: [Api.User]
public init(invite: Api.ExportedChatInvite, newInvite: Api.ExportedChatInvite, users: [Api.User]) {
self.invite = invite
self.newInvite = newInvite
self.users = users
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("exportedChatInviteReplaced", [("invite", self.invite as Any), ("newInvite", self.newInvite as Any), ("users", self.users as Any)])
}
}
case exportedChatInvite(Cons_exportedChatInvite)
case exportedChatInviteReplaced(Cons_exportedChatInviteReplaced)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .exportedChatInvite(let _data):
if boxed {
buffer.appendInt32(410107472)
}
_data.invite.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.users.count))
for item in _data.users {
item.serialize(buffer, true)
}
break
case .exportedChatInviteReplaced(let _data):
if boxed {
buffer.appendInt32(572915951)
}
_data.invite.serialize(buffer, true)
_data.newInvite.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.users.count))
for item in _data.users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .exportedChatInvite(let _data):
return ("exportedChatInvite", [("invite", _data.invite as Any), ("users", _data.users as Any)])
case .exportedChatInviteReplaced(let _data):
return ("exportedChatInviteReplaced", [("invite", _data.invite as Any), ("newInvite", _data.newInvite as Any), ("users", _data.users as Any)])
}
}
public static func parse_exportedChatInvite(_ reader: BufferReader) -> ExportedChatInvite? {
var _1: Api.ExportedChatInvite?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
}
var _2: [Api.User]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.messages.ExportedChatInvite.exportedChatInvite(Cons_exportedChatInvite(invite: _1!, users: _2!))
}
else {
return nil
}
}
public static func parse_exportedChatInviteReplaced(_ reader: BufferReader) -> ExportedChatInvite? {
var _1: Api.ExportedChatInvite?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
}
var _2: Api.ExportedChatInvite?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
}
var _3: [Api.User]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.messages.ExportedChatInvite.exportedChatInviteReplaced(Cons_exportedChatInviteReplaced(invite: _1!, newInvite: _2!, users: _3!))
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,112 @@
public extension Api.messages {
enum ExportedChatInvite: TypeConstructorDescription {
public class Cons_exportedChatInvite: TypeConstructorDescription {
public var invite: Api.ExportedChatInvite
public var users: [Api.User]
public init(invite: Api.ExportedChatInvite, users: [Api.User]) {
self.invite = invite
self.users = users
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("exportedChatInvite", [("invite", self.invite as Any), ("users", self.users as Any)])
}
}
public class Cons_exportedChatInviteReplaced: TypeConstructorDescription {
public var invite: Api.ExportedChatInvite
public var newInvite: Api.ExportedChatInvite
public var users: [Api.User]
public init(invite: Api.ExportedChatInvite, newInvite: Api.ExportedChatInvite, users: [Api.User]) {
self.invite = invite
self.newInvite = newInvite
self.users = users
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("exportedChatInviteReplaced", [("invite", self.invite as Any), ("newInvite", self.newInvite as Any), ("users", self.users as Any)])
}
}
case exportedChatInvite(Cons_exportedChatInvite)
case exportedChatInviteReplaced(Cons_exportedChatInviteReplaced)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .exportedChatInvite(let _data):
if boxed {
buffer.appendInt32(410107472)
}
_data.invite.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.users.count))
for item in _data.users {
item.serialize(buffer, true)
}
break
case .exportedChatInviteReplaced(let _data):
if boxed {
buffer.appendInt32(572915951)
}
_data.invite.serialize(buffer, true)
_data.newInvite.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.users.count))
for item in _data.users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .exportedChatInvite(let _data):
return ("exportedChatInvite", [("invite", _data.invite as Any), ("users", _data.users as Any)])
case .exportedChatInviteReplaced(let _data):
return ("exportedChatInviteReplaced", [("invite", _data.invite as Any), ("newInvite", _data.newInvite as Any), ("users", _data.users as Any)])
}
}
public static func parse_exportedChatInvite(_ reader: BufferReader) -> ExportedChatInvite? {
var _1: Api.ExportedChatInvite?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
}
var _2: [Api.User]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.messages.ExportedChatInvite.exportedChatInvite(Cons_exportedChatInvite(invite: _1!, users: _2!))
}
else {
return nil
}
}
public static func parse_exportedChatInviteReplaced(_ reader: BufferReader) -> ExportedChatInvite? {
var _1: Api.ExportedChatInvite?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
}
var _2: Api.ExportedChatInvite?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
}
var _3: [Api.User]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.messages.ExportedChatInvite.exportedChatInviteReplaced(Cons_exportedChatInviteReplaced(invite: _1!, newInvite: _2!, users: _3!))
}
else {
return nil
}
}
}
}
public extension Api.messages { public extension Api.messages {
enum ExportedChatInvites: TypeConstructorDescription { enum ExportedChatInvites: TypeConstructorDescription {
public class Cons_exportedChatInvites: TypeConstructorDescription { public class Cons_exportedChatInvites: TypeConstructorDescription {
@ -1837,94 +1946,3 @@ public extension Api.messages {
} }
} }
} }
public extension Api.messages {
enum RecentStickers: TypeConstructorDescription {
public class Cons_recentStickers: TypeConstructorDescription {
public var hash: Int64
public var packs: [Api.StickerPack]
public var stickers: [Api.Document]
public var dates: [Int32]
public init(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32]) {
self.hash = hash
self.packs = packs
self.stickers = stickers
self.dates = dates
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("recentStickers", [("hash", self.hash as Any), ("packs", self.packs as Any), ("stickers", self.stickers as Any), ("dates", self.dates as Any)])
}
}
case recentStickers(Cons_recentStickers)
case recentStickersNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .recentStickers(let _data):
if boxed {
buffer.appendInt32(-1999405994)
}
serializeInt64(_data.hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.packs.count))
for item in _data.packs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.stickers.count))
for item in _data.stickers {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.dates.count))
for item in _data.dates {
serializeInt32(item, buffer: buffer, boxed: false)
}
break
case .recentStickersNotModified:
if boxed {
buffer.appendInt32(186120336)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .recentStickers(let _data):
return ("recentStickers", [("hash", _data.hash as Any), ("packs", _data.packs as Any), ("stickers", _data.stickers as Any), ("dates", _data.dates as Any)])
case .recentStickersNotModified:
return ("recentStickersNotModified", [])
}
}
public static func parse_recentStickers(_ reader: BufferReader) -> RecentStickers? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.StickerPack]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
}
var _3: [Api.Document]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
var _4: [Int32]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.messages.RecentStickers.recentStickers(Cons_recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!))
}
else {
return nil
}
}
public static func parse_recentStickersNotModified(_ reader: BufferReader) -> RecentStickers? {
return Api.messages.RecentStickers.recentStickersNotModified
}
}
}

View file

@ -1,3 +1,94 @@
public extension Api.messages {
enum RecentStickers: TypeConstructorDescription {
public class Cons_recentStickers: TypeConstructorDescription {
public var hash: Int64
public var packs: [Api.StickerPack]
public var stickers: [Api.Document]
public var dates: [Int32]
public init(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32]) {
self.hash = hash
self.packs = packs
self.stickers = stickers
self.dates = dates
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("recentStickers", [("hash", self.hash as Any), ("packs", self.packs as Any), ("stickers", self.stickers as Any), ("dates", self.dates as Any)])
}
}
case recentStickers(Cons_recentStickers)
case recentStickersNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .recentStickers(let _data):
if boxed {
buffer.appendInt32(-1999405994)
}
serializeInt64(_data.hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.packs.count))
for item in _data.packs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.stickers.count))
for item in _data.stickers {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.dates.count))
for item in _data.dates {
serializeInt32(item, buffer: buffer, boxed: false)
}
break
case .recentStickersNotModified:
if boxed {
buffer.appendInt32(186120336)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .recentStickers(let _data):
return ("recentStickers", [("hash", _data.hash as Any), ("packs", _data.packs as Any), ("stickers", _data.stickers as Any), ("dates", _data.dates as Any)])
case .recentStickersNotModified:
return ("recentStickersNotModified", [])
}
}
public static func parse_recentStickers(_ reader: BufferReader) -> RecentStickers? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.StickerPack]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
}
var _3: [Api.Document]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
var _4: [Int32]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.messages.RecentStickers.recentStickers(Cons_recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!))
}
else {
return nil
}
}
public static func parse_recentStickersNotModified(_ reader: BufferReader) -> RecentStickers? {
return Api.messages.RecentStickers.recentStickersNotModified
}
}
}
public extension Api.messages { public extension Api.messages {
enum SavedDialogs: TypeConstructorDescription { enum SavedDialogs: TypeConstructorDescription {
public class Cons_savedDialogs: TypeConstructorDescription { public class Cons_savedDialogs: TypeConstructorDescription {
@ -1646,204 +1737,3 @@ public extension Api.payments {
} }
} }
} }
public extension Api.payments {
enum ExportedInvoice: TypeConstructorDescription {
public class Cons_exportedInvoice: TypeConstructorDescription {
public var url: String
public init(url: String) {
self.url = url
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("exportedInvoice", [("url", self.url as Any)])
}
}
case exportedInvoice(Cons_exportedInvoice)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .exportedInvoice(let _data):
if boxed {
buffer.appendInt32(-1362048039)
}
serializeString(_data.url, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .exportedInvoice(let _data):
return ("exportedInvoice", [("url", _data.url as Any)])
}
}
public static func parse_exportedInvoice(_ reader: BufferReader) -> ExportedInvoice? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.payments.ExportedInvoice.exportedInvoice(Cons_exportedInvoice(url: _1!))
}
else {
return nil
}
}
}
}
public extension Api.payments {
enum GiveawayInfo: TypeConstructorDescription {
public class Cons_giveawayInfo: TypeConstructorDescription {
public var flags: Int32
public var startDate: Int32
public var joinedTooEarlyDate: Int32?
public var adminDisallowedChatId: Int64?
public var disallowedCountry: String?
public init(flags: Int32, startDate: Int32, joinedTooEarlyDate: Int32?, adminDisallowedChatId: Int64?, disallowedCountry: String?) {
self.flags = flags
self.startDate = startDate
self.joinedTooEarlyDate = joinedTooEarlyDate
self.adminDisallowedChatId = adminDisallowedChatId
self.disallowedCountry = disallowedCountry
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("giveawayInfo", [("flags", self.flags as Any), ("startDate", self.startDate as Any), ("joinedTooEarlyDate", self.joinedTooEarlyDate as Any), ("adminDisallowedChatId", self.adminDisallowedChatId as Any), ("disallowedCountry", self.disallowedCountry as Any)])
}
}
public class Cons_giveawayInfoResults: TypeConstructorDescription {
public var flags: Int32
public var startDate: Int32
public var giftCodeSlug: String?
public var starsPrize: Int64?
public var finishDate: Int32
public var winnersCount: Int32
public var activatedCount: Int32?
public init(flags: Int32, startDate: Int32, giftCodeSlug: String?, starsPrize: Int64?, finishDate: Int32, winnersCount: Int32, activatedCount: Int32?) {
self.flags = flags
self.startDate = startDate
self.giftCodeSlug = giftCodeSlug
self.starsPrize = starsPrize
self.finishDate = finishDate
self.winnersCount = winnersCount
self.activatedCount = activatedCount
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("giveawayInfoResults", [("flags", self.flags as Any), ("startDate", self.startDate as Any), ("giftCodeSlug", self.giftCodeSlug as Any), ("starsPrize", self.starsPrize as Any), ("finishDate", self.finishDate as Any), ("winnersCount", self.winnersCount as Any), ("activatedCount", self.activatedCount as Any)])
}
}
case giveawayInfo(Cons_giveawayInfo)
case giveawayInfoResults(Cons_giveawayInfoResults)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .giveawayInfo(let _data):
if boxed {
buffer.appendInt32(1130879648)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
serializeInt32(_data.startDate, buffer: buffer, boxed: false)
if Int(_data.flags) & Int(1 << 1) != 0 {
serializeInt32(_data.joinedTooEarlyDate!, buffer: buffer, boxed: false)
}
if Int(_data.flags) & Int(1 << 2) != 0 {
serializeInt64(_data.adminDisallowedChatId!, buffer: buffer, boxed: false)
}
if Int(_data.flags) & Int(1 << 4) != 0 {
serializeString(_data.disallowedCountry!, buffer: buffer, boxed: false)
}
break
case .giveawayInfoResults(let _data):
if boxed {
buffer.appendInt32(-512366993)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
serializeInt32(_data.startDate, buffer: buffer, boxed: false)
if Int(_data.flags) & Int(1 << 3) != 0 {
serializeString(_data.giftCodeSlug!, buffer: buffer, boxed: false)
}
if Int(_data.flags) & Int(1 << 4) != 0 {
serializeInt64(_data.starsPrize!, buffer: buffer, boxed: false)
}
serializeInt32(_data.finishDate, buffer: buffer, boxed: false)
serializeInt32(_data.winnersCount, buffer: buffer, boxed: false)
if Int(_data.flags) & Int(1 << 2) != 0 {
serializeInt32(_data.activatedCount!, buffer: buffer, boxed: false)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .giveawayInfo(let _data):
return ("giveawayInfo", [("flags", _data.flags as Any), ("startDate", _data.startDate as Any), ("joinedTooEarlyDate", _data.joinedTooEarlyDate as Any), ("adminDisallowedChatId", _data.adminDisallowedChatId as Any), ("disallowedCountry", _data.disallowedCountry as Any)])
case .giveawayInfoResults(let _data):
return ("giveawayInfoResults", [("flags", _data.flags as Any), ("startDate", _data.startDate as Any), ("giftCodeSlug", _data.giftCodeSlug as Any), ("starsPrize", _data.starsPrize as Any), ("finishDate", _data.finishDate as Any), ("winnersCount", _data.winnersCount as Any), ("activatedCount", _data.activatedCount as Any)])
}
}
public static func parse_giveawayInfo(_ reader: BufferReader) -> GiveawayInfo? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {
_3 = reader.readInt32()
}
var _4: Int64?
if Int(_1!) & Int(1 << 2) != 0 {
_4 = reader.readInt64()
}
var _5: String?
if Int(_1!) & Int(1 << 4) != 0 {
_5 = parseString(reader)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.payments.GiveawayInfo.giveawayInfo(Cons_giveawayInfo(flags: _1!, startDate: _2!, joinedTooEarlyDate: _3, adminDisallowedChatId: _4, disallowedCountry: _5))
}
else {
return nil
}
}
public static func parse_giveawayInfoResults(_ reader: BufferReader) -> GiveawayInfo? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
if Int(_1!) & Int(1 << 3) != 0 {
_3 = parseString(reader)
}
var _4: Int64?
if Int(_1!) & Int(1 << 4) != 0 {
_4 = reader.readInt64()
}
var _5: Int32?
_5 = reader.readInt32()
var _6: Int32?
_6 = reader.readInt32()
var _7: Int32?
if Int(_1!) & Int(1 << 2) != 0 {
_7 = reader.readInt32()
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.payments.GiveawayInfo.giveawayInfoResults(Cons_giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, starsPrize: _4, finishDate: _5!, winnersCount: _6!, activatedCount: _7))
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,204 @@
public extension Api.payments {
enum ExportedInvoice: TypeConstructorDescription {
public class Cons_exportedInvoice: TypeConstructorDescription {
public var url: String
public init(url: String) {
self.url = url
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("exportedInvoice", [("url", self.url as Any)])
}
}
case exportedInvoice(Cons_exportedInvoice)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .exportedInvoice(let _data):
if boxed {
buffer.appendInt32(-1362048039)
}
serializeString(_data.url, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .exportedInvoice(let _data):
return ("exportedInvoice", [("url", _data.url as Any)])
}
}
public static func parse_exportedInvoice(_ reader: BufferReader) -> ExportedInvoice? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.payments.ExportedInvoice.exportedInvoice(Cons_exportedInvoice(url: _1!))
}
else {
return nil
}
}
}
}
public extension Api.payments {
enum GiveawayInfo: TypeConstructorDescription {
public class Cons_giveawayInfo: TypeConstructorDescription {
public var flags: Int32
public var startDate: Int32
public var joinedTooEarlyDate: Int32?
public var adminDisallowedChatId: Int64?
public var disallowedCountry: String?
public init(flags: Int32, startDate: Int32, joinedTooEarlyDate: Int32?, adminDisallowedChatId: Int64?, disallowedCountry: String?) {
self.flags = flags
self.startDate = startDate
self.joinedTooEarlyDate = joinedTooEarlyDate
self.adminDisallowedChatId = adminDisallowedChatId
self.disallowedCountry = disallowedCountry
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("giveawayInfo", [("flags", self.flags as Any), ("startDate", self.startDate as Any), ("joinedTooEarlyDate", self.joinedTooEarlyDate as Any), ("adminDisallowedChatId", self.adminDisallowedChatId as Any), ("disallowedCountry", self.disallowedCountry as Any)])
}
}
public class Cons_giveawayInfoResults: TypeConstructorDescription {
public var flags: Int32
public var startDate: Int32
public var giftCodeSlug: String?
public var starsPrize: Int64?
public var finishDate: Int32
public var winnersCount: Int32
public var activatedCount: Int32?
public init(flags: Int32, startDate: Int32, giftCodeSlug: String?, starsPrize: Int64?, finishDate: Int32, winnersCount: Int32, activatedCount: Int32?) {
self.flags = flags
self.startDate = startDate
self.giftCodeSlug = giftCodeSlug
self.starsPrize = starsPrize
self.finishDate = finishDate
self.winnersCount = winnersCount
self.activatedCount = activatedCount
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("giveawayInfoResults", [("flags", self.flags as Any), ("startDate", self.startDate as Any), ("giftCodeSlug", self.giftCodeSlug as Any), ("starsPrize", self.starsPrize as Any), ("finishDate", self.finishDate as Any), ("winnersCount", self.winnersCount as Any), ("activatedCount", self.activatedCount as Any)])
}
}
case giveawayInfo(Cons_giveawayInfo)
case giveawayInfoResults(Cons_giveawayInfoResults)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .giveawayInfo(let _data):
if boxed {
buffer.appendInt32(1130879648)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
serializeInt32(_data.startDate, buffer: buffer, boxed: false)
if Int(_data.flags) & Int(1 << 1) != 0 {
serializeInt32(_data.joinedTooEarlyDate!, buffer: buffer, boxed: false)
}
if Int(_data.flags) & Int(1 << 2) != 0 {
serializeInt64(_data.adminDisallowedChatId!, buffer: buffer, boxed: false)
}
if Int(_data.flags) & Int(1 << 4) != 0 {
serializeString(_data.disallowedCountry!, buffer: buffer, boxed: false)
}
break
case .giveawayInfoResults(let _data):
if boxed {
buffer.appendInt32(-512366993)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
serializeInt32(_data.startDate, buffer: buffer, boxed: false)
if Int(_data.flags) & Int(1 << 3) != 0 {
serializeString(_data.giftCodeSlug!, buffer: buffer, boxed: false)
}
if Int(_data.flags) & Int(1 << 4) != 0 {
serializeInt64(_data.starsPrize!, buffer: buffer, boxed: false)
}
serializeInt32(_data.finishDate, buffer: buffer, boxed: false)
serializeInt32(_data.winnersCount, buffer: buffer, boxed: false)
if Int(_data.flags) & Int(1 << 2) != 0 {
serializeInt32(_data.activatedCount!, buffer: buffer, boxed: false)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .giveawayInfo(let _data):
return ("giveawayInfo", [("flags", _data.flags as Any), ("startDate", _data.startDate as Any), ("joinedTooEarlyDate", _data.joinedTooEarlyDate as Any), ("adminDisallowedChatId", _data.adminDisallowedChatId as Any), ("disallowedCountry", _data.disallowedCountry as Any)])
case .giveawayInfoResults(let _data):
return ("giveawayInfoResults", [("flags", _data.flags as Any), ("startDate", _data.startDate as Any), ("giftCodeSlug", _data.giftCodeSlug as Any), ("starsPrize", _data.starsPrize as Any), ("finishDate", _data.finishDate as Any), ("winnersCount", _data.winnersCount as Any), ("activatedCount", _data.activatedCount as Any)])
}
}
public static func parse_giveawayInfo(_ reader: BufferReader) -> GiveawayInfo? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {
_3 = reader.readInt32()
}
var _4: Int64?
if Int(_1!) & Int(1 << 2) != 0 {
_4 = reader.readInt64()
}
var _5: String?
if Int(_1!) & Int(1 << 4) != 0 {
_5 = parseString(reader)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.payments.GiveawayInfo.giveawayInfo(Cons_giveawayInfo(flags: _1!, startDate: _2!, joinedTooEarlyDate: _3, adminDisallowedChatId: _4, disallowedCountry: _5))
}
else {
return nil
}
}
public static func parse_giveawayInfoResults(_ reader: BufferReader) -> GiveawayInfo? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
if Int(_1!) & Int(1 << 3) != 0 {
_3 = parseString(reader)
}
var _4: Int64?
if Int(_1!) & Int(1 << 4) != 0 {
_4 = reader.readInt64()
}
var _5: Int32?
_5 = reader.readInt32()
var _6: Int32?
_6 = reader.readInt32()
var _7: Int32?
if Int(_1!) & Int(1 << 2) != 0 {
_7 = reader.readInt32()
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.payments.GiveawayInfo.giveawayInfoResults(Cons_giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, starsPrize: _4, finishDate: _5!, winnersCount: _6!, activatedCount: _7))
}
else {
return nil
}
}
}
}
public extension Api.payments { public extension Api.payments {
enum PaymentForm: TypeConstructorDescription { enum PaymentForm: TypeConstructorDescription {
public class Cons_paymentForm: TypeConstructorDescription { public class Cons_paymentForm: TypeConstructorDescription {
@ -2179,215 +2380,3 @@ public extension Api.payments {
} }
} }
} }
public extension Api.phone {
enum ExportedGroupCallInvite: TypeConstructorDescription {
public class Cons_exportedGroupCallInvite: TypeConstructorDescription {
public var link: String
public init(link: String) {
self.link = link
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("exportedGroupCallInvite", [("link", self.link as Any)])
}
}
case exportedGroupCallInvite(Cons_exportedGroupCallInvite)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .exportedGroupCallInvite(let _data):
if boxed {
buffer.appendInt32(541839704)
}
serializeString(_data.link, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .exportedGroupCallInvite(let _data):
return ("exportedGroupCallInvite", [("link", _data.link as Any)])
}
}
public static func parse_exportedGroupCallInvite(_ reader: BufferReader) -> ExportedGroupCallInvite? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(Cons_exportedGroupCallInvite(link: _1!))
}
else {
return nil
}
}
}
}
public extension Api.phone {
enum GroupCall: TypeConstructorDescription {
public class Cons_groupCall: TypeConstructorDescription {
public var call: Api.GroupCall
public var participants: [Api.GroupCallParticipant]
public var participantsNextOffset: String
public var chats: [Api.Chat]
public var users: [Api.User]
public init(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User]) {
self.call = call
self.participants = participants
self.participantsNextOffset = participantsNextOffset
self.chats = chats
self.users = users
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("groupCall", [("call", self.call as Any), ("participants", self.participants as Any), ("participantsNextOffset", self.participantsNextOffset as Any), ("chats", self.chats as Any), ("users", self.users as Any)])
}
}
case groupCall(Cons_groupCall)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .groupCall(let _data):
if boxed {
buffer.appendInt32(-1636664659)
}
_data.call.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.participants.count))
for item in _data.participants {
item.serialize(buffer, true)
}
serializeString(_data.participantsNextOffset, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.chats.count))
for item in _data.chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.users.count))
for item in _data.users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .groupCall(let _data):
return ("groupCall", [("call", _data.call as Any), ("participants", _data.participants as Any), ("participantsNextOffset", _data.participantsNextOffset as Any), ("chats", _data.chats as Any), ("users", _data.users as Any)])
}
}
public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? {
var _1: Api.GroupCall?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.GroupCall
}
var _2: [Api.GroupCallParticipant]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self)
}
var _3: String?
_3 = parseString(reader)
var _4: [Api.Chat]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.phone.GroupCall.groupCall(Cons_groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!))
}
else {
return nil
}
}
}
}
public extension Api.phone {
enum GroupCallStars: TypeConstructorDescription {
public class Cons_groupCallStars: TypeConstructorDescription {
public var totalStars: Int64
public var topDonors: [Api.GroupCallDonor]
public var chats: [Api.Chat]
public var users: [Api.User]
public init(totalStars: Int64, topDonors: [Api.GroupCallDonor], chats: [Api.Chat], users: [Api.User]) {
self.totalStars = totalStars
self.topDonors = topDonors
self.chats = chats
self.users = users
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("groupCallStars", [("totalStars", self.totalStars as Any), ("topDonors", self.topDonors as Any), ("chats", self.chats as Any), ("users", self.users as Any)])
}
}
case groupCallStars(Cons_groupCallStars)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .groupCallStars(let _data):
if boxed {
buffer.appendInt32(-1658995418)
}
serializeInt64(_data.totalStars, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.topDonors.count))
for item in _data.topDonors {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.chats.count))
for item in _data.chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.users.count))
for item in _data.users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .groupCallStars(let _data):
return ("groupCallStars", [("totalStars", _data.totalStars as Any), ("topDonors", _data.topDonors as Any), ("chats", _data.chats as Any), ("users", _data.users as Any)])
}
}
public static func parse_groupCallStars(_ reader: BufferReader) -> GroupCallStars? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.GroupCallDonor]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallDonor.self)
}
var _3: [Api.Chat]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _4: [Api.User]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.phone.GroupCallStars.groupCallStars(Cons_groupCallStars(totalStars: _1!, topDonors: _2!, chats: _3!, users: _4!))
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,215 @@
public extension Api.phone {
enum ExportedGroupCallInvite: TypeConstructorDescription {
public class Cons_exportedGroupCallInvite: TypeConstructorDescription {
public var link: String
public init(link: String) {
self.link = link
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("exportedGroupCallInvite", [("link", self.link as Any)])
}
}
case exportedGroupCallInvite(Cons_exportedGroupCallInvite)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .exportedGroupCallInvite(let _data):
if boxed {
buffer.appendInt32(541839704)
}
serializeString(_data.link, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .exportedGroupCallInvite(let _data):
return ("exportedGroupCallInvite", [("link", _data.link as Any)])
}
}
public static func parse_exportedGroupCallInvite(_ reader: BufferReader) -> ExportedGroupCallInvite? {
var _1: String?
_1 = parseString(reader)
let _c1 = _1 != nil
if _c1 {
return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(Cons_exportedGroupCallInvite(link: _1!))
}
else {
return nil
}
}
}
}
public extension Api.phone {
enum GroupCall: TypeConstructorDescription {
public class Cons_groupCall: TypeConstructorDescription {
public var call: Api.GroupCall
public var participants: [Api.GroupCallParticipant]
public var participantsNextOffset: String
public var chats: [Api.Chat]
public var users: [Api.User]
public init(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User]) {
self.call = call
self.participants = participants
self.participantsNextOffset = participantsNextOffset
self.chats = chats
self.users = users
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("groupCall", [("call", self.call as Any), ("participants", self.participants as Any), ("participantsNextOffset", self.participantsNextOffset as Any), ("chats", self.chats as Any), ("users", self.users as Any)])
}
}
case groupCall(Cons_groupCall)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .groupCall(let _data):
if boxed {
buffer.appendInt32(-1636664659)
}
_data.call.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.participants.count))
for item in _data.participants {
item.serialize(buffer, true)
}
serializeString(_data.participantsNextOffset, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.chats.count))
for item in _data.chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.users.count))
for item in _data.users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .groupCall(let _data):
return ("groupCall", [("call", _data.call as Any), ("participants", _data.participants as Any), ("participantsNextOffset", _data.participantsNextOffset as Any), ("chats", _data.chats as Any), ("users", _data.users as Any)])
}
}
public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? {
var _1: Api.GroupCall?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.GroupCall
}
var _2: [Api.GroupCallParticipant]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self)
}
var _3: String?
_3 = parseString(reader)
var _4: [Api.Chat]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.phone.GroupCall.groupCall(Cons_groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!))
}
else {
return nil
}
}
}
}
public extension Api.phone {
enum GroupCallStars: TypeConstructorDescription {
public class Cons_groupCallStars: TypeConstructorDescription {
public var totalStars: Int64
public var topDonors: [Api.GroupCallDonor]
public var chats: [Api.Chat]
public var users: [Api.User]
public init(totalStars: Int64, topDonors: [Api.GroupCallDonor], chats: [Api.Chat], users: [Api.User]) {
self.totalStars = totalStars
self.topDonors = topDonors
self.chats = chats
self.users = users
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("groupCallStars", [("totalStars", self.totalStars as Any), ("topDonors", self.topDonors as Any), ("chats", self.chats as Any), ("users", self.users as Any)])
}
}
case groupCallStars(Cons_groupCallStars)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .groupCallStars(let _data):
if boxed {
buffer.appendInt32(-1658995418)
}
serializeInt64(_data.totalStars, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.topDonors.count))
for item in _data.topDonors {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.chats.count))
for item in _data.chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.users.count))
for item in _data.users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .groupCallStars(let _data):
return ("groupCallStars", [("totalStars", _data.totalStars as Any), ("topDonors", _data.topDonors as Any), ("chats", _data.chats as Any), ("users", _data.users as Any)])
}
}
public static func parse_groupCallStars(_ reader: BufferReader) -> GroupCallStars? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.GroupCallDonor]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallDonor.self)
}
var _3: [Api.Chat]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _4: [Api.User]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.phone.GroupCallStars.groupCallStars(Cons_groupCallStars(totalStars: _1!, topDonors: _2!, chats: _3!, users: _4!))
}
else {
return nil
}
}
}
}
public extension Api.phone { public extension Api.phone {
enum GroupCallStreamChannels: TypeConstructorDescription { enum GroupCallStreamChannels: TypeConstructorDescription {
public class Cons_groupCallStreamChannels: TypeConstructorDescription { public class Cons_groupCallStreamChannels: TypeConstructorDescription {
@ -1706,213 +1918,3 @@ public extension Api.storage {
} }
} }
} }
public extension Api.stories {
enum Albums: TypeConstructorDescription {
public class Cons_albums: TypeConstructorDescription {
public var hash: Int64
public var albums: [Api.StoryAlbum]
public init(hash: Int64, albums: [Api.StoryAlbum]) {
self.hash = hash
self.albums = albums
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("albums", [("hash", self.hash as Any), ("albums", self.albums as Any)])
}
}
case albums(Cons_albums)
case albumsNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .albums(let _data):
if boxed {
buffer.appendInt32(-1013417414)
}
serializeInt64(_data.hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.albums.count))
for item in _data.albums {
item.serialize(buffer, true)
}
break
case .albumsNotModified:
if boxed {
buffer.appendInt32(1448008427)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .albums(let _data):
return ("albums", [("hash", _data.hash as Any), ("albums", _data.albums as Any)])
case .albumsNotModified:
return ("albumsNotModified", [])
}
}
public static func parse_albums(_ reader: BufferReader) -> Albums? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.StoryAlbum]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryAlbum.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.stories.Albums.albums(Cons_albums(hash: _1!, albums: _2!))
}
else {
return nil
}
}
public static func parse_albumsNotModified(_ reader: BufferReader) -> Albums? {
return Api.stories.Albums.albumsNotModified
}
}
}
public extension Api.stories {
enum AllStories: TypeConstructorDescription {
public class Cons_allStories: TypeConstructorDescription {
public var flags: Int32
public var count: Int32
public var state: String
public var peerStories: [Api.PeerStories]
public var chats: [Api.Chat]
public var users: [Api.User]
public var stealthMode: Api.StoriesStealthMode
public init(flags: Int32, count: Int32, state: String, peerStories: [Api.PeerStories], chats: [Api.Chat], users: [Api.User], stealthMode: Api.StoriesStealthMode) {
self.flags = flags
self.count = count
self.state = state
self.peerStories = peerStories
self.chats = chats
self.users = users
self.stealthMode = stealthMode
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("allStories", [("flags", self.flags as Any), ("count", self.count as Any), ("state", self.state as Any), ("peerStories", self.peerStories as Any), ("chats", self.chats as Any), ("users", self.users as Any), ("stealthMode", self.stealthMode as Any)])
}
}
public class Cons_allStoriesNotModified: TypeConstructorDescription {
public var flags: Int32
public var state: String
public var stealthMode: Api.StoriesStealthMode
public init(flags: Int32, state: String, stealthMode: Api.StoriesStealthMode) {
self.flags = flags
self.state = state
self.stealthMode = stealthMode
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("allStoriesNotModified", [("flags", self.flags as Any), ("state", self.state as Any), ("stealthMode", self.stealthMode as Any)])
}
}
case allStories(Cons_allStories)
case allStoriesNotModified(Cons_allStoriesNotModified)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .allStories(let _data):
if boxed {
buffer.appendInt32(1862033025)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
serializeInt32(_data.count, buffer: buffer, boxed: false)
serializeString(_data.state, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.peerStories.count))
for item in _data.peerStories {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.chats.count))
for item in _data.chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.users.count))
for item in _data.users {
item.serialize(buffer, true)
}
_data.stealthMode.serialize(buffer, true)
break
case .allStoriesNotModified(let _data):
if boxed {
buffer.appendInt32(291044926)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
serializeString(_data.state, buffer: buffer, boxed: false)
_data.stealthMode.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .allStories(let _data):
return ("allStories", [("flags", _data.flags as Any), ("count", _data.count as Any), ("state", _data.state as Any), ("peerStories", _data.peerStories as Any), ("chats", _data.chats as Any), ("users", _data.users as Any), ("stealthMode", _data.stealthMode as Any)])
case .allStoriesNotModified(let _data):
return ("allStoriesNotModified", [("flags", _data.flags as Any), ("state", _data.state as Any), ("stealthMode", _data.stealthMode as Any)])
}
}
public static func parse_allStories(_ reader: BufferReader) -> AllStories? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: [Api.PeerStories]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerStories.self)
}
var _5: [Api.Chat]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _6: [Api.User]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
var _7: Api.StoriesStealthMode?
if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.stories.AllStories.allStories(Cons_allStories(flags: _1!, count: _2!, state: _3!, peerStories: _4!, chats: _5!, users: _6!, stealthMode: _7!))
}
else {
return nil
}
}
public static func parse_allStoriesNotModified(_ reader: BufferReader) -> AllStories? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: Api.StoriesStealthMode?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.stories.AllStories.allStoriesNotModified(Cons_allStoriesNotModified(flags: _1!, state: _2!, stealthMode: _3!))
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,213 @@
public extension Api.stories {
enum Albums: TypeConstructorDescription {
public class Cons_albums: TypeConstructorDescription {
public var hash: Int64
public var albums: [Api.StoryAlbum]
public init(hash: Int64, albums: [Api.StoryAlbum]) {
self.hash = hash
self.albums = albums
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("albums", [("hash", self.hash as Any), ("albums", self.albums as Any)])
}
}
case albums(Cons_albums)
case albumsNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .albums(let _data):
if boxed {
buffer.appendInt32(-1013417414)
}
serializeInt64(_data.hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.albums.count))
for item in _data.albums {
item.serialize(buffer, true)
}
break
case .albumsNotModified:
if boxed {
buffer.appendInt32(1448008427)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .albums(let _data):
return ("albums", [("hash", _data.hash as Any), ("albums", _data.albums as Any)])
case .albumsNotModified:
return ("albumsNotModified", [])
}
}
public static func parse_albums(_ reader: BufferReader) -> Albums? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.StoryAlbum]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryAlbum.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.stories.Albums.albums(Cons_albums(hash: _1!, albums: _2!))
}
else {
return nil
}
}
public static func parse_albumsNotModified(_ reader: BufferReader) -> Albums? {
return Api.stories.Albums.albumsNotModified
}
}
}
public extension Api.stories {
enum AllStories: TypeConstructorDescription {
public class Cons_allStories: TypeConstructorDescription {
public var flags: Int32
public var count: Int32
public var state: String
public var peerStories: [Api.PeerStories]
public var chats: [Api.Chat]
public var users: [Api.User]
public var stealthMode: Api.StoriesStealthMode
public init(flags: Int32, count: Int32, state: String, peerStories: [Api.PeerStories], chats: [Api.Chat], users: [Api.User], stealthMode: Api.StoriesStealthMode) {
self.flags = flags
self.count = count
self.state = state
self.peerStories = peerStories
self.chats = chats
self.users = users
self.stealthMode = stealthMode
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("allStories", [("flags", self.flags as Any), ("count", self.count as Any), ("state", self.state as Any), ("peerStories", self.peerStories as Any), ("chats", self.chats as Any), ("users", self.users as Any), ("stealthMode", self.stealthMode as Any)])
}
}
public class Cons_allStoriesNotModified: TypeConstructorDescription {
public var flags: Int32
public var state: String
public var stealthMode: Api.StoriesStealthMode
public init(flags: Int32, state: String, stealthMode: Api.StoriesStealthMode) {
self.flags = flags
self.state = state
self.stealthMode = stealthMode
}
public func descriptionFields() -> (String, [(String, Any)]) {
return ("allStoriesNotModified", [("flags", self.flags as Any), ("state", self.state as Any), ("stealthMode", self.stealthMode as Any)])
}
}
case allStories(Cons_allStories)
case allStoriesNotModified(Cons_allStoriesNotModified)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .allStories(let _data):
if boxed {
buffer.appendInt32(1862033025)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
serializeInt32(_data.count, buffer: buffer, boxed: false)
serializeString(_data.state, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.peerStories.count))
for item in _data.peerStories {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.chats.count))
for item in _data.chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(_data.users.count))
for item in _data.users {
item.serialize(buffer, true)
}
_data.stealthMode.serialize(buffer, true)
break
case .allStoriesNotModified(let _data):
if boxed {
buffer.appendInt32(291044926)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
serializeString(_data.state, buffer: buffer, boxed: false)
_data.stealthMode.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .allStories(let _data):
return ("allStories", [("flags", _data.flags as Any), ("count", _data.count as Any), ("state", _data.state as Any), ("peerStories", _data.peerStories as Any), ("chats", _data.chats as Any), ("users", _data.users as Any), ("stealthMode", _data.stealthMode as Any)])
case .allStoriesNotModified(let _data):
return ("allStoriesNotModified", [("flags", _data.flags as Any), ("state", _data.state as Any), ("stealthMode", _data.stealthMode as Any)])
}
}
public static func parse_allStories(_ reader: BufferReader) -> AllStories? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: [Api.PeerStories]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerStories.self)
}
var _5: [Api.Chat]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _6: [Api.User]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
var _7: Api.StoriesStealthMode?
if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.stories.AllStories.allStories(Cons_allStories(flags: _1!, count: _2!, state: _3!, peerStories: _4!, chats: _5!, users: _6!, stealthMode: _7!))
}
else {
return nil
}
}
public static func parse_allStoriesNotModified(_ reader: BufferReader) -> AllStories? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: Api.StoriesStealthMode?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.stories.AllStories.allStoriesNotModified(Cons_allStoriesNotModified(flags: _1!, state: _2!, stealthMode: _3!))
}
else {
return nil
}
}
}
}
public extension Api.stories { public extension Api.stories {
enum CanSendStoryCount: TypeConstructorDescription { enum CanSendStoryCount: TypeConstructorDescription {
public class Cons_canSendStoryCount: TypeConstructorDescription { public class Cons_canSendStoryCount: TypeConstructorDescription {

View file

@ -2772,12 +2772,12 @@ public extension Api.functions.bots {
} }
} }
public extension Api.functions.bots { public extension Api.functions.bots {
static func getRequestedWebViewButton(bot: Api.InputUser, requestId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.KeyboardButton>) { static func getRequestedWebViewButton(bot: Api.InputUser, webappReqId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.KeyboardButton>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1295431495) buffer.appendInt32(-1088047117)
bot.serialize(buffer, true) bot.serialize(buffer, true)
serializeString(requestId, buffer: buffer, boxed: false) serializeString(webappReqId, buffer: buffer, boxed: false)
return (FunctionDescription(name: "bots.getRequestedWebViewButton", parameters: [("bot", String(describing: bot)), ("requestId", String(describing: requestId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.KeyboardButton? in return (FunctionDescription(name: "bots.getRequestedWebViewButton", parameters: [("bot", String(describing: bot)), ("webappReqId", String(describing: webappReqId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.KeyboardButton? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.KeyboardButton? var result: Api.KeyboardButton?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -5496,9 +5496,9 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func composeMessageWithAI(flags: Int32, text: Api.TextWithEntities, translateToLang: String?, changeTone: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.TextWithEntities>) { static func composeMessageWithAI(flags: Int32, text: Api.TextWithEntities, translateToLang: String?, changeTone: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ComposedMessageWithAI>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1080571914) buffer.appendInt32(-45978882)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
text.serialize(buffer, true) text.serialize(buffer, true)
if Int(flags) & Int(1 << 1) != 0 { if Int(flags) & Int(1 << 1) != 0 {
@ -5507,11 +5507,11 @@ public extension Api.functions.messages {
if Int(flags) & Int(1 << 2) != 0 { if Int(flags) & Int(1 << 2) != 0 {
serializeString(changeTone!, buffer: buffer, boxed: false) serializeString(changeTone!, buffer: buffer, boxed: false)
} }
return (FunctionDescription(name: "messages.composeMessageWithAI", parameters: [("flags", String(describing: flags)), ("text", String(describing: text)), ("translateToLang", String(describing: translateToLang)), ("changeTone", String(describing: changeTone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.TextWithEntities? in return (FunctionDescription(name: "messages.composeMessageWithAI", parameters: [("flags", String(describing: flags)), ("text", String(describing: text)), ("translateToLang", String(describing: translateToLang)), ("changeTone", String(describing: changeTone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ComposedMessageWithAI? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.TextWithEntities? var result: Api.messages.ComposedMessageWithAI?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.TextWithEntities result = Api.parse(reader, signature: signature) as? Api.messages.ComposedMessageWithAI
} }
return result return result
}) })
@ -8887,16 +8887,16 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func sendBotRequestedPeer(flags: Int32, peer: Api.InputPeer, msgId: Int32?, requestId: String?, buttonId: Int32, requestedPeers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) { static func sendBotRequestedPeer(flags: Int32, peer: Api.InputPeer, msgId: Int32?, webappReqId: String?, buttonId: Int32, requestedPeers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(-1662773304) buffer.appendInt32(1818030759)
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true) peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 { if Int(flags) & Int(1 << 0) != 0 {
serializeInt32(msgId!, buffer: buffer, boxed: false) serializeInt32(msgId!, buffer: buffer, boxed: false)
} }
if Int(flags) & Int(1 << 1) != 0 { if Int(flags) & Int(1 << 1) != 0 {
serializeString(requestId!, buffer: buffer, boxed: false) serializeString(webappReqId!, buffer: buffer, boxed: false)
} }
serializeInt32(buttonId, buffer: buffer, boxed: false) serializeInt32(buttonId, buffer: buffer, boxed: false)
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
@ -8904,7 +8904,7 @@ public extension Api.functions.messages {
for item in requestedPeers { for item in requestedPeers {
item.serialize(buffer, true) item.serialize(buffer, true)
} }
return (FunctionDescription(name: "messages.sendBotRequestedPeer", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("requestId", String(describing: requestId)), ("buttonId", String(describing: buttonId)), ("requestedPeers", String(describing: requestedPeers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in return (FunctionDescription(name: "messages.sendBotRequestedPeer", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("webappReqId", String(describing: webappReqId)), ("buttonId", String(describing: buttonId)), ("requestedPeers", String(describing: requestedPeers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Updates? var result: Api.Updates?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {

View file

@ -824,6 +824,8 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
format = .full(timeFormat: timeFormat, dateFormat: dateFormat, dayOfWeek: (flags & (1 << 5)) != 0) format = .full(timeFormat: timeFormat, dateFormat: dateFormat, dayOfWeek: (flags & (1 << 5)) != 0)
} }
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .FormattedDate(format: format, date: date))) result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .FormattedDate(format: format, date: date)))
case .messageEntityDiffDelete, .messageEntityDiffInsert, .messageEntityDiffReplace:
break
} }
} }
return result return result

View file

@ -467,3 +467,13 @@ public func trimStringWithEntities(string: String, entities: [MessageTextEntity]
let nsRange = NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound) let nsRange = NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound)
return (nsString.substring(with: nsRange), messageTextEntitiesInRange(entities: entities, range: nsRange, onlyQuoteable: false)) return (nsString.substring(with: nsRange), messageTextEntitiesInRange(entities: entities, range: nsRange, onlyQuoteable: false))
} }
public struct TextWithEntities: Codable, Equatable {
public let text: String
public let entities: [MessageTextEntity]
public init(text: String, entities: [MessageTextEntity]) {
self.text = text
self.entities = entities
}
}

View file

@ -1762,6 +1762,10 @@ public extension TelegramEngine {
} }
|> ignoreValues |> ignoreValues
} }
public func composeAIMessage(text: TextWithEntities, mode: TelegramComposeAIMessageMode) -> Signal<TelegramAIComposeMessageResult?, NoError> {
return _internal_composeAIMessage(account: self.account, text: text, mode: mode)
}
} }
} }

View file

@ -365,3 +365,128 @@ func _internal_togglePeerMessagesTranslationHidden(account: Account, peerId: Eng
|> ignoreValues |> ignoreValues
} }
} }
public enum TelegramComposeAIMessageMode {
public enum Style {
case neutral
case formal
case short
case savage
case biblical
case posh
}
case translate(toLanguage: String, emojify: Bool, style: Style)
case stylize(emojify: Bool, style: Style)
case proofread
}
extension TelegramComposeAIMessageMode.Style {
var apiStyle: String {
switch self {
case .neutral:
return ""
case .formal:
return "formal"
case .short:
return "short"
case .savage:
return "savage"
case .biblical:
return "biblical"
case .posh:
return "posh"
}
}
}
public final class TelegramAIComposeMessageResult {
public let text: TextWithEntities
public let diffRanges: [Range<Int>]
public init(text: TextWithEntities, diffRanges: [Range<Int>]) {
self.text = text
self.diffRanges = diffRanges
}
}
extension TextWithEntities {
init(apiValue: Api.TextWithEntities) {
switch apiValue {
case let .textWithEntities(textWithEntities):
self.init(text: textWithEntities.text, entities: messageTextEntitiesFromApiEntities(textWithEntities.entities))
}
}
}
func _internal_composeAIMessage(account: Account, text: TextWithEntities, mode: TelegramComposeAIMessageMode) -> Signal<TelegramAIComposeMessageResult?, NoError> {
var flags: Int32 = 0
var translateToLang: String?
var changeTone: String?
switch mode {
case let .translate(toLanguage, emojify, style):
translateToLang = toLanguage
flags |= (1 << 1)
if emojify {
flags |= (1 << 3)
}
if style != .neutral {
changeTone = style.apiStyle
flags |= (1 << 2)
}
case let .stylize(emojify, style):
if emojify {
flags |= (1 << 3)
}
if style != .neutral {
changeTone = style.apiStyle
flags |= (1 << 2)
}
case .proofread:
flags |= (1 << 0)
}
let inputText: Api.TextWithEntities = .textWithEntities(Api.TextWithEntities.Cons_textWithEntities(text: text.text, entities: apiEntitiesFromMessageTextEntities(text.entities, associatedPeers: SimpleDictionary())))
return account.network.request(Api.functions.messages.composeMessageWithAI(flags: flags, text: inputText, translateToLang: translateToLang, changeTone: changeTone))
|> delay(0.4, queue: .mainQueue())
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.ComposedMessageWithAI?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<TelegramAIComposeMessageResult?, NoError> in
guard let result else {
return .single(nil)
}
switch result {
case let .composedMessageWithAI(composedMessageWithAI):
var diffRanges: [Range<Int>] = []
if let diffText = composedMessageWithAI.diffText {
switch diffText {
case let .textWithEntities(textWithEntities):
for entity in textWithEntities.entities {
switch entity {
case let .messageEntityDiffReplace(messageEntityDiffReplace):
if messageEntityDiffReplace.length >= 0 {
diffRanges.append(Int(messageEntityDiffReplace.offset) ..< Int(messageEntityDiffReplace.offset + messageEntityDiffReplace.length))
}
case let .messageEntityDiffInsert(messageEntityDiffInsert):
if messageEntityDiffInsert.length >= 0 {
diffRanges.append(Int(messageEntityDiffInsert.offset) ..< Int(messageEntityDiffInsert.offset + messageEntityDiffInsert.length))
}
default:
break
}
}
}
}
return .single(TelegramAIComposeMessageResult(
text: TextWithEntities(apiValue: composedMessageWithAI.resultText),
diffRanges: diffRanges
))
}
}
}

View file

@ -369,7 +369,7 @@ func _internal_sendBotRequestedPeer(account: Account, peerId: PeerId, messageId:
if let _ = requestId { if let _ = requestId {
flags |= (1 << 1) flags |= (1 << 1)
} }
let signal = account.network.request(Api.functions.messages.sendBotRequestedPeer(flags: flags, peer: inputPeer, msgId: msgId, requestId: requestId, buttonId: buttonId, requestedPeers: inputRequestedPeers)) let signal = account.network.request(Api.functions.messages.sendBotRequestedPeer(flags: flags, peer: inputPeer, msgId: msgId, webappReqId: requestId, buttonId: buttonId, requestedPeers: inputRequestedPeers))
|> mapError { error -> SendBotRequestedPeerError in |> mapError { error -> SendBotRequestedPeerError in
return .generic return .generic
} }
@ -436,7 +436,7 @@ func _internal_getRequestedWebViewButton(account: Account, botId: PeerId, reques
guard let inputUser = inputUser else { guard let inputUser = inputUser else {
return .fail(.generic) return .fail(.generic)
} }
return account.network.request(Api.functions.bots.getRequestedWebViewButton(bot: inputUser, requestId: requestId)) return account.network.request(Api.functions.bots.getRequestedWebViewButton(bot: inputUser, webappReqId: requestId))
|> mapError { _ -> GetRequestedWebViewButtonError in |> mapError { _ -> GetRequestedWebViewButtonError in
return .generic return .generic
} }

View file

@ -521,6 +521,7 @@ swift_library(
"//submodules/TelegramUI/Components/PeerInfo/PeerCopyProtectionInfoScreen", "//submodules/TelegramUI/Components/PeerInfo/PeerCopyProtectionInfoScreen",
"//submodules/TelegramUI/Components/Chat/ChatRankInfoScreen", "//submodules/TelegramUI/Components/Chat/ChatRankInfoScreen",
"//submodules/TelegramUI/Components/RankChatPreviewItem", "//submodules/TelegramUI/Components/RankChatPreviewItem",
"//submodules/TelegramUI/Components/TextProcessingScreen",
] + select({ ] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
"//build-system:ios_sim_arm64": [], "//build-system:ios_sim_arm64": [],

View file

@ -181,6 +181,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
}, displayUndo: { _ in }, displayUndo: { _ in
}, presentInputTextTranslation: { _, _ in }, presentInputTextTranslation: { _, _ in
}, sendEmoji: { _, _, _ in }, sendEmoji: { _, _, _ in
}, openAICompose: {
}, updateHistoryFilter: { _ in }, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in }, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: { }, toggleChatSidebarMode: {

View file

@ -760,7 +760,9 @@ public final class ChatTextInputPanelComponent: Component {
}, },
sendEmoji: { _, _, _ in sendEmoji: { _, _, _ in
}, },
updateHistoryFilter: { _ in openAICompose: {
}
,updateHistoryFilter: { _ in
}, },
updateChatLocationThread: { _, _ in updateChatLocationThread: { _, _ in
}, },

View file

@ -246,6 +246,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
public var mediaRecordingAccessibilityArea: AccessibilityAreaNode? public var mediaRecordingAccessibilityArea: AccessibilityAreaNode?
private let counterTextNode: ImmediateTextNode private let counterTextNode: ImmediateTextNode
private var aiButton: (button: HighlightTrackingButton, icon: UIImageView)?
public let menuButton: HighlightTrackingButtonNode public let menuButton: HighlightTrackingButtonNode
private let menuButtonBackgroundView: GlassBackgroundView private let menuButtonBackgroundView: GlassBackgroundView
private let menuButtonClippingNode: ASDisplayNode private let menuButtonClippingNode: ASDisplayNode
@ -333,6 +335,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
private let hapticFeedback = HapticFeedback() private let hapticFeedback = HapticFeedback()
public var isAIEnabled: Bool = false
public var inputTextState: ChatTextInputState { public var inputTextState: ChatTextInputState {
if let textInputNode = self.textInputNode { if let textInputNode = self.textInputNode {
let selectionRange: Range<Int> = textInputNode.selectedRange.location ..< (textInputNode.selectedRange.location + textInputNode.selectedRange.length) let selectionRange: Range<Int> = textInputNode.selectedRange.location ..< (textInputNode.selectedRange.location + textInputNode.selectedRange.length)
@ -3523,6 +3527,52 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
) )
} }
if self.isAIEnabled {
let aiButton: (button: HighlightTrackingButton, icon: UIImageView)
if let current = self.aiButton {
aiButton = current
} else {
aiButton = (HighlightTrackingButton(), GlassBackgroundView.ContentImageView())
self.aiButton = aiButton
aiButton.button.highligthedChanged = { [weak self] highlighted in
guard let self, let aiButton = self.aiButton else {
return
}
if highlighted {
aiButton.icon.alpha = 0.6
} else {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
transition.updateAlpha(layer: aiButton.icon.layer, alpha: 1.0)
}
}
aiButton.button.addTarget(self, action: #selector(self.aiButtonPressed), for: .touchUpInside)
aiButton.button.addSubview(aiButton.icon)
aiButton.icon.image = UIImage(bundleImageName: "Chat/Input/Text/InputAIIcon")?.withRenderingMode(.alwaysTemplate)
self.textInputContainerBackgroundView.contentView.addSubview(aiButton.icon)
self.textInputContainerBackgroundView.contentView.addSubview(aiButton.button)
}
aiButton.icon.tintColor = interfaceState.theme.chat.inputPanel.inputControlColor
if let image = aiButton.icon.image {
let aiButtonSize = CGSize(width: 40.0, height: 40.0)
let aiButtonFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.width - aiButtonSize.width - 3.0, y: 0.0), size: aiButtonSize)
transition.updateFrame(view: aiButton.button, frame: aiButtonFrame)
transition.updateFrame(view: aiButton.icon, frame: image.size.centered(in: aiButtonFrame))
}
let aiButtonAlpha: CGFloat = textInputContainerBackgroundFrame.height >= 78.0 ? 1.0 : 0.0
transition.updateAlpha(layer: aiButton.button.layer, alpha: aiButtonAlpha)
transition.updateAlpha(layer: aiButton.icon.layer, alpha: aiButtonAlpha)
} else if let aiButton = self.aiButton {
self.aiButton = nil
let aiButtonView = aiButton.button
let aiButtonIconView = aiButton.button
transition.updateAlpha(layer: aiButton.button.layer, alpha: 0.0, completion: { [weak aiButtonView] _ in
aiButtonView?.removeFromSuperview()
})
transition.updateAlpha(layer: aiButton.icon.layer, alpha: 0.0, completion: { [weak aiButtonIconView] _ in
aiButtonIconView?.removeFromSuperview()
})
}
let containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentHeight + 64.0)) let containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentHeight + 64.0))
transition.updateFrame(view: self.glassBackgroundContainer, frame: containerFrame) transition.updateFrame(view: self.glassBackgroundContainer, frame: containerFrame)
self.glassBackgroundContainer.update(size: containerFrame.size, isDark: interfaceState.theme.overallDarkAppearance, transition: ComponentTransition(transition)) self.glassBackgroundContainer.update(size: containerFrame.size, isDark: interfaceState.theme.overallDarkAppearance, transition: ComponentTransition(transition))
@ -3555,6 +3605,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.interfaceInteraction?.resumeMediaRecording() self.interfaceInteraction?.resumeMediaRecording()
} }
@objc private func aiButtonPressed() {
self.interfaceInteraction?.openAICompose()
}
private func displayViewOnceTooltip(text: String) { private func displayViewOnceTooltip(text: String) {
guard let context = self.context, let parentController = self.interfaceInteraction?.chatController() else { guard let context = self.context, let parentController = self.interfaceInteraction?.chatController() else {
return return

View file

@ -258,7 +258,7 @@ public final class HeaderPanelContainerComponent: Component {
} }
panelCount += component.panels.count panelCount += component.panels.count
var cornerRadius: CGFloat = 0.0 var cornerRadius: CGFloat = 0.0
if panelCount == 1 && backgroundFrame.height <= 50.0 * 2.0 { if panelCount == 1 && backgroundFrame.height <= 50.0 {
cornerRadius = backgroundFrame.height * 0.5 cornerRadius = backgroundFrame.height * 0.5
} else { } else {
cornerRadius = 20.0 cornerRadius = 20.0

View file

@ -183,7 +183,7 @@ public final class HorizontalTabsComponent: Component {
public let contextAction: ((ContextExtractedContentContainingView, ContextGesture?) -> Void)? public let contextAction: ((ContextExtractedContentContainingView, ContextGesture?) -> Void)?
public let deleteAction: (() -> Void)? public let deleteAction: (() -> Void)?
public init(id: AnyHashable, content: Content, badge: Badge?, action: @escaping () -> Void, contextAction: ((ContextExtractedContentContainingView, ContextGesture?) -> Void)?, deleteAction: (() -> Void)?) { public init(id: AnyHashable, content: Content, badge: Badge?, action: @escaping () -> Void, contextAction: ((ContextExtractedContentContainingView, ContextGesture?) -> Void)? = nil, deleteAction: (() -> Void)? = nil) {
self.id = id self.id = id
self.content = content self.content = content
self.badge = badge self.badge = badge

View file

@ -174,6 +174,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, displayUndo: { _ in }, displayUndo: { _ in
}, presentInputTextTranslation: { _, _ in }, presentInputTextTranslation: { _, _ in
}, sendEmoji: { _, _, _ in }, sendEmoji: { _, _, _ in
}, openAICompose: {
}, updateHistoryFilter: { _ in }, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in }, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: { }, toggleChatSidebarMode: {

View file

@ -835,6 +835,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
}, displayUndo: { _ in }, displayUndo: { _ in
}, presentInputTextTranslation: { _, _ in }, presentInputTextTranslation: { _, _ in
}, sendEmoji: { _, _, _ in }, sendEmoji: { _, _, _ in
}, openAICompose: {
}, updateHistoryFilter: { _ in }, updateHistoryFilter: { _ in
}, updateChatLocationThread: { _, _ in }, updateChatLocationThread: { _, _ in
}, toggleChatSidebarMode: { }, toggleChatSidebarMode: {

View file

@ -248,27 +248,73 @@ public final class NavigationSearchView: UIView {
public final class TabBarComponent: Component { public final class TabBarComponent: Component {
public final class Item: Equatable { public final class Item: Equatable {
public let item: UITabBarItem public enum Content: Equatable {
public struct CustomItem: Equatable {
public enum Icon: Equatable {
case bundleIcon(name: String)
case animation(name: String, offset: CGPoint)
}
public var id: AnyHashable
public var title: String
public var icon: Icon
public var badge: String?
public init(id: AnyHashable, title: String, icon: Icon, badge: String? = nil) {
self.id = id
self.title = title
self.icon = icon
self.badge = badge
}
}
case tabBarItem(UITabBarItem)
case customItem(CustomItem)
public static func ==(lhs: Content, rhs: Content) -> Bool {
switch lhs {
case let .tabBarItem(lhsTabBarItem):
if case let .tabBarItem(rhsTabBarItem) = rhs {
return lhsTabBarItem === rhsTabBarItem
} else {
return false
}
case let .customItem(lhsCustomItem):
if case let .customItem(rhsCustomItem) = rhs {
return lhsCustomItem == rhsCustomItem
} else {
return false
}
}
}
}
public let content: Content
public let action: (Bool) -> Void public let action: (Bool) -> Void
public let doubleTapAction: (() -> Void)? public let doubleTapAction: (() -> Void)?
public let contextAction: ((ContextGesture, ContextExtractedContentContainingView) -> Void)? public let contextAction: ((ContextGesture, ContextExtractedContentContainingView) -> Void)?
fileprivate var id: AnyHashable { fileprivate var id: AnyHashable {
return AnyHashable(ObjectIdentifier(self.item)) switch self.content {
case let .tabBarItem(tabBarItem):
return AnyHashable(ObjectIdentifier(tabBarItem))
case let .customItem(customItem):
return customItem.id
}
} }
public init(item: UITabBarItem, action: @escaping (Bool) -> Void, doubleTapAction: (() -> Void)?, contextAction: ((ContextGesture, ContextExtractedContentContainingView) -> Void)?) { public init(content: Content, action: @escaping (Bool) -> Void, doubleTapAction: (() -> Void)?, contextAction: ((ContextGesture, ContextExtractedContentContainingView) -> Void)?) {
self.item = item self.content = content
self.action = action self.action = action
self.doubleTapAction = doubleTapAction self.doubleTapAction = doubleTapAction
self.contextAction = contextAction self.contextAction = contextAction
} }
public static func ==(lhs: Item, rhs: Item) -> Bool { public static func ==(lhs: Item, rhs: Item) -> Bool {
if lhs === rhs { if lhs === rhs {
return true return true
} }
if lhs.item !== rhs.item { if lhs.content != rhs.content {
return false return false
} }
if (lhs.doubleTapAction == nil) != (rhs.doubleTapAction == nil) { if (lhs.doubleTapAction == nil) != (rhs.doubleTapAction == nil) {
@ -301,6 +347,7 @@ public final class TabBarComponent: Component {
} }
public let theme: PresentationTheme public let theme: PresentationTheme
public let tintSelectedItem: Bool
public let strings: PresentationStrings public let strings: PresentationStrings
public let items: [Item] public let items: [Item]
public let search: Search? public let search: Search?
@ -309,6 +356,7 @@ public final class TabBarComponent: Component {
public init( public init(
theme: PresentationTheme, theme: PresentationTheme,
tintSelectedItem: Bool = true,
strings: PresentationStrings, strings: PresentationStrings,
items: [Item], items: [Item],
search: Search?, search: Search?,
@ -316,6 +364,7 @@ public final class TabBarComponent: Component {
outerInsets: UIEdgeInsets outerInsets: UIEdgeInsets
) { ) {
self.theme = theme self.theme = theme
self.tintSelectedItem = tintSelectedItem
self.strings = strings self.strings = strings
self.items = items self.items = items
self.search = search self.search = search
@ -327,6 +376,9 @@ public final class TabBarComponent: Component {
if lhs.theme !== rhs.theme { if lhs.theme !== rhs.theme {
return false return false
} }
if lhs.tintSelectedItem != rhs.tintSelectedItem {
return false
}
if lhs.strings !== rhs.strings { if lhs.strings !== rhs.strings {
return false return false
} }
@ -633,6 +685,7 @@ public final class TabBarComponent: Component {
theme: component.theme, theme: component.theme,
isCompact: false, isCompact: false,
isSelected: false, isSelected: false,
tintSelectedItem: true,
isUnconstrained: true isUnconstrained: true
)), )),
environment: {}, environment: {},
@ -709,6 +762,7 @@ public final class TabBarComponent: Component {
theme: component.theme, theme: component.theme,
isCompact: component.search?.isActive == true, isCompact: component.search?.isActive == true,
isSelected: false, isSelected: false,
tintSelectedItem: component.tintSelectedItem,
isUnconstrained: false isUnconstrained: false
)), )),
environment: {}, environment: {},
@ -721,6 +775,7 @@ public final class TabBarComponent: Component {
theme: component.theme, theme: component.theme,
isCompact: component.search?.isActive == true, isCompact: component.search?.isActive == true,
isSelected: true, isSelected: true,
tintSelectedItem: component.tintSelectedItem,
isUnconstrained: false isUnconstrained: false
)), )),
environment: {}, environment: {},
@ -895,13 +950,15 @@ private final class ItemComponent: Component {
let theme: PresentationTheme let theme: PresentationTheme
let isCompact: Bool let isCompact: Bool
let isSelected: Bool let isSelected: Bool
let tintSelectedItem: Bool
let isUnconstrained: Bool let isUnconstrained: Bool
init(item: TabBarComponent.Item, theme: PresentationTheme, isCompact: Bool, isSelected: Bool, isUnconstrained: Bool) { init(item: TabBarComponent.Item, theme: PresentationTheme, isCompact: Bool, isSelected: Bool, tintSelectedItem: Bool, isUnconstrained: Bool) {
self.item = item self.item = item
self.theme = theme self.theme = theme
self.isCompact = isCompact self.isCompact = isCompact
self.isSelected = isSelected self.isSelected = isSelected
self.tintSelectedItem = tintSelectedItem
self.isUnconstrained = isUnconstrained self.isUnconstrained = isUnconstrained
} }
@ -918,6 +975,9 @@ private final class ItemComponent: Component {
if lhs.isSelected != rhs.isSelected { if lhs.isSelected != rhs.isSelected {
return false return false
} }
if lhs.tintSelectedItem != rhs.tintSelectedItem {
return false
}
if lhs.isUnconstrained != rhs.isUnconstrained { if lhs.isUnconstrained != rhs.isUnconstrained {
return false return false
} }
@ -934,37 +994,37 @@ private final class ItemComponent: Component {
private var component: ItemComponent? private var component: ItemComponent?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
private var setImageListener: Int? private var setImageListener: Int?
private var setSelectedImageListener: Int? private var setSelectedImageListener: Int?
private var setBadgeListener: Int? private var setBadgeListener: Int?
override init(frame: CGRect) { override init(frame: CGRect) {
self.contextContainerView = ContextExtractedContentContainingView() self.contextContainerView = ContextExtractedContentContainingView()
super.init(frame: frame) super.init(frame: frame)
self.addSubview(self.contextContainerView) self.addSubview(self.contextContainerView)
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit { deinit {
if let component = self.component { if let component = self.component, case let .tabBarItem(tabBarItem) = component.item.content {
if let setImageListener = self.setImageListener { if let setImageListener = self.setImageListener {
component.item.item.removeSetImageListener(setImageListener) tabBarItem.removeSetImageListener(setImageListener)
} }
if let setSelectedImageListener = self.setSelectedImageListener { if let setSelectedImageListener = self.setSelectedImageListener {
component.item.item.removeSetSelectedImageListener(setSelectedImageListener) tabBarItem.removeSetSelectedImageListener(setSelectedImageListener)
} }
if let setBadgeListener = self.setBadgeListener { if let setBadgeListener = self.setBadgeListener {
component.item.item.removeSetBadgeListener(setBadgeListener) tabBarItem.removeSetBadgeListener(setBadgeListener)
} }
} }
} }
func playSelectionAnimation() { func playSelectionAnimation() {
if let animationIconView = self.animationIcon?.view as? LottieComponent.View { if let animationIconView = self.animationIcon?.view as? LottieComponent.View {
animationIconView.playOnce() animationIconView.playOnce()
@ -975,125 +1035,241 @@ private final class ItemComponent: Component {
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25) let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25)
let previousComponent = self.component let previousComponent = self.component
if previousComponent?.item.item !== component.item.item { let previousTabBarItem: UITabBarItem?
if let setImageListener = self.setImageListener { if let previousComponent, case let .tabBarItem(tabBarItem) = previousComponent.item.content {
self.component?.item.item.removeSetImageListener(setImageListener) previousTabBarItem = tabBarItem
} } else {
if let setSelectedImageListener = self.setSelectedImageListener { previousTabBarItem = nil
self.component?.item.item.removeSetSelectedImageListener(setSelectedImageListener) }
}
if let setBadgeListener = self.setBadgeListener { let currentTabBarItem: UITabBarItem?
self.component?.item.item.removeSetBadgeListener(setBadgeListener) if case let .tabBarItem(tabBarItem) = component.item.content {
} currentTabBarItem = tabBarItem
self.setImageListener = component.item.item.addSetImageListener { [weak self] _ in } else {
guard let self else { currentTabBarItem = nil
return }
if previousTabBarItem !== currentTabBarItem {
if let previousTabBarItem {
if let setImageListener = self.setImageListener {
previousTabBarItem.removeSetImageListener(setImageListener)
} }
self.state?.updated(transition: .immediate, isLocal: true) if let setSelectedImageListener = self.setSelectedImageListener {
} previousTabBarItem.removeSetSelectedImageListener(setSelectedImageListener)
self.setSelectedImageListener = component.item.item.addSetSelectedImageListener { [weak self] _ in
guard let self else {
return
} }
self.state?.updated(transition: .immediate, isLocal: true) if let setBadgeListener = self.setBadgeListener {
} previousTabBarItem.removeSetBadgeListener(setBadgeListener)
self.setBadgeListener = UITabBarItem_addSetBadgeListener(component.item.item) { [weak self] _ in }
guard let self else { self.setImageListener = nil
return self.setSelectedImageListener = nil
self.setBadgeListener = nil
}
if let currentTabBarItem {
self.setImageListener = currentTabBarItem.addSetImageListener { [weak self] _ in
guard let self else {
return
}
self.state?.updated(transition: .immediate, isLocal: true)
}
self.setSelectedImageListener = currentTabBarItem.addSetSelectedImageListener { [weak self] _ in
guard let self else {
return
}
self.state?.updated(transition: .immediate, isLocal: true)
}
self.setBadgeListener = UITabBarItem_addSetBadgeListener(currentTabBarItem) { [weak self] _ in
guard let self else {
return
}
self.state?.updated(transition: .immediate, isLocal: true)
} }
self.state?.updated(transition: .immediate, isLocal: true)
} }
} }
self.component = component self.component = component
self.state = state self.state = state
if let animationName = component.item.item.animationName { let iconTintColor = (component.isSelected && component.tintSelectedItem && !component.isCompact) ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor
if let imageIcon = self.imageIcon {
self.imageIcon = nil let title: String
imageIcon.view?.removeFromSuperview() let badgeValue: String?
}
switch component.item.content {
let animationIcon: ComponentView<Empty> case let .tabBarItem(tabBarItem):
var iconTransition = transition title = tabBarItem.title ?? " "
if let current = self.animationIcon { badgeValue = tabBarItem.badgeValue
animationIcon = current
} else { if let animationName = tabBarItem.animationName {
iconTransition = iconTransition.withAnimation(.none) if let imageIcon = self.imageIcon {
animationIcon = ComponentView() self.imageIcon = nil
self.animationIcon = animationIcon imageIcon.view?.removeFromSuperview()
}
let iconSize = animationIcon.update(
transition: iconTransition,
component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(
name: animationName
),
color: (component.isSelected && !component.isCompact) ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor,
placeholderColor: nil,
startingPosition: .end,
size: CGSize(width: 48.0, height: 48.0),
loop: false
)),
environment: {},
containerSize: CGSize(width: 48.0, height: 48.0)
)
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: -4.0), size: iconSize).offsetBy(dx: component.item.item.animationOffset.x, dy: component.item.item.animationOffset.y)
if let animationIconView = animationIcon.view {
if animationIconView.superview == nil {
if let badgeView = self.badge?.view {
self.contextContainerView.contentView.insertSubview(animationIconView, belowSubview: badgeView)
} else {
self.contextContainerView.contentView.addSubview(animationIconView)
}
} }
iconTransition.setFrame(view: animationIconView, frame: iconFrame)
} let animationIcon: ComponentView<Empty>
} else { var iconTransition = transition
if let animationIcon = self.animationIcon { if let current = self.animationIcon {
self.animationIcon = nil animationIcon = current
animationIcon.view?.removeFromSuperview() } else {
} iconTransition = iconTransition.withAnimation(.none)
animationIcon = ComponentView()
let imageIcon: ComponentView<Empty> self.animationIcon = animationIcon
var iconTransition = transition }
if let current = self.imageIcon {
imageIcon = current let iconSize = animationIcon.update(
} else { transition: iconTransition,
iconTransition = iconTransition.withAnimation(.none) component: AnyComponent(LottieComponent(
imageIcon = ComponentView() content: LottieComponent.AppBundleContent(
self.imageIcon = imageIcon name: animationName
} ),
color: iconTintColor,
let iconSize = imageIcon.update( placeholderColor: nil,
transition: iconTransition, startingPosition: .end,
component: AnyComponent(Image( size: CGSize(width: 48.0, height: 48.0),
image: component.isSelected ? component.item.item.selectedImage : component.item.item.image, loop: false
tintColor: nil, )),
contentMode: .center environment: {},
)), containerSize: CGSize(width: 48.0, height: 48.0)
environment: {}, )
containerSize: CGSize(width: 100.0, height: 100.0) let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: -4.0), size: iconSize).offsetBy(dx: tabBarItem.animationOffset.x, dy: tabBarItem.animationOffset.y)
) if let animationIconView = animationIcon.view {
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 3.0), size: iconSize) if animationIconView.superview == nil {
if let imageIconView = imageIcon.view { if let badgeView = self.badge?.view {
if imageIconView.superview == nil { self.contextContainerView.contentView.insertSubview(animationIconView, belowSubview: badgeView)
if let badgeView = self.badge?.view { } else {
self.contextContainerView.contentView.insertSubview(imageIconView, belowSubview: badgeView) self.contextContainerView.contentView.addSubview(animationIconView)
} else { }
self.contextContainerView.contentView.addSubview(imageIconView) }
} iconTransition.setFrame(view: animationIconView, frame: iconFrame)
}
} else {
if let animationIcon = self.animationIcon {
self.animationIcon = nil
animationIcon.view?.removeFromSuperview()
}
let imageIcon: ComponentView<Empty>
var iconTransition = transition
if let current = self.imageIcon {
imageIcon = current
} else {
iconTransition = iconTransition.withAnimation(.none)
imageIcon = ComponentView()
self.imageIcon = imageIcon
}
let iconSize = imageIcon.update(
transition: iconTransition,
component: AnyComponent(Image(
image: component.isSelected ? tabBarItem.selectedImage : tabBarItem.image,
tintColor: nil,
contentMode: .center
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 3.0), size: iconSize)
if let imageIconView = imageIcon.view {
if imageIconView.superview == nil {
if let badgeView = self.badge?.view {
self.contextContainerView.contentView.insertSubview(imageIconView, belowSubview: badgeView)
} else {
self.contextContainerView.contentView.addSubview(imageIconView)
}
}
iconTransition.setFrame(view: imageIconView, frame: iconFrame)
}
}
case let .customItem(customItem):
title = customItem.title
badgeValue = customItem.badge
switch customItem.icon {
case let .animation(name, offset):
if let imageIcon = self.imageIcon {
self.imageIcon = nil
imageIcon.view?.removeFromSuperview()
}
let animationIcon: ComponentView<Empty>
var iconTransition = transition
if let current = self.animationIcon {
animationIcon = current
} else {
iconTransition = iconTransition.withAnimation(.none)
animationIcon = ComponentView()
self.animationIcon = animationIcon
}
let iconSize = animationIcon.update(
transition: iconTransition,
component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(
name: name
),
color: iconTintColor,
placeholderColor: nil,
startingPosition: .end,
size: CGSize(width: 48.0, height: 48.0),
loop: false
)),
environment: {},
containerSize: CGSize(width: 48.0, height: 48.0)
)
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: -4.0), size: iconSize).offsetBy(dx: offset.x, dy: offset.y)
if let animationIconView = animationIcon.view {
if animationIconView.superview == nil {
if let badgeView = self.badge?.view {
self.contextContainerView.contentView.insertSubview(animationIconView, belowSubview: badgeView)
} else {
self.contextContainerView.contentView.addSubview(animationIconView)
}
}
iconTransition.setFrame(view: animationIconView, frame: iconFrame)
}
case let .bundleIcon(name):
if let animationIcon = self.animationIcon {
self.animationIcon = nil
animationIcon.view?.removeFromSuperview()
}
let imageIcon: ComponentView<Empty>
var iconTransition = transition
if let current = self.imageIcon {
imageIcon = current
} else {
iconTransition = iconTransition.withAnimation(.none)
imageIcon = ComponentView()
self.imageIcon = imageIcon
}
let iconSize = imageIcon.update(
transition: iconTransition,
component: AnyComponent(BundleIconComponent(
name: name,
tintColor: iconTintColor
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 8.0), size: iconSize)
if let imageIconView = imageIcon.view {
if imageIconView.superview == nil {
if let badgeView = self.badge?.view {
self.contextContainerView.contentView.insertSubview(imageIconView, belowSubview: badgeView)
} else {
self.contextContainerView.contentView.addSubview(imageIconView)
}
}
iconTransition.setFrame(view: imageIconView, frame: iconFrame)
} }
iconTransition.setFrame(view: imageIconView, frame: iconFrame)
} }
} }
let titleSize = self.title.update( let titleSize = self.title.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.item.item.title ?? " ", font: Font.semibold(10.0), textColor: component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor)) text: .plain(NSAttributedString(string: title, font: Font.semibold(10.0), textColor: (component.isSelected && component.tintSelectedItem) ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor))
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width, height: 100.0) containerSize: CGSize(width: availableSize.width, height: 100.0)
@ -1106,8 +1282,8 @@ private final class ItemComponent: Component {
titleView.frame = titleFrame titleView.frame = titleFrame
alphaTransition.setAlpha(view: titleView, alpha: component.isCompact ? 0.0 : 1.0) alphaTransition.setAlpha(view: titleView, alpha: component.isCompact ? 0.0 : 1.0)
} }
if let badgeText = component.item.item.badgeValue, !badgeText.isEmpty { if let badgeText = badgeValue, !badgeText.isEmpty {
let badge: ComponentView<Empty> let badge: ComponentView<Empty>
var badgeTransition = transition var badgeTransition = transition
if let current = self.badge { if let current = self.badge {
@ -1142,11 +1318,11 @@ private final class ItemComponent: Component {
self.badge = nil self.badge = nil
badge.view?.removeFromSuperview() badge.view?.removeFromSuperview()
} }
transition.setFrame(view: self.contextContainerView, frame: CGRect(origin: CGPoint(), size: availableSize)) transition.setFrame(view: self.contextContainerView, frame: CGRect(origin: CGPoint(), size: availableSize))
transition.setFrame(view: self.contextContainerView.contentView, frame: CGRect(origin: CGPoint(), size: availableSize)) transition.setFrame(view: self.contextContainerView.contentView, frame: CGRect(origin: CGPoint(), size: availableSize))
self.contextContainerView.contentRect = CGRect(origin: CGPoint(), size: availableSize) self.contextContainerView.contentRect = CGRect(origin: CGPoint(), size: availableSize)
if component.isUnconstrained { if component.isUnconstrained {
return CGSize(width: titleSize.width + 10.0 * 2.0, height: availableSize.height) return CGSize(width: titleSize.width + 10.0 * 2.0, height: availableSize.height)
} else { } else {

View file

@ -0,0 +1,40 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "TextProcessingScreen",
module_name = "TextProcessingScreen",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/TelegramPresentationData",
"//submodules/ComponentFlow",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/AccountContext",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/PresentationDataUtils",
"//submodules/Components/ResizableSheetComponent",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/TabBarComponent",
"//submodules/TranslateUI",
"//submodules/Components/MultilineTextWithEntitiesComponent",
"//submodules/TextFormat",
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/TelegramUI/Components/CheckComponent",
"//submodules/ShimmerEffect",
"//submodules/TelegramUI/Components/LottieComponent",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,805 @@
import Foundation
import UIKit
import Display
import TelegramPresentationData
import ComponentFlow
import GlassBackgroundComponent
import MultilineTextComponent
import BundleIconComponent
import TelegramCore
import TranslateUI
final class TextProcessingLanguageSelectionComponent: Component {
public struct Language: Equatable {
public let id: String
public let languageCode: String
public let name: String
public init(id: String, languageCode: String, name: String) {
self.id = id
self.languageCode = languageCode
self.name = name
}
}
let theme: PresentationTheme
let strings: PresentationStrings
let sourceView: UIView
let topLanguages: [Language]
let selectedLanguageCode: String
let currentStyle: TelegramComposeAIMessageMode.Style
let displayStyles: Bool
let completion: (String, TelegramComposeAIMessageMode.Style) -> Void
let dismissed: () -> Void
init(
theme: PresentationTheme,
strings: PresentationStrings,
sourceView: UIView,
topLanguages: [Language],
selectedLanguageCode: String,
currentStyle: TelegramComposeAIMessageMode.Style,
displayStyles: Bool,
completion: @escaping (String, TelegramComposeAIMessageMode.Style) -> Void,
dismissed: @escaping () -> Void
) {
self.theme = theme
self.strings = strings
self.sourceView = sourceView
self.topLanguages = topLanguages
self.selectedLanguageCode = selectedLanguageCode
self.currentStyle = currentStyle
self.displayStyles = displayStyles
self.completion = completion
self.dismissed = dismissed
}
static func ==(lhs: TextProcessingLanguageSelectionComponent, rhs: TextProcessingLanguageSelectionComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.topLanguages != rhs.topLanguages {
return false
}
if lhs.selectedLanguageCode != rhs.selectedLanguageCode {
return false
}
if lhs.currentStyle != rhs.currentStyle {
return false
}
if lhs.displayStyles != rhs.displayStyles {
return false
}
return true
}
private final class ScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
return true
}
}
private struct ItemLayout: Equatable {
let size: CGSize
let itemHeight: CGFloat
let itemCount: Int
let topSeparatedItemCount: Int
let topSeparatorHeight: CGFloat
let verticalInset: CGFloat
let contentHeight: CGFloat
init(size: CGSize, itemHeight: CGFloat, itemCount: Int, topSeparatedItemCount: Int, verticalInset: CGFloat) {
self.size = size
self.itemHeight = itemHeight
self.itemCount = itemCount
self.topSeparatedItemCount = topSeparatedItemCount
self.topSeparatorHeight = 20.0
self.verticalInset = verticalInset
var contentHeight = verticalInset * 2.0 + CGFloat(itemCount) * itemHeight
self.contentHeight = contentHeight
if self.topSeparatedItemCount != 0 {
contentHeight += self.topSeparatorHeight
}
}
func indexRange(minY: CGFloat, maxY: CGFloat) -> Range<Int>? {
var firstIndex = Int(floor((minY - self.verticalInset - self.topSeparatorHeight) / self.itemHeight))
firstIndex = max(0, firstIndex)
var lastIndex = Int(ceil((maxY - self.verticalInset + self.topSeparatorHeight) / self.itemHeight))
lastIndex = min(self.itemCount - 1, lastIndex)
if firstIndex < lastIndex {
return firstIndex ..< (lastIndex + 1)
} else {
return nil
}
}
func frame(forItemAt index: Int) -> CGRect {
var rect = CGRect(origin: CGPoint(x: 0.0, y: self.verticalInset + CGFloat(index) * self.itemHeight), size: CGSize(width: self.size.width, height: self.itemHeight))
if index >= self.topSeparatedItemCount && self.topSeparatedItemCount != 0 {
rect.origin.y += self.topSeparatorHeight
}
return rect
}
}
final class View: UIView, UIScrollViewDelegate {
private let dimView: UIView
private let backgroundContainer: GlassBackgroundContainerView
private let mainBackground: GlassBackgroundView
private let mainScrollView: ScrollView
private var mainItemViews: [String: ComponentView<Empty>] = [:]
private let mainMeasureItem = ComponentView<Empty>()
private let mainTopSeparator: SimpleLayer
private let stylesBackground: GlassBackgroundView
private let stylesScrollView: ScrollView
private let stylesSelectionView: UIImageView
private var stylesItemViews: [TelegramComposeAIMessageMode.Style: ComponentView<Empty>] = [:]
private var mainItems: [Language] = []
private var mainTopItemCount: Int = 0
private var mainItemLayout: ItemLayout?
private var component: TextProcessingLanguageSelectionComponent?
private weak var state: EmptyComponentState?
private var isUpdating: Bool = false
private var ignoreScrolling: Bool = false
private var updatedLanguage: String?
private var updatedStyle: TelegramComposeAIMessageMode.Style?
override init(frame: CGRect) {
self.dimView = UIView()
self.backgroundContainer = GlassBackgroundContainerView()
self.mainBackground = GlassBackgroundView()
self.backgroundContainer.contentView.addSubview(self.mainBackground)
self.stylesBackground = GlassBackgroundView()
self.mainScrollView = ScrollView()
self.stylesScrollView = ScrollView()
self.mainTopSeparator = SimpleLayer()
self.mainScrollView.layer.addSublayer(self.mainTopSeparator)
self.stylesSelectionView = UIImageView()
self.stylesScrollView.addSubview(self.stylesSelectionView)
super.init(frame: frame)
self.addSubview(self.dimView)
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onDimTapGesture(_:))))
self.addSubview(self.backgroundContainer)
self.mainScrollView.delaysContentTouches = false
self.mainScrollView.canCancelContentTouches = true
self.mainScrollView.contentInsetAdjustmentBehavior = .never
self.mainScrollView.automaticallyAdjustsScrollIndicatorInsets = false
self.mainScrollView.showsVerticalScrollIndicator = false
self.mainScrollView.showsHorizontalScrollIndicator = false
self.mainScrollView.alwaysBounceHorizontal = false
self.mainScrollView.alwaysBounceVertical = true
self.mainScrollView.scrollsToTop = false
self.mainScrollView.delegate = self
self.mainScrollView.clipsToBounds = true
self.mainBackground.contentView.addSubview(self.mainScrollView)
self.stylesScrollView.delaysContentTouches = false
self.stylesScrollView.canCancelContentTouches = true
self.stylesScrollView.contentInsetAdjustmentBehavior = .never
self.stylesScrollView.automaticallyAdjustsScrollIndicatorInsets = false
self.stylesScrollView.showsVerticalScrollIndicator = false
self.stylesScrollView.showsHorizontalScrollIndicator = false
self.stylesScrollView.alwaysBounceHorizontal = false
self.stylesScrollView.alwaysBounceVertical = false
self.stylesScrollView.scrollsToTop = false
self.stylesScrollView.delegate = self
self.stylesScrollView.clipsToBounds = true
self.stylesBackground.contentView.addSubview(self.stylesScrollView)
}
required init?(coder: NSCoder) {
preconditionFailure()
}
@objc private func onDimTapGesture(_ recognizer: UITapGestureRecognizer) {
guard let component = self.component else {
return
}
if case .ended = recognizer.state {
if component.displayStyles, let updatedStyle = self.updatedStyle {
component.completion(component.selectedLanguageCode, updatedStyle)
}
self.animateOut()
}
}
private func animateIn() {
self.mainBackground.layer.animateSpring(from: 0.001, to: 1.0, keyPath: "transform.scale", duration: 0.5)
self.stylesBackground.layer.animateSpring(from: 0.001, to: 1.0, keyPath: "transform.scale", duration: 0.5)
self.backgroundContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
private func animateOut() {
self.mainBackground.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
self.stylesBackground.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
self.backgroundContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in
guard let self, let component = self.component else {
return
}
component.dismissed()
})
}
private func completeIfPossible() {
guard let component = self.component else {
return
}
if self.updatedLanguage != nil || self.updatedStyle != nil {
component.completion(self.updatedLanguage ?? component.selectedLanguageCode, self.updatedStyle ?? component.currentStyle)
}
self.animateOut()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.ignoreScrolling {
self.updateScrolling(scrollView: scrollView, transition: .immediate)
}
}
private func updateScrolling(scrollView: UIScrollView, transition: ComponentTransition) {
guard let component = self.component else {
return
}
if scrollView == self.mainScrollView {
guard let itemLayout = self.mainItemLayout else {
return
}
let visibleBounds = scrollView.bounds
var validIds: [String] = []
if let indexRange = itemLayout.indexRange(minY: visibleBounds.minY, maxY: visibleBounds.maxY) {
for index in indexRange.lowerBound ..< indexRange.upperBound {
if index >= self.mainItems.count {
break
}
let item = self.mainItems[index]
validIds.append(item.id)
let itemView: ComponentView<Empty>
var itemTransition = transition
if let current = self.mainItemViews[item.id] {
itemView = current
} else {
itemTransition = itemTransition.withAnimation(.none)
itemView = ComponentView()
self.mainItemViews[item.id] = itemView
}
let itemFrame = itemLayout.frame(forItemAt: index)
let _ = itemView.update(
transition: itemTransition,
component: AnyComponent(LanguageItemComponent(
theme: component.theme,
title: item.name,
isSelected: item.languageCode == component.selectedLanguageCode,
action: { [weak self] in
guard let self else {
return
}
self.updatedLanguage = item.languageCode
self.completeIfPossible()
}
)),
environment: {},
containerSize: itemFrame.size
)
if let itemComponentView = itemView.view {
if itemComponentView.superview == nil {
self.mainScrollView.addSubview(itemComponentView)
}
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
}
}
}
var removedIds: [String] = []
for (id, itemView) in self.mainItemViews {
if !validIds.contains(id) {
removedIds.append(id)
itemView.view?.removeFromSuperview()
}
}
for id in removedIds {
self.mainItemViews.removeValue(forKey: id)
}
}
}
func update(component: TextProcessingLanguageSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
let containerSideInset: CGFloat = 16.0
var shouldAnimateIn = false
if self.component == nil {
shouldAnimateIn = true
}
self.component = component
self.state = state
if self.mainItems.isEmpty {
self.mainItems = supportedTranslationLanguages.compactMap { item in
return Language(id: item, languageCode: item, name: localizedLanguageName(strings: component.strings, language: item))
}
var topIds: [String] = []
if !topIds.contains(component.selectedLanguageCode), let item = self.mainItems.first(where: { $0.languageCode == component.selectedLanguageCode }) {
self.mainItems.insert(TextProcessingLanguageSelectionComponent.Language(
id: "top-" + item.id,
languageCode: item.languageCode,
name: item.name
), at: 0)
topIds.append(item.languageCode)
}
if !topIds.contains("en"), let item = self.mainItems.first(where: { $0.languageCode == "en" }) {
self.mainItems.insert(TextProcessingLanguageSelectionComponent.Language(
id: "top-" + item.id,
languageCode: item.languageCode,
name: item.name
), at: 0)
topIds.append(item.languageCode)
}
var languageCode = component.strings.baseLanguageCode
let rawSuffix = "-raw"
if languageCode.hasSuffix(rawSuffix) {
languageCode = String(languageCode.dropLast(rawSuffix.count))
}
if !topIds.contains(languageCode), let item = self.mainItems.first(where: { $0.languageCode == languageCode }) {
self.mainItems.insert(TextProcessingLanguageSelectionComponent.Language(
id: "top-" + item.id,
languageCode: item.languageCode,
name: item.name
), at: 0)
topIds.append(item.languageCode)
}
self.mainTopItemCount = topIds.count
}
let mainWidth: CGFloat = 220.0
let mainContainerInset: CGFloat = 11.0
let mainItemSize = self.mainMeasureItem.update(
transition: .immediate,
component: AnyComponent(LanguageItemComponent(
theme: component.theme,
title: "A",
isSelected: false,
action: {
}
)),
environment: {},
containerSize: CGSize(width: mainWidth, height: 1000.0)
)
let mainContentHeight = mainContainerInset * 2.0 + CGFloat(self.mainItems.count) * mainItemSize.height
var mainSize = CGSize(width: mainWidth, height: min(370.0, mainContentHeight))
var stylesSize: CGSize?
let stylesSpacing: CGFloat = 8.0
if component.displayStyles {
var styleData: [(id: TelegramComposeAIMessageMode.Style, icon: String, title: String)] = []
styleData.append((.neutral, "🏳️", "Neutral"))
styleData.append((.formal, "🤝", "Formal"))
styleData.append((.short, "🎯", "Short"))
styleData.append((.savage, "🍖", "Savage"))
styleData.append((.biblical, "🕯", "Biblical"))
styleData.append((.posh, "🍷", "Posh"))
let stylesItemSize = CGSize(width: 82.0, height: 60.0)
var selectedItemFrame: CGRect?
stylesSize = CGSize(width: stylesItemSize.width, height: CGFloat(styleData.count) * stylesItemSize.height)
for index in 0 ..< styleData.count {
let item = styleData[index]
let itemView: ComponentView<Empty>
var itemViewTransition = transition
if let current = self.stylesItemViews[item.id] {
itemView = current
} else {
itemViewTransition = itemViewTransition.withAnimation(.none)
itemView = ComponentView()
self.stylesItemViews[item.id] = itemView
}
let _ = itemView.update(
transition: itemViewTransition,
component: AnyComponent(StyleItemComponent(
theme: component.theme,
icon: item.icon,
title: item.title,
action: { [weak self] in
guard let self else {
return
}
self.updatedStyle = item.id
self.completeIfPossible()
}
)),
environment: {},
containerSize: stylesItemSize
)
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: CGFloat(index) * stylesItemSize.height), size: stylesItemSize)
if let itemComponentView = itemView.view {
if itemComponentView.superview == nil {
self.stylesScrollView.addSubview(itemComponentView)
}
itemViewTransition.setFrame(view: itemComponentView, frame: itemFrame)
}
if item.id == self.updatedStyle ?? component.currentStyle {
selectedItemFrame = itemFrame
}
}
if self.stylesSelectionView.image == nil {
self.stylesSelectionView.image = generateStretchableFilledCircleImage(diameter: (30.0 - 4.0) * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
}
self.stylesSelectionView.tintColor = component.theme.list.itemHighlightedBackgroundColor.withMultipliedAlpha(0.6)
if let selectedItemFrame {
var selectedBackgroundTransition = transition
if self.stylesSelectionView.isHidden {
self.stylesSelectionView.isHidden = false
selectedBackgroundTransition = selectedBackgroundTransition.withAnimation(.none)
}
selectedBackgroundTransition.setFrame(view: self.stylesSelectionView, frame: selectedItemFrame.insetBy(dx: 4.0, dy: 4.0))
transition.setAlpha(view: self.stylesSelectionView, alpha: 1.0)
} else {
if !self.stylesSelectionView.isHidden {
transition.setAlpha(view: self.stylesSelectionView, alpha: 0.0, completion: { [weak self] flag in
guard let self, flag else {
return
}
self.stylesSelectionView.isHidden = true
})
}
}
}
if let stylesSize {
mainSize.height = stylesSize.height
}
let mainItemLayout = ItemLayout(size: mainSize, itemHeight: mainItemSize.height, itemCount: self.mainItems.count, topSeparatedItemCount: self.mainTopItemCount, verticalInset: mainContainerInset)
self.mainItemLayout = mainItemLayout
if mainItemLayout.topSeparatedItemCount != 0 {
self.mainTopSeparator.backgroundColor = component.theme.contextMenu.itemSeparatorColor.cgColor
self.mainTopSeparator.isHidden = false
var topSeparatorFrame = CGRect(origin: CGPoint(x: 18.0, y: mainItemLayout.verticalInset + CGFloat(mainItemLayout.topSeparatedItemCount) * mainItemLayout.itemHeight), size: CGSize(width: mainItemLayout.size.width - 18.0 - 18.0, height: UIScreenPixel))
topSeparatorFrame.origin.y += floorToScreenPixels((mainItemLayout.topSeparatorHeight - topSeparatorFrame.height) * 0.5)
transition.setFrame(layer: self.mainTopSeparator, frame: topSeparatorFrame)
} else {
self.mainTopSeparator.isHidden = true
}
self.ignoreScrolling = true
if self.mainScrollView.bounds.size != mainItemLayout.size || self.mainScrollView.contentSize.height != mainItemLayout.contentHeight {
self.mainScrollView.frame = CGRect(origin: CGPoint(), size: mainItemLayout.size)
self.mainScrollView.contentSize = CGSize(width: mainItemLayout.size.width, height: mainItemLayout.contentHeight)
}
self.ignoreScrolling = false
self.updateScrolling(scrollView: self.mainScrollView, transition: transition)
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: availableSize))
self.backgroundContainer.update(size: availableSize, isDark: component.theme.overallDarkAppearance, transition: transition)
let sourceLocation = component.sourceView.convert(component.sourceView.bounds.center, to: self)
var mainFrame = CGRect(origin: CGPoint(x: floor(sourceLocation.x - mainItemLayout.size.width * 0.5), y: floor(sourceLocation.y - mainItemLayout.size.height * 0.5)), size: mainItemLayout.size)
if mainFrame.origin.x + mainFrame.size.width > availableSize.width - containerSideInset {
mainFrame.origin.x = availableSize.width - containerSideInset - mainFrame.size.width
}
if mainFrame.origin.y + mainFrame.size.height > availableSize.height - containerSideInset {
mainFrame.origin.y = availableSize.height - containerSideInset - mainFrame.size.height
}
if mainFrame.origin.x < containerSideInset {
mainFrame.origin.x = containerSideInset
}
if mainFrame.origin.y < containerSideInset {
mainFrame.origin.y = containerSideInset
}
transition.setFrame(view: self.mainBackground, frame: mainFrame)
self.mainBackground.update(size: mainFrame.size, cornerRadius: 30.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
if let stylesSize {
let stylesFrame = CGRect(origin: CGPoint(x: mainFrame.maxX + stylesSpacing, y: mainFrame.minY), size: stylesSize)
if self.stylesBackground.superview == nil {
self.backgroundContainer.contentView.addSubview(self.stylesBackground)
}
transition.setFrame(view: self.stylesBackground, frame: stylesFrame)
self.stylesBackground.update(size: stylesFrame.size, cornerRadius: 30.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
transition.setFrame(view: self.stylesScrollView, frame: CGRect(origin: CGPoint(), size: stylesFrame.size))
self.stylesScrollView.contentSize = stylesFrame.size
}
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
if shouldAnimateIn {
self.animateIn()
}
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class LanguageItemComponent: Component {
let theme: PresentationTheme
let title: String
let isSelected: Bool
let action: () -> Void
init(
theme: PresentationTheme,
title: String,
isSelected: Bool,
action: @escaping () -> Void
) {
self.theme = theme
self.title = title
self.isSelected = isSelected
self.action = action
}
static func ==(lhs: LanguageItemComponent, rhs: LanguageItemComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.isSelected != rhs.isSelected {
return false
}
return true
}
final class View: UIView {
private var imageIcon: ComponentView<Empty>?
private let title = ComponentView<Empty>()
private var component: LanguageItemComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
guard let component = self.component else {
return
}
if case .ended = recognizer.state {
component.action()
}
}
func update(component: LanguageItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let size = CGSize(width: availableSize.width, height: 42.0)
let leftTitleInset: CGFloat = 60.0
let rightTitleInset: CGFloat = 8.0
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.regular(17.0), textColor: component.theme.contextMenu.primaryColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - leftTitleInset - rightTitleInset, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: leftTitleInset, y: floorToScreenPixels((size.height - titleSize.height) * 0.5)), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.addSubview(titleView)
}
titleView.frame = titleFrame
}
if component.isSelected {
let imageIcon: ComponentView<Empty>
var imageIconTransition = transition
if let current = self.imageIcon {
imageIcon = current
} else {
imageIconTransition = imageIconTransition.withAnimation(.none)
imageIcon = ComponentView()
self.imageIcon = imageIcon
}
let imageIconSize = imageIcon.update(
transition: imageIconTransition,
component: AnyComponent(BundleIconComponent(
name: "Chat/Context Menu/Check",
tintColor: component.theme.contextMenu.primaryColor
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let imageIconFrame = CGRect(origin: CGPoint(x: 23.0, y: floorToScreenPixels((size.height - imageIconSize.height) * 0.5)), size: imageIconSize)
if let imageIconView = imageIcon.view {
if imageIconView.superview == nil {
imageIconView.isUserInteractionEnabled = false
self.addSubview(imageIconView)
}
imageIconTransition.setFrame(view: imageIconView, frame: imageIconFrame)
}
} else {
if let imageIcon = self.imageIcon {
self.imageIcon = nil
imageIcon.view?.removeFromSuperview()
}
}
return size
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class StyleItemComponent: Component {
let theme: PresentationTheme
let icon: String
let title: String
let action: () -> Void
init(
theme: PresentationTheme,
icon: String,
title: String,
action: @escaping () -> Void
) {
self.theme = theme
self.icon = icon
self.title = title
self.action = action
}
static func ==(lhs: StyleItemComponent, rhs: StyleItemComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.icon != rhs.icon {
return false
}
if lhs.title != rhs.title {
return false
}
return true
}
final class View: UIView {
private var imageIcon: ComponentView<Empty>?
private let title = ComponentView<Empty>()
private var component: StyleItemComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
guard let component = self.component else {
return
}
if case .ended = recognizer.state {
component.action()
}
}
func update(component: StyleItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let iconTintColor = component.theme.list.itemPrimaryTextColor
let imageIcon: ComponentView<Empty>
var iconTransition = transition
if let current = self.imageIcon {
imageIcon = current
} else {
iconTransition = iconTransition.withAnimation(.none)
imageIcon = ComponentView()
self.imageIcon = imageIcon
}
let iconSize = imageIcon.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.icon, font: Font.regular(25.0), textColor: .black))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 8.0), size: iconSize)
if let imageIconView = imageIcon.view {
if imageIconView.superview == nil {
self.addSubview(imageIconView)
}
iconTransition.setFrame(view: imageIconView, frame: iconFrame)
}
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(10.0), textColor: iconTintColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: availableSize.height - 9.0 - titleSize.height), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
titleView.frame = titleFrame
}
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -0,0 +1,722 @@
import Foundation
import UIKit
import SwiftSignalKit
import Display
import TelegramPresentationData
import ComponentFlow
import ComponentDisplayAdapters
import AccountContext
import ViewControllerComponent
import MultilineTextComponent
import ButtonComponent
import BundleIconComponent
import TelegramCore
import PresentationDataUtils
import ResizableSheetComponent
import GlassBarButtonComponent
import TabBarComponent
import TranslateUI
import LottieComponent
final class TextProcessingContentComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
final class ExternalState {
fileprivate(set) var isProcessing: Bool = false
fileprivate(set) var result: TextWithEntities?
init() {
}
}
let externalState: ExternalState
let context: AccountContext
let inputText: TextWithEntities
let copyCurrentResult: () -> Void
let displayLanguageSelectionMenu: (UIView, String, TelegramComposeAIMessageMode.Style, Bool, @escaping (String, TelegramComposeAIMessageMode.Style) -> Void) -> Void
init(
externalState: ExternalState,
context: AccountContext,
inputText: TextWithEntities,
copyCurrentResult: @escaping () -> Void,
displayLanguageSelectionMenu: @escaping (UIView, String, TelegramComposeAIMessageMode.Style, Bool, @escaping (String, TelegramComposeAIMessageMode.Style) -> Void) -> Void
) {
self.externalState = externalState
self.context = context
self.inputText = inputText
self.copyCurrentResult = copyCurrentResult
self.displayLanguageSelectionMenu = displayLanguageSelectionMenu
}
static func ==(lhs: TextProcessingContentComponent, rhs: TextProcessingContentComponent) -> Bool {
return true
}
private enum Mode {
case translate
case stylize
case fix
}
final class View: UIView {
private var component: TextProcessingContentComponent?
private weak var state: EmptyComponentState?
private var isUpdating: Bool = false
private let modeTabs = ComponentView<Empty>()
private let currentContentBackground: UIImageView
private let currentContentContainer: UIView
private let translateState = TextProcessingTranslateContentComponent.ExternalState()
private let stylizeState = TextProcessingTranslateContentComponent.ExternalState()
private let fixState = TextProcessingTranslateContentComponent.ExternalState()
private var currentContent: (mode: Mode, view: ComponentView<Empty>)?
private var currentMode: Mode = .translate
override init(frame: CGRect) {
self.currentContentBackground = UIImageView()
self.currentContentContainer = UIView()
self.currentContentContainer.clipsToBounds = true
super.init(frame: frame)
self.addSubview(self.currentContentBackground)
self.addSubview(self.currentContentContainer)
self.translateState.resultUpdated = { [weak self] result in
guard let self, let component = self.component else {
return
}
if case .translate = self.currentMode {
component.externalState.result = result?.text
}
}
self.translateState.isProcessingUpdated = { [weak self] isProcessing in
guard let self, let component = self.component else {
return
}
if case .translate = self.currentMode {
component.externalState.isProcessing = isProcessing
}
}
self.stylizeState.resultUpdated = { [weak self] result in
guard let self, let component = self.component else {
return
}
if case .stylize = self.currentMode {
component.externalState.result = result?.text
}
}
self.stylizeState.isProcessingUpdated = { [weak self] isProcessing in
guard let self, let component = self.component else {
return
}
if case .stylize = self.currentMode {
component.externalState.isProcessing = isProcessing
}
}
self.fixState.resultUpdated = { [weak self] result in
guard let self, let component = self.component else {
return
}
if case .fix = self.currentMode {
component.externalState.result = result?.text
}
}
self.fixState.isProcessingUpdated = { [weak self] isProcessing in
guard let self, let component = self.component else {
return
}
if case .fix = self.currentMode {
component.externalState.isProcessing = isProcessing
}
}
}
required init?(coder: NSCoder) {
preconditionFailure()
}
func update(component: TextProcessingContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
let environment = environment[ViewControllerComponentContainer.Environment.self].value
self.component = component
self.state = state
let sideInset: CGFloat = 16.0
var contentHeight: CGFloat = 0.0
contentHeight += 85.0
var tabs: [TabBarComponent.Item] = []
tabs.append(TabBarComponent.Item(
content: .customItem(TabBarComponent.Item.Content.CustomItem(
id: "translate",
title: "Translate",
icon: .bundleIcon(name: "TextProcessing/TabTranslate")
)),
action: { [weak self] _ in
guard let self else {
return
}
if self.currentMode != .translate {
self.currentMode = .translate
}
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
},
doubleTapAction: nil,
contextAction: nil
))
tabs.append(TabBarComponent.Item(
content: .customItem(TabBarComponent.Item.Content.CustomItem(
id: "stylize",
title: "Style",
icon: .bundleIcon(name: "TextProcessing/TabStylize")
)),
action: { [weak self] _ in
guard let self else {
return
}
if self.currentMode != .stylize {
self.currentMode = .stylize
}
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
},
doubleTapAction: nil,
contextAction: nil
))
tabs.append(TabBarComponent.Item(
content: .customItem(TabBarComponent.Item.Content.CustomItem(
id: "fix",
title: "Fix",
icon: .bundleIcon(name: "TextProcessing/TabFix")
)),
action: { [weak self] _ in
guard let self else {
return
}
if self.currentMode != .fix {
self.currentMode = .fix
}
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
},
doubleTapAction: nil,
contextAction: nil
))
let currentModeId: String
switch self.currentMode {
case .translate:
currentModeId = "translate"
case .stylize:
currentModeId = "stylize"
case .fix:
currentModeId = "fix"
}
let modeTabsSize = self.modeTabs.update(
transition: transition,
component: AnyComponent(TabBarComponent(
theme: environment.theme,
tintSelectedItem: false,
strings: environment.strings,
items: tabs,
search: nil,
selectedId: currentModeId,
outerInsets: UIEdgeInsets()
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 62.0)
)
let modeTabsFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: modeTabsSize)
if let modeTabsView = self.modeTabs.view {
if modeTabsView.superview == nil {
self.modeTabs.parentState = state
self.addSubview(modeTabsView)
}
transition.setFrame(view: modeTabsView, frame: modeTabsFrame)
}
contentHeight += modeTabsSize.height
contentHeight += 24.0
if let currentContent = self.currentContent, currentContent.mode != self.currentMode {
if let currentContentView = currentContent.view.view {
transition.setAlpha(view: currentContentView, alpha: 0.0, completion: { [weak currentContentView] _ in
currentContentView?.removeFromSuperview()
})
}
self.currentContent = nil
}
let contentComponent: AnyComponent<Empty>
switch self.currentMode {
case .translate:
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,
externalState: self.translateState,
inputText: component.inputText,
mode: .translate,
copyAction: component.copyCurrentResult,
displayLanguageSelectionMenu: component.displayLanguageSelectionMenu
))
case .stylize:
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,
externalState: self.stylizeState,
inputText: component.inputText,
mode: .stylize,
copyAction: component.copyCurrentResult,
displayLanguageSelectionMenu: component.displayLanguageSelectionMenu
))
case .fix:
contentComponent = AnyComponent(TextProcessingTranslateContentComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,
externalState: self.fixState,
inputText: component.inputText,
mode: .fix,
copyAction: component.copyCurrentResult,
displayLanguageSelectionMenu: component.displayLanguageSelectionMenu
))
}
let content: ComponentView<Empty>
var contentTransition = transition
if let current = self.currentContent {
content = current.view
} else {
content = ComponentView()
self.currentContent = (self.currentMode, content)
contentTransition = contentTransition.withAnimation(.none)
}
let contentSize = content.update(
transition: contentTransition,
component: contentComponent,
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000000.0)
)
if let contentView = content.view {
if contentView.superview == nil {
content.parentState = state
self.currentContentContainer.addSubview(contentView)
contentView.layer.allowsGroupOpacity = true
contentView.alpha = 0.0
}
alphaTransition.setAlpha(view: contentView, alpha: 1.0)
contentTransition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: contentSize))
}
let contentFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: contentSize)
transition.setFrame(view: self.currentContentContainer, frame: contentFrame)
if self.currentContentBackground.image == nil {
self.currentContentBackground.image = generateStretchableFilledCircleImage(diameter: 60.0, color: .white)?.withRenderingMode(.alwaysTemplate)
}
self.currentContentBackground.tintColor = environment.theme.list.itemBlocksBackgroundColor
transition.setFrame(view: self.currentContentBackground, frame: contentFrame)
contentHeight += contentSize.height
contentHeight += 106.0
return CGSize(width: availableSize.width, height: contentHeight)
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class TextProcessingSheetComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let inputText: TextWithEntities
let copyCurrentResult: (TextWithEntities) -> Void
let completion: (TextWithEntities) -> Void
init(
context: AccountContext,
inputText: TextWithEntities,
copyCurrentResult: @escaping (TextWithEntities) -> Void,
completion: @escaping (TextWithEntities) -> Void
) {
self.context = context
self.inputText = inputText
self.copyCurrentResult = copyCurrentResult
self.completion = completion
}
static func ==(lhs: TextProcessingSheetComponent, rhs: TextProcessingSheetComponent) -> Bool {
return true
}
static var body: Body {
let sheet = Child(ResizableSheetComponent<EnvironmentType>.self)
let languageSelectionMenu = Child(TextProcessingLanguageSelectionComponent.self)
let animateOut = StoredActionSlot(Action<Void>.self)
let contentExternalState = TextProcessingContentComponent.ExternalState()
class LanguageSelectionMenuData {
let sourceView: UIView
let currentLanguage: String
let currentStyle: TelegramComposeAIMessageMode.Style
let displayStyle: Bool
let completion: (String, TelegramComposeAIMessageMode.Style) -> Void
init(sourceView: UIView, currentLanguage: String, currentStyle: TelegramComposeAIMessageMode.Style, displayStyle: Bool, completion: @escaping (String, TelegramComposeAIMessageMode.Style) -> Void) {
self.sourceView = sourceView
self.currentLanguage = currentLanguage
self.currentStyle = currentStyle
self.displayStyle = displayStyle
self.completion = completion
}
}
var languageSelectionMenuData: LanguageSelectionMenuData?
return { context in
let environment = context.environment[EnvironmentType.self]
let controller = environment.controller
let theme = environment.theme
let state = context.state
let dismiss: (Bool) -> Void = { animated in
if animated {
animateOut.invoke(Action { _ in
if let controller = controller() {
controller.dismiss(completion: nil)
}
})
} else {
if let controller = controller() {
controller.dismiss(completion: nil)
}
}
}
let completion = context.component.completion
let performMainAction: () -> Void = {
if let result = contentExternalState.result {
completion(result)
}
dismiss(true)
}
let copyCurrentResult = context.component.copyCurrentResult
let copyCurrentResultImpl: () -> Void = {
if let result = contentExternalState.result {
copyCurrentResult(result)
}
}
//TODO:localize (for AI: don't remove this line)
let titleString: String = "AI Editor"
//TODO:localize (for AI: don't remove this line)
let actionButtonTitle: String = "Apply"
let sheet = sheet.update(
component: ResizableSheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(TextProcessingContentComponent(
externalState: contentExternalState,
context: context.component.context,
inputText: context.component.inputText,
copyCurrentResult: {
copyCurrentResultImpl()
},
displayLanguageSelectionMenu: { [weak state] sourceView, currentLanguage, currentStyle, displayStyle, completion in
languageSelectionMenuData = LanguageSelectionMenuData(sourceView: sourceView, currentLanguage: currentLanguage, currentStyle: currentStyle, displayStyle: displayStyle, completion: completion)
state?.updated(transition: .immediate)
}
)),
titleItem: AnyComponent(TitleComponent(
theme: theme,
title: titleString,
isProcessing: contentExternalState.isProcessing
)),
leftItem: AnyComponent(
GlassBarButtonComponent(
size: CGSize(width: 44.0, height: 44.0),
backgroundColor: nil,
isDark: theme.overallDarkAppearance,
state: .glass,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: theme.chat.inputPanel.panelControlColor
)
)),
action: { _ in
dismiss(true)
}
)
),
bottomItem: AnyComponent(
ButtonComponent(
background: ButtonComponent.Background(
style: .glass,
color: theme.list.itemCheckColors.fillColor,
foreground: theme.list.itemCheckColors.foregroundColor,
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(ButtonTextContentComponent(
text: actionButtonTitle,
badge: 0,
textColor: theme.list.itemCheckColors.foregroundColor,
badgeBackground: theme.list.itemCheckColors.foregroundColor,
badgeForeground: theme.list.itemCheckColors.fillColor
))
),
isEnabled: !contentExternalState.isProcessing,
displaysProgress: false,
action: {
performMainAction()
}
)
),
backgroundColor: .color(theme.list.blocksBackgroundColor),
animateOut: animateOut
),
environment: {
environment
ResizableSheetComponentEnvironment(
theme: theme,
statusBarHeight: environment.statusBarHeight,
safeInsets: environment.safeInsets,
metrics: environment.metrics,
deviceMetrics: environment.deviceMetrics,
isDisplaying: environment.value.isVisible,
isCentered: environment.metrics.widthClass == .regular,
screenSize: context.availableSize,
regularMetricsSize: nil,
dismiss: { animated in
dismiss(animated)
}
)
},
availableSize: context.availableSize,
transition: context.transition
)
context.add(sheet
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
if let languageSelectionMenuDataValue = languageSelectionMenuData {
let languageSelectionMenu = languageSelectionMenu.update(
component: TextProcessingLanguageSelectionComponent(
theme: environment.theme,
strings: environment.strings,
sourceView: languageSelectionMenuDataValue.sourceView,
topLanguages: [],
selectedLanguageCode: languageSelectionMenuDataValue.currentLanguage,
currentStyle: languageSelectionMenuDataValue.currentStyle,
displayStyles: languageSelectionMenuDataValue.displayStyle,
completion: languageSelectionMenuDataValue.completion,
dismissed: { [weak state] in
languageSelectionMenuData = nil
state?.updated(transition: .immediate)
}
),
availableSize: context.availableSize,
transition: context.transition
)
context.add(languageSelectionMenu
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
)
}
return context.availableSize
}
}
}
public class TextProcessingScreen: ViewControllerComponentContainer {
private let context: AccountContext
public init(
context: AccountContext,
inputText: TextWithEntities,
copyResult: @escaping (TextWithEntities) -> Void,
completion: @escaping (TextWithEntities) -> Void
) {
self.context = context
super.init(
context: context,
component: TextProcessingSheetComponent(
context: context,
inputText: inputText,
copyCurrentResult: copyResult,
completion: completion
),
navigationBarAppearance: .none,
statusBarStyle: .ignore,
theme: .default
)
self.statusBar.statusBarStyle = .Ignore
self.navigationPresentation = .flatModal
self.blocksBackgroundWhenInOverlay = true
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
public func dismissAnimated() {
if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? ResizableSheetComponent<ViewControllerComponentContainer.Environment>.View {
view.dismissAnimated()
}
}
}
private final class TitleComponent: Component {
let theme: PresentationTheme
let title: String
let isProcessing: Bool
init(
theme: PresentationTheme,
title: String,
isProcessing: Bool
) {
self.theme = theme
self.title = title
self.isProcessing = isProcessing
}
static func ==(lhs: TitleComponent, rhs: TitleComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.isProcessing != rhs.isProcessing {
return false
}
return true
}
final class View: UIView {
private var animationIcon: ComponentView<Empty>?
private let title = ComponentView<Empty>()
private var component: TitleComponent?
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: TitleComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.addSubview(titleView)
}
titleView.frame = titleFrame
}
if component.isProcessing {
let animationIcon: ComponentView<Empty>
var animationIconTransition = transition
if let current = self.animationIcon {
animationIcon = current
} else {
animationIconTransition = animationIconTransition.withAnimation(.none)
animationIcon = ComponentView()
self.animationIcon = animationIcon
}
let animationIconSize = animationIcon.update(
transition: animationIconTransition,
component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(
name: "SparklesEmoji"
),
placeholderColor: nil,
startingPosition: .begin,
size: CGSize(width: 30.0, height: 30.0),
loop: true
)),
environment: {},
containerSize: CGSize(width: 30.0, height: 30.0)
)
let animationIconFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - animationIconSize.height) * 0.5) - 2.0), size: animationIconSize)
if let animationIconView = animationIcon.view {
if animationIconView.superview == nil {
self.addSubview(animationIconView)
animationIconView.alpha = 0.0
}
animationIconTransition.setFrame(view: animationIconView, frame: animationIconFrame)
transition.setAlpha(view: animationIconView, alpha: 1.0)
}
} else {
if let animationIcon = self.animationIcon {
self.animationIcon = nil
if let animationIconView = animationIcon.view {
transition.setAlpha(view: animationIconView, alpha: 0.0, completion: { [weak animationIconView] _ in
animationIconView?.removeFromSuperview()
})
}
}
}
return titleSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -0,0 +1,273 @@
import Foundation
import UIKit
import Display
import TelegramPresentationData
import ComponentFlow
import MultilineTextComponent
import TelegramCore
final class TextProcessingStyleSelectionComponent: Component {
let theme: PresentationTheme
let strings: PresentationStrings
let selectedStyle: TelegramComposeAIMessageMode.Style
let updateStyle: (TelegramComposeAIMessageMode.Style) -> Void
init(
theme: PresentationTheme,
strings: PresentationStrings,
selectedStyle: TelegramComposeAIMessageMode.Style,
updateStyle: @escaping (TelegramComposeAIMessageMode.Style) -> Void
) {
self.theme = theme
self.strings = strings
self.selectedStyle = selectedStyle
self.updateStyle = updateStyle
}
static func ==(lhs: TextProcessingStyleSelectionComponent, rhs: TextProcessingStyleSelectionComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.selectedStyle != rhs.selectedStyle {
return false
}
return true
}
final class View: UIView {
private var component: TextProcessingStyleSelectionComponent?
private weak var state: EmptyComponentState?
private var isUpdating: Bool = false
private var itemViews: [TelegramComposeAIMessageMode.Style: ComponentView<Empty>] = [:]
private let selectedBackgroundView: UIImageView
override init(frame: CGRect) {
self.selectedBackgroundView = UIImageView()
self.selectedBackgroundView.isHidden = true
self.selectedBackgroundView.alpha = 0.0
super.init(frame: frame)
self.addSubview(self.selectedBackgroundView)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
}
required init?(coder: NSCoder) {
preconditionFailure()
}
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
guard let component = self.component else {
return
}
if case .ended = recognizer.state {
for (id, itemView) in self.itemViews {
if let itemComponentView = itemView.view {
if itemComponentView.bounds.contains(self.convert(recognizer.location(in: self), to: itemComponentView)) {
component.updateStyle(id)
break
}
}
}
}
}
func update(component: TextProcessingStyleSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
self.component = component
self.state = state
var styleData: [(id: TelegramComposeAIMessageMode.Style, icon: String, title: String)] = []
styleData.append((.neutral, "🏳️", "Neutral"))
styleData.append((.formal, "🤝", "Formal"))
styleData.append((.short, "🎯", "Short"))
styleData.append((.savage, "🍖", "Savage"))
styleData.append((.biblical, "🕯", "Biblical"))
styleData.append((.posh, "🍷", "Posh"))
let itemSize = CGSize(width: floor(availableSize.width / CGFloat(styleData.count)), height: availableSize.height)
var selectedItemFrame: CGRect?
for i in 0 ..< styleData.count {
let style = styleData[i]
let itemView: ComponentView<Empty>
var itemTransition = transition
if let current = self.itemViews[style.id] {
itemView = current
} else {
itemTransition = itemTransition.withAnimation(.none)
itemView = ComponentView()
self.itemViews[style.id] = itemView
}
let itemFrame = CGRect(origin: CGPoint(x: CGFloat(i) * itemSize.width, y: 0.0), size: itemSize)
let _ = itemView.update(
transition: itemTransition,
component: AnyComponent(ItemComponent(
theme: component.theme,
icon: style.icon,
title: style.title
)),
environment: {},
containerSize: itemSize
)
if let itemComponentView = itemView.view {
if itemComponentView.superview == nil {
self.addSubview(itemComponentView)
}
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
}
if style.id == component.selectedStyle {
selectedItemFrame = CGRect(origin: CGPoint(x: itemFrame.minX, y: itemFrame.minY - 5.0), size: CGSize(width: itemFrame.width, height: itemFrame.height + 5.0 + 3.0))
}
}
if self.selectedBackgroundView.image == nil {
self.selectedBackgroundView.image = generateStretchableFilledCircleImage(diameter: 16.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
}
self.selectedBackgroundView.tintColor = component.theme.list.itemHighlightedBackgroundColor.withMultipliedAlpha(0.6)
if let selectedItemFrame {
var selectedBackgroundTransition = transition
if self.selectedBackgroundView.isHidden {
self.selectedBackgroundView.isHidden = false
selectedBackgroundTransition = selectedBackgroundTransition.withAnimation(.none)
}
selectedBackgroundTransition.setFrame(view: self.selectedBackgroundView, frame: selectedItemFrame)
alphaTransition.setAlpha(view: self.selectedBackgroundView, alpha: 1.0)
} else {
if !self.selectedBackgroundView.isHidden {
alphaTransition.setAlpha(view: self.selectedBackgroundView, alpha: 0.0, completion: { [weak self] flag in
guard let self, flag else {
return
}
self.selectedBackgroundView.isHidden = true
})
}
}
return CGSize(width: availableSize.width, height: availableSize.height)
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class ItemComponent: Component {
let theme: PresentationTheme
let icon: String
let title: String
init(
theme: PresentationTheme,
icon: String,
title: String
) {
self.theme = theme
self.icon = icon
self.title = title
}
static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.icon != rhs.icon {
return false
}
if lhs.title != rhs.title {
return false
}
return true
}
final class View: UIView {
private var imageIcon: ComponentView<Empty>?
private let title = ComponentView<Empty>()
private var component: ItemComponent?
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: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let iconTintColor = component.theme.list.itemPrimaryTextColor
let imageIcon: ComponentView<Empty>
var iconTransition = transition
if let current = self.imageIcon {
imageIcon = current
} else {
iconTransition = iconTransition.withAnimation(.none)
imageIcon = ComponentView()
self.imageIcon = imageIcon
}
let iconSize = imageIcon.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.icon, font: Font.regular(25.0), textColor: .black))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: -1.0), size: iconSize)
if let imageIconView = imageIcon.view {
if imageIconView.superview == nil {
self.addSubview(imageIconView)
}
iconTransition.setFrame(view: imageIconView, frame: iconFrame)
}
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(10.0), textColor: iconTintColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 100.0)
)
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: availableSize.height - 5.0 - titleSize.height), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
titleView.frame = titleFrame
}
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -0,0 +1,532 @@
import Foundation
import UIKit
import SwiftSignalKit
import Display
import TelegramPresentationData
import ComponentFlow
import AccountContext
import MultilineTextComponent
import BundleIconComponent
import TelegramCore
import MultilineTextWithEntitiesComponent
import TextFormat
import PlainButtonComponent
import CheckComponent
import ShimmerEffect
final class TextProcessingTextAreaComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let titlePrefix: String
let title: String
let titleAction: ((UIView) -> Void)?
let isExpanded: (value: Bool, toggle: () -> Void)?
let copyAction: (() -> Void)?
let emojify: (value: Bool, toggle: () -> Void)?
let text: TextWithEntities?
let textCorrectionRanges: [Range<Int>]
init(
context: AccountContext,
theme: PresentationTheme,
titlePrefix: String,
title: String,
titleAction: ((UIView) -> Void)?,
isExpanded: (value: Bool, toggle: () -> Void)?,
copyAction: (() -> Void)?,
emojify: (value: Bool, toggle: () -> Void)?,
text: TextWithEntities?,
textCorrectionRanges: [Range<Int>]
) {
self.context = context
self.theme = theme
self.titlePrefix = titlePrefix
self.isExpanded = isExpanded
self.copyAction = copyAction
self.title = title
self.titleAction = titleAction
self.emojify = emojify
self.text = text
self.textCorrectionRanges = textCorrectionRanges
}
static func ==(lhs: TextProcessingTextAreaComponent, rhs: TextProcessingTextAreaComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.titlePrefix != rhs.titlePrefix {
return false
}
if lhs.title != rhs.title {
return false
}
if (lhs.titleAction == nil) != (rhs.titleAction == nil) {
return false
}
if lhs.isExpanded?.value != rhs.isExpanded?.value {
return false
}
if (lhs.copyAction == nil) != (rhs.copyAction == nil) {
return false
}
if lhs.emojify?.value != rhs.emojify?.value {
return false
}
if lhs.text != rhs.text {
return false
}
if lhs.textCorrectionRanges != rhs.textCorrectionRanges {
return false
}
return true
}
final class View: UIView {
private var component: TextProcessingTextAreaComponent?
private weak var state: EmptyComponentState?
private var isUpdating: Bool = false
private let titlePrefix = ComponentView<Empty>()
private let title = ComponentView<Empty>()
private var titleArrow: ComponentView<Empty>?
private var emojify: ComponentView<Empty>?
private let titleButton: HighlightTrackingButton
private let textState = MultilineTextWithEntitiesComponent.External()
private let textContainer: UIView
private let text = ComponentView<Empty>()
private var expandShadow: UIImageView?
private var expandButton: ComponentView<Empty>?
private let copyButton = ComponentView<Empty>()
private let measureLoadingTextState = MultilineTextWithEntitiesComponent.External()
private let measureLoadingText = ComponentView<Empty>()
private var shimmerEffectNode: ShimmerEffectNode?
override init(frame: CGRect) {
self.textContainer = UIView()
self.textContainer.clipsToBounds = true
self.titleButton = HighlightTrackingButton()
super.init(frame: frame)
self.addSubview(self.textContainer)
self.addSubview(self.titleButton)
self.titleButton.highligthedChanged = { [weak self] highighed in
guard let self, let titleView = self.title.view, let titleArrowView = self.titleArrow?.view else {
return
}
if highighed {
titleView.alpha = 0.6
titleArrowView.alpha = 0.6
} else {
let transition: ComponentTransition = .easeInOut(duration: 0.25)
transition.setAlpha(view: titleView, alpha: 1.0)
transition.setAlpha(view: titleArrowView, alpha: 1.0)
}
}
self.titleButton.addTarget(self, action: #selector(self.titleButtonPressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
preconditionFailure()
}
@objc private func titleButtonPressed() {
guard let component = self.component, let titleView = self.title.view else {
return
}
component.titleAction?(titleView)
}
func update(component: TextProcessingTextAreaComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
self.component = component
self.state = state
self.titleButton.isUserInteractionEnabled = component.titleAction != nil
let topInset: CGFloat = 0.0
let bottomInset: CGFloat = 0.0
let sideInset: CGFloat = 0.0
var contentHeight: CGFloat = 0.0
contentHeight += topInset
let titlePrefixSize = self.titlePrefix.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.titlePrefix, font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 10.0, height: 100.0)
)
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(13.0), textColor: component.theme.list.itemAccentColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 10.0, height: 100.0)
)
let titlePrefixFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: titlePrefixSize)
var titleFrame = CGRect(origin: CGPoint(x: titlePrefixFrame.maxX, y: titlePrefixFrame.minY), size: titleSize)
if !component.titlePrefix.isEmpty {
titleFrame.origin.x += 3.0
}
transition.setFrame(view: self.titleButton, frame: titleFrame.insetBy(dx: -10.0, dy: -10.0))
if let titlePrefixView = self.titlePrefix.view {
if titlePrefixView.superview == nil {
titlePrefixView.layer.anchorPoint = CGPoint()
titlePrefixView.isUserInteractionEnabled = false
self.addSubview(titlePrefixView)
}
titlePrefixView.bounds = CGRect(origin: CGPoint(), size: titlePrefixFrame.size)
transition.setPosition(view: titlePrefixView, position: titlePrefixFrame.origin)
}
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.layer.anchorPoint = CGPoint()
titleView.isUserInteractionEnabled = false
self.addSubview(titleView)
}
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
transition.setPosition(view: titleView, position: titleFrame.origin)
}
if component.titleAction != nil {
let titleArrow: ComponentView<Empty>
var titleArrowTransition = transition
if let current = self.titleArrow {
titleArrow = current
} else {
titleArrowTransition = titleArrowTransition.withAnimation(.none)
titleArrow = ComponentView()
self.titleArrow = titleArrow
}
let titleArrowSize = titleArrow.update(
transition: titleArrowTransition,
component: AnyComponent(BundleIconComponent(
name: "Item List/ExpandableSelectorArrows", tintColor: component.theme.list.itemAccentColor.withMultipliedAlpha(0.8))),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let titleArrowFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 2.0, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - titleArrowSize.height) * 0.5)), size: titleArrowSize)
if let titleArrowView = titleArrow.view {
if titleArrowView.superview == nil {
titleArrowView.isUserInteractionEnabled = false
self.addSubview(titleArrowView)
}
transition.setFrame(view: titleArrowView, frame: titleArrowFrame)
}
} else {
if let titleArrow = self.titleArrow {
self.titleArrow = nil
titleArrow.view?.removeFromSuperview()
}
}
if let emojifyValue = component.emojify {
let emojify: ComponentView<Empty>
var emojifyTransition = transition
if let current = self.emojify {
emojify = current
} else {
emojify = ComponentView()
self.emojify = emojify
emojifyTransition = emojifyTransition.withAnimation(.none)
}
let checkTheme = CheckComponent.Theme(
backgroundColor: component.theme.list.itemCheckColors.fillColor,
strokeColor: component.theme.list.itemCheckColors.foregroundColor,
borderColor: component.theme.list.itemCheckColors.strokeColor,
overlayBorder: false,
hasInset: false,
hasShadow: false
)
let emojifySize = emojify.update(
transition: emojifyTransition,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(HStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent(
theme: checkTheme,
size: CGSize(width: 16.0, height: 16.0),
selected: emojifyValue.value
))),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Emojify", font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor))
)))
], spacing: 7.0)),
effectAlignment: .center,
action: {
emojifyValue.toggle()
},
animateAlpha: false,
animateScale: false
)),
environment: {
},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
)
let emojifyFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - emojifySize.width, y: contentHeight), size: emojifySize)
if let emojifyView = emojify.view {
if emojifyView.superview == nil {
self.addSubview(emojifyView)
}
emojifyTransition.setFrame(view: emojifyView, frame: emojifyFrame)
}
} else {
if let emojify = self.emojify {
self.emojify = nil
emojify.view?.removeFromSuperview()
}
}
contentHeight += 25.0
let fontSize: CGFloat = 17.0
let textValue = NSMutableAttributedString(attributedString: stringWithAppliedEntities(
component.text?.text ?? "",
entities: component.text?.entities ?? [],
baseColor: component.theme.list.itemPrimaryTextColor,
linkColor: component.theme.list.itemAccentColor,
baseFont: Font.regular(fontSize),
linkFont: Font.regular(fontSize),
boldFont: Font.semibold(fontSize),
italicFont: Font.italic(fontSize),
boldItalicFont: Font.semiboldItalic(fontSize),
fixedFont: Font.monospace(fontSize),
blockQuoteFont: Font.monospace(fontSize),
message: nil
))
for range in component.textCorrectionRanges {
if range.lowerBound >= 0 && range.upperBound < textValue.length {
textValue.addAttributes([
.underlineColor: component.theme.list.itemAccentColor,
.underlineStyle: NSUnderlineStyle.patternDot.rawValue
], range: NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound))
}
}
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(MultilineTextWithEntitiesComponent(
external: self.textState,
context: component.context,
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer,
placeholderColor: component.theme.list.mediaPlaceholderColor,
text: .plain(textValue),
maximumNumberOfLines: 0,
lineSpacing: 0.12,
cutout: nil,
insets: UIEdgeInsets(),
spoilerColor: component.theme.list.itemPrimaryTextColor,
enableLooping: true,
displaysAsynchronously: false,
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
)
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: textSize)
if let textView = self.text.view {
if textView.superview == nil {
textView.layer.anchorPoint = CGPoint()
self.textContainer.addSubview(textView)
}
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
}
var textContainerFrame = textFrame
if let isExpanded = component.isExpanded, let textLayout = self.textState.layout, textLayout.numberOfLines > 1 {
if !isExpanded.value, let firstLineRect = textLayout.linesRects().first {
textContainerFrame.size.height = firstLineRect.maxY - 14.0
}
let expandButton: ComponentView<Empty>
var expandButtonTransition = transition
if let current = self.expandButton {
expandButton = current
} else {
expandButtonTransition = expandButtonTransition.withAnimation(.none)
expandButton = ComponentView()
self.expandButton = expandButton
}
let expandShadow: UIImageView
if let current = self.expandShadow {
expandShadow = current
} else {
expandShadow = UIImageView()
self.expandShadow = expandShadow
self.addSubview(expandShadow)
}
let expandShadowExtent: CGFloat = 20.0
if expandShadow.image == nil {
let baseSize: CGFloat = 20.0
expandShadow.image = generateImage(CGSize(width: baseSize + expandShadowExtent * 2.0, height: baseSize + expandShadowExtent * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let colors: [CGColor] = [UIColor.white.cgColor, UIColor.white.withAlphaComponent(0.0).cgColor]
let locations: [CGFloat] = [0.0, 1.0]
if let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: locations) {
let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
context.drawRadialGradient(gradient, startCenter: center, startRadius: baseSize / 2.0, endCenter: center, endRadius: size.width / 2.0, options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
}
})?.withRenderingMode(.alwaysTemplate).stretchableImage(withLeftCapWidth: Int(baseSize / 2.0 + expandShadowExtent), topCapHeight: Int(baseSize / 2.0 + expandShadowExtent))
}
expandShadow.tintColor = component.theme.list.itemBlocksBackgroundColor
let expandButtonSize = expandButton.update(
transition: expandButtonTransition,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: isExpanded.value ? "less" : "more", font: Font.regular(17.0), textColor: component.theme.list.itemAccentColor))
)),
effectAlignment: .right,
action: {
isExpanded.toggle()
},
animateAlpha: false,
animateScale: false,
animateContents: false
)),
environment: {},
containerSize: CGSize(width: 200.0, height: 100.0)
)
let expandButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - expandButtonSize.width, y: textContainerFrame.maxY - expandButtonSize.height - 2.0), size: expandButtonSize)
if let expandButtonView = expandButton.view {
if expandButtonView.superview == nil {
expandButtonView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
self.addSubview(expandButtonView)
}
expandButtonTransition.setPosition(view: expandButtonView, position: CGPoint(x: expandButtonFrame.maxX, y: expandButtonFrame.minY))
expandButtonTransition.setBounds(view: expandButtonView, bounds: CGRect(origin: CGPoint(), size: expandButtonFrame.size))
expandButtonTransition.setFrame(view: expandShadow, frame: expandButtonFrame.insetBy(dx: -expandShadowExtent, dy: -expandShadowExtent))
}
} else {
if let expandButton = self.expandButton {
self.expandButton = nil
expandButton.view?.removeFromSuperview()
}
if let expandShadow = self.expandShadow {
self.expandShadow = nil
expandShadow.removeFromSuperview()
}
}
if component.text == nil {
let shimmerEffectNode: ShimmerEffectNode
if let current = self.shimmerEffectNode {
shimmerEffectNode = current
} else {
shimmerEffectNode = ShimmerEffectNode()
self.shimmerEffectNode = shimmerEffectNode
self.addSubview(shimmerEffectNode.view)
}
let measureLoadingTextSize = self.measureLoadingText.update(
transition: .immediate,
component: AnyComponent(MultilineTextWithEntitiesComponent(
external: self.measureLoadingTextState,
context: component.context,
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer,
placeholderColor: component.theme.list.mediaPlaceholderColor,
text: .plain(NSAttributedString(string: "a\na\na\na", font: Font.regular(fontSize), textColor: .black)),
maximumNumberOfLines: 0,
lineSpacing: 0.12,
cutout: nil,
insets: UIEdgeInsets(),
spoilerColor: component.theme.list.itemPrimaryTextColor,
enableLooping: true,
displaysAsynchronously: false,
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
)
textContainerFrame.size = CGSize(width: availableSize.width, height: measureLoadingTextSize.height)
shimmerEffectNode.frame = textContainerFrame
var shapes: [ShimmerEffectNode.Shape] = []
if let textLayout = self.measureLoadingTextState.layout {
let lineWidths: [CGFloat] = [1.0, 0.9, 1.0, 0.8]
var index = 0
for lineRect in textLayout.linesRects() {
shapes.append(.roundedRectLine(startPoint: CGPoint(x: 0.0, y: lineRect.midY - 18.0), width: floor(textContainerFrame.width * lineWidths[index % lineWidths.count]), diameter: 6.0))
index += 1
}
}
shimmerEffectNode.updateAbsoluteRect(shimmerEffectNode.bounds, within: shimmerEffectNode.bounds.size)
shimmerEffectNode.update(backgroundColor: component.theme.list.plainBackgroundColor, foregroundColor: component.theme.list.mediaPlaceholderColor, shimmeringColor: component.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: shimmerEffectNode.bounds.size)
} else {
if let shimmerEffectNode = self.shimmerEffectNode {
self.shimmerEffectNode = nil
shimmerEffectNode.view.removeFromSuperview()
}
}
if let copyAction = component.copyAction {
let copyButtonSize = self.copyButton.update(
transition: transition,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(BundleIconComponent(
name: "Chat/Context Menu/Copy",
tintColor: component.theme.list.itemAccentColor
)),
effectAlignment: .right,
action: {
copyAction()
},
animateAlpha: true,
animateScale: false,
animateContents: false
)),
environment: {},
containerSize: CGSize(width: 200.0, height: 100.0)
)
let copyButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - copyButtonSize.width, y: textContainerFrame.maxY - copyButtonSize.height - 2.0), size: copyButtonSize)
if let copyButtonView = self.copyButton.view {
if copyButtonView.superview == nil {
copyButtonView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
self.addSubview(copyButtonView)
}
transition.setPosition(view: copyButtonView, position: CGPoint(x: copyButtonFrame.maxX, y: copyButtonFrame.minY))
transition.setBounds(view: copyButtonView, bounds: CGRect(origin: CGPoint(), size: copyButtonFrame.size))
alphaTransition.setAlpha(view: copyButtonView, alpha: component.text != nil ? 1.0 : 0.0)
}
}
transition.setFrame(view: self.textContainer, frame: textContainerFrame)
contentHeight += textContainerFrame.height
contentHeight += bottomInset
return CGSize(width: availableSize.width, height: contentHeight)
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -0,0 +1,460 @@
import Foundation
import NaturalLanguage
import UIKit
import SwiftSignalKit
import Display
import TelegramPresentationData
import ComponentFlow
import AccountContext
import MultilineTextComponent
import BundleIconComponent
import TelegramCore
import TranslateUI
private let languageRecognizer = NLLanguageRecognizer()
func localizedLanguageName(strings: PresentationStrings, language: String) -> String {
let toLang = language
let key = "Translation.Language.\(toLang)"
let translateTitle: String
if let string = strings.primaryComponent.dict[key] {
translateTitle = string
} else {
let languageLocale = Locale(identifier: language)
let toLanguage = languageLocale.localizedString(forLanguageCode: toLang) ?? ""
return toLanguage
}
return translateTitle
}
final class TextProcessingTranslateContentComponent: Component {
enum Mode {
case translate
case stylize
case fix
}
final class ExternalState {
fileprivate(set) var sourceLanguage: String?
fileprivate(set) var result: (language: String, text: TextWithEntities?, textCorrectionRanges: [Range<Int>])? = nil {
didSet {
if self.result?.language != oldValue?.language || self.result?.text != oldValue?.text {
self.resultUpdated?(self.result)
}
}
}
var resultUpdated: (((language: String, text: TextWithEntities?, textCorrectionRanges: [Range<Int>])?) -> Void)?
fileprivate(set) var emojify: Bool = false
fileprivate(set) var isSourceTextExpanded: Bool = false
fileprivate(set) var style: TelegramComposeAIMessageMode.Style = .neutral
fileprivate(set) var isProcessing: Bool = false {
didSet {
if self.isProcessing != oldValue {
self.isProcessingUpdated?(self.isProcessing)
}
}
}
var isProcessingUpdated: ((Bool) -> Void)?
init() {
}
}
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let inputText: TextWithEntities
let externalState: ExternalState
let mode: Mode
let copyAction: () -> Void
let displayLanguageSelectionMenu: (UIView, String, TelegramComposeAIMessageMode.Style, Bool, @escaping (String, TelegramComposeAIMessageMode.Style) -> Void) -> Void
init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
externalState: ExternalState,
inputText: TextWithEntities,
mode: Mode,
copyAction: @escaping () -> Void,
displayLanguageSelectionMenu: @escaping (UIView, String, TelegramComposeAIMessageMode.Style, Bool, @escaping (String, TelegramComposeAIMessageMode.Style) -> Void) -> Void
) {
self.context = context
self.theme = theme
self.strings = strings
self.externalState = externalState
self.inputText = inputText
self.mode = mode
self.copyAction = copyAction
self.displayLanguageSelectionMenu = displayLanguageSelectionMenu
}
static func ==(lhs: TextProcessingTranslateContentComponent, rhs: TextProcessingTranslateContentComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.externalState !== rhs.externalState {
return false
}
if lhs.inputText != rhs.inputText {
return false
}
if lhs.mode != rhs.mode {
return false
}
return true
}
final class View: UIView {
private let sourceText = ComponentView<Empty>()
private let targetText = ComponentView<Empty>()
private let separatorLayer: SimpleLayer
private var component: TextProcessingTranslateContentComponent?
private weak var state: EmptyComponentState?
private var isUpdating: Bool = false
private var processDisposable: Disposable?
override init(frame: CGRect) {
self.separatorLayer = SimpleLayer()
super.init(frame: frame)
self.layer.addSublayer(self.separatorLayer)
}
required init?(coder: NSCoder) {
preconditionFailure()
}
deinit {
self.processDisposable?.dispose()
}
private func beginTranslationIfNecessary(reset: Bool) {
guard let component = self.component else {
return
}
if reset {
self.processDisposable?.dispose()
self.processDisposable = nil
if let result = component.externalState.result {
component.externalState.result = (result.language, nil, [])
}
}
if let result = component.externalState.result, result.text == nil, self.processDisposable == nil {
switch component.mode {
case .translate:
component.externalState.isProcessing = true
self.processDisposable = (component.context.engine.messages.composeAIMessage(
text: component.inputText,
mode: .translate(toLanguage: result.language, emojify: component.externalState.emojify, style: component.externalState.style)
) |> deliverOnMainQueue).startStrict(next: { [weak self] processedText in
guard let self, let component = self.component, let processedText else {
return
}
component.externalState.isProcessing = false
component.externalState.result = (result.language, processedText.text, processedText.diffRanges)
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
})
case .stylize:
if !component.externalState.emojify && component.externalState.style == .neutral {
component.externalState.isProcessing = false
component.externalState.result = (result.language, component.inputText, [])
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
} else {
component.externalState.isProcessing = true
self.processDisposable = (component.context.engine.messages.composeAIMessage(
text: component.inputText,
mode: .stylize(emojify: component.externalState.emojify, style: component.externalState.style)
) |> deliverOnMainQueue).startStrict(next: { [weak self] processedText in
guard let self, let component = self.component, let processedText else {
return
}
component.externalState.isProcessing = false
component.externalState.result = (result.language, processedText.text, processedText.diffRanges)
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
})
}
case .fix:
component.externalState.isProcessing = true
self.processDisposable = (component.context.engine.messages.composeAIMessage(
text: component.inputText,
mode: .proofread
) |> deliverOnMainQueue).startStrict(next: { [weak self] processedText in
guard let self, let component = self.component, let processedText else {
return
}
component.externalState.isProcessing = false
component.externalState.result = (result.language, processedText.text, processedText.diffRanges)
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
})
}
}
}
func update(component: TextProcessingTranslateContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
if component.externalState.sourceLanguage == nil {
languageRecognizer.processString(component.inputText.text)
let hypotheses = languageRecognizer.languageHypotheses(withMaximum: 3)
languageRecognizer.reset()
let filteredLanguages = hypotheses.sorted(by: { $0.value > $1.value })
if let first = filteredLanguages.first {
component.externalState.sourceLanguage = normalizeTranslationLanguage(first.key.rawValue)
} else {
component.externalState.sourceLanguage = "en"
}
}
self.component = component
self.state = state
if component.externalState.result == nil {
switch component.mode {
case .translate:
var languageCode = component.strings.baseLanguageCode
let rawSuffix = "-raw"
if languageCode.hasSuffix(rawSuffix) {
languageCode = String(languageCode.dropLast(rawSuffix.count))
}
component.externalState.result = (languageCode, nil, [])
self.beginTranslationIfNecessary(reset: false)
case .stylize:
component.externalState.result = ("", component.inputText, [])
case .fix:
component.externalState.result = ("", nil, [])
self.beginTranslationIfNecessary(reset: false)
}
}
var contentHeight: CGFloat = 0.0
let sideInset: CGFloat = 16.0
let topInset: CGFloat = 17.0
let bottomInset: CGFloat = 14.0
let blockSpacing: CGFloat = 30.0
let fromPrefix: String
let toPrefix: String
var toTitle: String
switch component.mode {
case .translate:
fromPrefix = "From"
toPrefix = "To"
toTitle = localizedLanguageName(strings: component.strings, language: component.externalState.result?.language ?? "")
if component.externalState.style != .neutral {
toTitle.append(" (")
let styleName: String
switch component.externalState.style {
case .neutral:
styleName = ""
case .formal:
styleName = "Formal"
case .short:
styleName = "Short"
case .savage:
styleName = "Savage"
case .biblical:
styleName = "Biblical"
case .posh:
styleName = "Posh"
}
toTitle.append(styleName)
toTitle.append(")")
}
case .stylize, .fix:
fromPrefix = "Original:"
toPrefix = "Result"
toTitle = ""
}
contentHeight += topInset
if case .stylize = component.mode {
let sourceTextSize = self.sourceText.update(
transition: transition,
component: AnyComponent(TextProcessingStyleSelectionComponent(
theme: component.theme,
strings: component.strings,
selectedStyle: component.externalState.style,
updateStyle: { [weak self] style in
guard let self, let component = self.component else {
return
}
if component.externalState.style != style {
component.externalState.style = style
if let result = component.externalState.result {
component.externalState.result = (result.language, nil, [])
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
self.beginTranslationIfNecessary(reset: true)
}
}
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 46.0)
)
let sourceTextFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: sourceTextSize)
contentHeight += sourceTextSize.height
if let sourceTextView = self.sourceText.view {
if sourceTextView.superview == nil {
self.sourceText.parentState = state
self.addSubview(sourceTextView)
}
transition.setFrame(view: sourceTextView, frame: sourceTextFrame)
}
} else {
let sourceTextSize = self.sourceText.update(
transition: transition,
component: AnyComponent(TextProcessingTextAreaComponent(
context: component.context,
theme: component.theme,
titlePrefix: fromPrefix,
title: localizedLanguageName(strings: component.strings, language: component.externalState.sourceLanguage ?? ""),
titleAction: nil,
isExpanded: (
component.externalState.isSourceTextExpanded,
{ [weak self] in
guard let self, let component = self.component else {
return
}
component.externalState.isSourceTextExpanded = !component.externalState.isSourceTextExpanded
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
}
),
copyAction: nil,
emojify: nil,
text: component.inputText,
textCorrectionRanges: []
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
)
let sourceTextFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: sourceTextSize)
contentHeight += sourceTextSize.height
if let sourceTextView = self.sourceText.view {
if sourceTextView.superview == nil {
self.sourceText.parentState = state
self.addSubview(sourceTextView)
}
transition.setFrame(view: sourceTextView, frame: sourceTextFrame)
}
}
let targetTextSize = self.targetText.update(
transition: transition,
component: AnyComponent(TextProcessingTextAreaComponent(
context: component.context,
theme: component.theme,
titlePrefix: toPrefix,
title: toTitle,
titleAction: component.mode == .translate ? { [weak self] sourceView in
guard let self, let component = self.component, let result = component.externalState.result else {
return
}
component.displayLanguageSelectionMenu(sourceView, result.language, component.externalState.style, true, { [weak self] language, style in
guard let self, let component = self.component else {
return
}
if component.externalState.result?.language != language || component.externalState.style != style {
component.externalState.result = (language, nil, [])
component.externalState.style = style
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
self.beginTranslationIfNecessary(reset: true)
}
})
} : nil,
isExpanded: nil,
copyAction: { [weak self] in
guard let self, let component = self.component else {
return
}
component.copyAction()
},
emojify: (component.mode == .translate || component.mode == .stylize) ? (
component.externalState.emojify,
{ [weak self] in
guard let self, let component = self.component else {
return
}
component.externalState.emojify = !component.externalState.emojify
self.beginTranslationIfNecessary(reset: true)
if !self.isUpdating {
self.state?.updated(transition: .spring(duration: 0.4))
}
}
) : nil,
text: component.externalState.result?.text,
textCorrectionRanges: component.externalState.result?.textCorrectionRanges ?? []
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
)
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight + floorToScreenPixels((blockSpacing - UIScreenPixel) * 0.5) - 1.0), size: CGSize(width: availableSize.width - sideInset * 2.0, height: UIScreenPixel)))
self.separatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor
contentHeight += blockSpacing
let targetTextFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: targetTextSize)
contentHeight += targetTextSize.height
if let targetTextView = self.targetText.view {
if targetTextView.superview == nil {
self.targetText.parentState = state
self.addSubview(targetTextView)
}
transition.setFrame(view: targetTextView, frame: targetTextFrame)
}
contentHeight += bottomInset
return CGSize(width: availableSize.width, height: contentHeight)
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ai.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "fix.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "style.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "translate.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -4501,6 +4501,11 @@ extension ChatControllerImpl {
return return
} }
self.controllerInteraction?.sendEmoji(text, attribute, immediately) self.controllerInteraction?.sendEmoji(text, attribute, immediately)
}, openAICompose: { [weak self] in
guard let self else {
return
}
self.chatDisplayNode.openAICompose()
}, updateHistoryFilter: { [weak self] update in }, updateHistoryFilter: { [weak self] update in
guard let self else { guard let self else {
return return

View file

@ -61,6 +61,8 @@ import LegacyChatHeaderPanelComponent
import ChatSearchNavigationContentNode import ChatSearchNavigationContentNode
import GroupCallHeaderPanelComponent import GroupCallHeaderPanelComponent
import PresentationDataUtils import PresentationDataUtils
import TextProcessingScreen
import Pasteboard
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
let itemNode: OverlayMediaItemNode let itemNode: OverlayMediaItemNode
@ -878,6 +880,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.textInputPanelNode = ChatTextInputPanelNode(context: context, presentationInterfaceState: chatPresentationInterfaceState, presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode), presentController: { [weak self] controller in self.textInputPanelNode = ChatTextInputPanelNode(context: context, presentationInterfaceState: chatPresentationInterfaceState, presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode), presentController: { [weak self] controller in
self?.interfaceInteraction?.presentController(controller, nil) self?.interfaceInteraction?.presentController(controller, nil)
}) })
self.textInputPanelNode?.isAIEnabled = true
self.textInputPanelNode?.textInputAccessoryPanel = textInputAccessoryPanel self.textInputPanelNode?.textInputAccessoryPanel = textInputAccessoryPanel
self.textInputPanelNode?.textInputContextPanel = textInputContextPanel self.textInputPanelNode?.textInputContextPanel = textInputContextPanel
self.textInputPanelNode?.storedInputLanguage = chatPresentationInterfaceState.interfaceState.inputLanguage self.textInputPanelNode?.storedInputLanguage = chatPresentationInterfaceState.interfaceState.inputLanguage
@ -4386,8 +4389,58 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
} }
} }
func sendCurrentMessage(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, repeatPeriod: Int32? = nil, postpone: Bool = false, messageEffect: ChatSendMessageEffect? = nil, completion: @escaping () -> Void = {}) { func openAICompose() {
var effectivePresentationInterfaceState = self.chatPresentationInterfaceState
if let textInputPanelNode = self.textInputPanelNode {
effectivePresentationInterfaceState = effectivePresentationInterfaceState.updatedInterfaceState { $0.withUpdatedEffectiveInputState(textInputPanelNode.inputTextState) }
}
let effectiveInputText: NSAttributedString
if effectivePresentationInterfaceState.interfaceState.editMessage != nil && effectivePresentationInterfaceState.interfaceState.postSuggestionState != nil {
effectiveInputText = expandedInputStateAttributedString(effectivePresentationInterfaceState.interfaceState.effectiveInputState.inputText)
} else {
effectiveInputText = expandedInputStateAttributedString(effectivePresentationInterfaceState.interfaceState.composeInputState.inputText)
}
if effectiveInputText.length == 0 {
return
}
let inputText = trimChatInputText(effectiveInputText)
var entities: [MessageTextEntity] = []
if inputText.length != 0 {
if case let .customChatContents(customChatContents) = self.chatPresentationInterfaceState.subject, case .businessLinkSetup = customChatContents.kind {
entities = generateChatInputTextEntities(inputText, generateLinks: false)
} else {
entities = generateTextEntities(inputText.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(inputText, maxAnimatedEmojisInText: 0))
}
}
self.controller?.push(TextProcessingScreen(
context: self.context,
inputText: TextWithEntities(text: inputText.string, entities: entities),
copyResult: { [weak self] text in
guard let self else {
return
}
let _ = self
storeMessageTextInPasteboard(text.text, entities: text.entities)
},
completion: { [weak self] text in
guard let self, let controller = self.controller else {
return
}
controller.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
return state.updatedInterfaceState { interfaceState in
return interfaceState.withUpdatedEffectiveInputState(ChatTextInputState(inputText: chatInputStateStringWithAppliedEntities(text.text, entities: text.entities)))
}
})
}
))
}
func sendCurrentMessage(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, repeatPeriod: Int32? = nil, postpone: Bool = false, messageEffect: ChatSendMessageEffect? = nil, completion: @escaping () -> Void = {}) {
guard let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode else { guard let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode else {
return return
} }

View file

@ -460,6 +460,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
let panel = ChatTextInputPanelNode(context: context, presentationInterfaceState: chatPresentationInterfaceState, presentationContext: nil, presentController: { [weak interfaceInteraction] controller in let panel = ChatTextInputPanelNode(context: context, presentationInterfaceState: chatPresentationInterfaceState, presentationContext: nil, presentController: { [weak interfaceInteraction] controller in
interfaceInteraction?.presentController(controller, nil) interfaceInteraction?.presentController(controller, nil)
}) })
panel.isAIEnabled = true
panel.textInputAccessoryPanel = textInputAccessoryPanel panel.textInputAccessoryPanel = textInputAccessoryPanel
panel.textInputContextPanel = textInputContextPanel panel.textInputContextPanel = textInputContextPanel
panel.chatControllerInteraction = chatControllerInteraction panel.chatControllerInteraction = chatControllerInteraction

View file

@ -172,9 +172,7 @@ final class MusicListenTracker {
let duration = self.accumulatedDuration let duration = self.accumulatedDuration
let trackDuration = self.trackDuration let trackDuration = self.trackDuration
// Check minimum threshold: min(30s, 90% of track) if duration >= 3.0 && trackDuration > 0 {
let threshold = min(30.0, trackDuration * 0.9)
if duration >= threshold && duration > 0 && trackDuration > 0 {
let reportedDuration = Int(duration) let reportedDuration = Int(duration)
self.reportDisposable.set( self.reportDisposable.set(
self.engine.messages.reportMusicListened(file: fileReference, duration: reportedDuration).startStrict() self.engine.messages.reportMusicListened(file: fileReference, duration: reportedDuration).startStrict()