Various improvements
This commit is contained in:
parent
67d43248da
commit
a2d5c530a5
49 changed files with 4592 additions and 1028 deletions
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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": [],
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -760,7 +760,9 @@ public final class ChatTextInputPanelComponent: Component {
|
||||||
},
|
},
|
||||||
sendEmoji: { _, _, _ in
|
sendEmoji: { _, _, _ in
|
||||||
},
|
},
|
||||||
updateHistoryFilter: { _ in
|
openAICompose: {
|
||||||
|
}
|
||||||
|
,updateHistoryFilter: { _ in
|
||||||
},
|
},
|
||||||
updateChatLocationThread: { _, _ in
|
updateChatLocationThread: { _, _ in
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
40
submodules/TelegramUI/Components/TextProcessingScreen/BUILD
Normal file
40
submodules/TelegramUI/Components/TextProcessingScreen/BUILD
Normal 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",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/InputAIIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/InputAIIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ai.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/InputAIIcon.imageset/ai.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/InputAIIcon.imageset/ai.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"provides-namespace" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabFix.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabFix.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "fix.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabFix.imageset/fix.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabFix.imageset/fix.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabStylize.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabStylize.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "style.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabStylize.imageset/style.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabStylize.imageset/style.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabTranslate.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/TextProcessing/TabTranslate.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "translate.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabTranslate.imageset/translate.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/TextProcessing/TabTranslate.imageset/translate.pdf
vendored
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/SparklesEmoji.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/SparklesEmoji.tgs
Normal file
Binary file not shown.
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue