Various improvements

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

View file

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

View file

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

View file

@ -460,6 +460,12 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
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 minScaledTranslation: CGFloat = itemLayout.isTablet ? 0.0 : (itemLayout.containerSize.height - itemLayout.containerSize.height * minScale) * 0.5 - 6.0
let minScaledCornerRadius: CGFloat = itemLayout.containerCornerRadius

View file

@ -15,12 +15,21 @@ private let codeIcon: UIImage = {
}()
private final class TextNodeStrikethrough {
enum Style {
case single
case wavy
}
let range: NSRange
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.frame = frame
self.color = color
self.style = style
}
}
@ -205,11 +214,7 @@ public struct TextNodeCutout: Equatable {
}
private let drawUnderlinesManually: Bool = {
if #available(iOS 18.0, *) {
return true
} else {
return false
}
return true
}()
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
blockWidth = max(blockWidth, line.frame.origin.x + line.frame.width)
if let range = line.range {
attributedString.enumerateAttributes(in: range, options: []) { attributes, range, _ in
if let lineRange = line.range {
attributedString.enumerateAttributes(in: lineRange, options: []) { attributes, range, _ in
if attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil {
var ascent: 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)
} 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 upperX = ceil(CTLineGetOffsetForStringIndex(line.line, range.location + range.length, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(line.line, clampedEnd, nil))
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) {
@ -2041,44 +2047,47 @@ open class TextNode: ASDisplayNode, TextNodeProtocol {
addSpoiler(line: coreTextLine, ascent: ascent, descent: descent, startIndex: range.location, endIndex: range.location + range.length)
} 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 upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, clampedEnd, nil))
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] {
let clampedEnd = max(range.location, min(brokenLineRange.location + brokenLineRange.length, range.location + range.length))
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
strikethroughs.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight)))
} else if let _ = attributes[NSAttributedString.Key.underlineStyle] {
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 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 upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, clampedEnd, nil))
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 {
headIndent = paragraphStyle.headIndent
}
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) {
var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
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 {
var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0
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)
}
}
}
var lineAscent: CGFloat = 0.0
var lineDescent: CGFloat = 0.0
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)
} 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 upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, clampedEnd, nil))
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] {
let clampedEnd = max(range.location, min(lineRange.location + lineRange.length, range.location + range.length))
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
strikethroughs.append(TextNodeStrikethrough(range: range, frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight)))
} else if let _ = attributes[NSAttributedString.Key.underlineStyle] {
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 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 upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil))
let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, clampedEnd, nil))
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 {
headIndent = paragraphStyle.headIndent
}
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) {
var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0
CTLineGetTypographicBounds(coreTextLine, &ascent, &descent, nil)
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 {
var ascent: CGFloat = 0.0
var descent: CGFloat = 0.0
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)
}
}
@ -2605,22 +2617,59 @@ open class TextNode: ASDisplayNode, TextNodeProtocol {
}
if drawUnderlinesManually {
if !line.underlines.isEmpty {
for strikethrough in line.underlines {
guard let lineRange = line.range else {
continue
for strikethrough in line.underlines {
guard let lineRange = line.range else {
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
if range == strikethrough.range, let color = attributes[NSAttributedString.Key.foregroundColor] as? UIColor {
textColor = color
}
}
if let textColor = textColor {
}
switch strikethrough.style {
case .single:
if let color = strikethrough.color {
context.setFillColor(color.cgColor)
} else if let textColor {
context.setFillColor(textColor.cgColor)
}
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))
case .wavy:
if let color = strikethrough.color {
context.setStrokeColor(color.cgColor)
} else if let textColor {
context.setStrokeColor(textColor.cgColor)
}
context.setLineWidth(1.33)
context.setLineCap(.round)
context.setLineJoin(.round)
let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY - 6.0)
let amplitude: CGFloat = 1.2
let period: CGFloat = 8.0
let phase: CGFloat = -0.5
let midY = frame.midY
let step: CGFloat = 1.0
context.saveGState()
context.clip(to: frame)
var x = frame.minX
context.move(to: CGPoint(x: x, y: midY + amplitude * sin(phase)))
x += step
while x <= frame.maxX + step {
let y = midY + amplitude * sin((x - frame.minX) * 2.0 * .pi / period + phase)
context.addLine(to: CGPoint(x: x, y: y))
x += step
}
context.strokePath()
context.restoreGState()
/*context.setFillColor(UIColor.red.cgColor)
let frame1 = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
context.fill(CGRect(x: frame1.minX, y: frame1.minY + 1.0, width: frame1.width, height: 1.0))*/
}
}
}

View file

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

View file

@ -654,6 +654,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1280209983] = { return Api.MessageEntity.parse_messageEntityCashtag($0) }
dict[681706865] = { return Api.MessageEntity.parse_messageEntityCode($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[-1874147385] = { return Api.MessageEntity.parse_messageEntityFormattedDate($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[428978491] = { return Api.bots.PopularAppBots.parse_popularAppBots($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[-541588713] = { return Api.channels.ChannelParticipant.parse_channelParticipant($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[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($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[-1058912715] = { return Api.messages.DhConfig.parse_dhConfigNotModified($0) }
dict[718878489] = { return Api.messages.DialogFilters.parse_dialogFilters($0) }
@ -2601,6 +2605,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.messages.CheckedHistoryImportPeer:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.ComposedMessageWithAI:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.DhConfig:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.DialogFilters:

View file

@ -94,6 +94,41 @@ public extension Api {
return ("messageEntityCustomEmoji", [("offset", self.offset as Any), ("length", self.length as Any), ("documentId", self.documentId as Any)])
}
}
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 var offset: Int32
public var length: Int32
@ -266,6 +301,9 @@ public extension Api {
case messageEntityCashtag(Cons_messageEntityCashtag)
case messageEntityCode(Cons_messageEntityCode)
case messageEntityCustomEmoji(Cons_messageEntityCustomEmoji)
case messageEntityDiffDelete(Cons_messageEntityDiffDelete)
case messageEntityDiffInsert(Cons_messageEntityDiffInsert)
case messageEntityDiffReplace(Cons_messageEntityDiffReplace)
case messageEntityEmail(Cons_messageEntityEmail)
case messageEntityFormattedDate(Cons_messageEntityFormattedDate)
case messageEntityHashtag(Cons_messageEntityHashtag)
@ -342,6 +380,28 @@ public extension Api {
serializeInt32(_data.length, buffer: buffer, boxed: false)
serializeInt64(_data.documentId, buffer: buffer, boxed: false)
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):
if boxed {
buffer.appendInt32(1692693954)
@ -466,6 +526,12 @@ public extension Api {
return ("messageEntityCode", [("offset", _data.offset as Any), ("length", _data.length as Any)])
case .messageEntityCustomEmoji(let _data):
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):
return ("messageEntityEmail", [("offset", _data.offset as Any), ("length", _data.length as Any)])
case .messageEntityFormattedDate(let _data):
@ -620,6 +686,51 @@ public extension Api {
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? {
var _1: Int32?
_1 = reader.readInt32()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2772,12 +2772,12 @@ 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()
buffer.appendInt32(-1295431495)
buffer.appendInt32(-1088047117)
bot.serialize(buffer, true)
serializeString(requestId, 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
serializeString(webappReqId, buffer: buffer, boxed: false)
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)
var result: Api.KeyboardButton?
if let signature = reader.readInt32() {
@ -5496,9 +5496,9 @@ 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()
buffer.appendInt32(-1080571914)
buffer.appendInt32(-45978882)
serializeInt32(flags, buffer: buffer, boxed: false)
text.serialize(buffer, true)
if Int(flags) & Int(1 << 1) != 0 {
@ -5507,11 +5507,11 @@ public extension Api.functions.messages {
if Int(flags) & Int(1 << 2) != 0 {
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)
var result: Api.TextWithEntities?
var result: Api.messages.ComposedMessageWithAI?
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
})
@ -8887,16 +8887,16 @@ 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()
buffer.appendInt32(-1662773304)
buffer.appendInt32(1818030759)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {
serializeInt32(msgId!, buffer: buffer, boxed: false)
}
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)
buffer.appendInt32(481674261)
@ -8904,7 +8904,7 @@ public extension Api.functions.messages {
for item in requestedPeers {
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)
var result: Api.Updates?
if let signature = reader.readInt32() {

View file

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

View file

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

View file

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

View file

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

View file

@ -369,7 +369,7 @@ func _internal_sendBotRequestedPeer(account: Account, peerId: PeerId, messageId:
if let _ = requestId {
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
return .generic
}
@ -436,7 +436,7 @@ func _internal_getRequestedWebViewButton(account: Account, botId: PeerId, reques
guard let inputUser = inputUser else {
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
return .generic
}

View file

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

View file

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

View file

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

View file

@ -246,6 +246,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
public var mediaRecordingAccessibilityArea: AccessibilityAreaNode?
private let counterTextNode: ImmediateTextNode
private var aiButton: (button: HighlightTrackingButton, icon: UIImageView)?
public let menuButton: HighlightTrackingButtonNode
private let menuButtonBackgroundView: GlassBackgroundView
private let menuButtonClippingNode: ASDisplayNode
@ -333,6 +335,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
private let hapticFeedback = HapticFeedback()
public var isAIEnabled: Bool = false
public var inputTextState: ChatTextInputState {
if let textInputNode = self.textInputNode {
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))
transition.updateFrame(view: self.glassBackgroundContainer, frame: containerFrame)
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()
}
@objc private func aiButtonPressed() {
self.interfaceInteraction?.openAICompose()
}
private func displayViewOnceTooltip(text: String) {
guard let context = self.context, let parentController = self.interfaceInteraction?.chatController() else {
return

View file

@ -258,7 +258,7 @@ public final class HeaderPanelContainerComponent: Component {
}
panelCount += component.panels.count
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
} else {
cornerRadius = 20.0

View file

@ -183,7 +183,7 @@ public final class HorizontalTabsComponent: Component {
public let contextAction: ((ContextExtractedContentContainingView, ContextGesture?) -> 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.content = content
self.badge = badge

View file

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

View file

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

View file

@ -248,27 +248,73 @@ public final class NavigationSearchView: UIView {
public final class TabBarComponent: Component {
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 doubleTapAction: (() -> Void)?
public let contextAction: ((ContextGesture, ContextExtractedContentContainingView) -> Void)?
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)?) {
self.item = item
public init(content: Content, action: @escaping (Bool) -> Void, doubleTapAction: (() -> Void)?, contextAction: ((ContextGesture, ContextExtractedContentContainingView) -> Void)?) {
self.content = content
self.action = action
self.doubleTapAction = doubleTapAction
self.contextAction = contextAction
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
if lhs === rhs {
return true
}
if lhs.item !== rhs.item {
if lhs.content != rhs.content {
return false
}
if (lhs.doubleTapAction == nil) != (rhs.doubleTapAction == nil) {
@ -301,6 +347,7 @@ public final class TabBarComponent: Component {
}
public let theme: PresentationTheme
public let tintSelectedItem: Bool
public let strings: PresentationStrings
public let items: [Item]
public let search: Search?
@ -309,6 +356,7 @@ public final class TabBarComponent: Component {
public init(
theme: PresentationTheme,
tintSelectedItem: Bool = true,
strings: PresentationStrings,
items: [Item],
search: Search?,
@ -316,6 +364,7 @@ public final class TabBarComponent: Component {
outerInsets: UIEdgeInsets
) {
self.theme = theme
self.tintSelectedItem = tintSelectedItem
self.strings = strings
self.items = items
self.search = search
@ -327,6 +376,9 @@ public final class TabBarComponent: Component {
if lhs.theme !== rhs.theme {
return false
}
if lhs.tintSelectedItem != rhs.tintSelectedItem {
return false
}
if lhs.strings !== rhs.strings {
return false
}
@ -633,6 +685,7 @@ public final class TabBarComponent: Component {
theme: component.theme,
isCompact: false,
isSelected: false,
tintSelectedItem: true,
isUnconstrained: true
)),
environment: {},
@ -709,6 +762,7 @@ public final class TabBarComponent: Component {
theme: component.theme,
isCompact: component.search?.isActive == true,
isSelected: false,
tintSelectedItem: component.tintSelectedItem,
isUnconstrained: false
)),
environment: {},
@ -721,6 +775,7 @@ public final class TabBarComponent: Component {
theme: component.theme,
isCompact: component.search?.isActive == true,
isSelected: true,
tintSelectedItem: component.tintSelectedItem,
isUnconstrained: false
)),
environment: {},
@ -895,13 +950,15 @@ private final class ItemComponent: Component {
let theme: PresentationTheme
let isCompact: Bool
let isSelected: Bool
let tintSelectedItem: 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.theme = theme
self.isCompact = isCompact
self.isSelected = isSelected
self.tintSelectedItem = tintSelectedItem
self.isUnconstrained = isUnconstrained
}
@ -918,6 +975,9 @@ private final class ItemComponent: Component {
if lhs.isSelected != rhs.isSelected {
return false
}
if lhs.tintSelectedItem != rhs.tintSelectedItem {
return false
}
if lhs.isUnconstrained != rhs.isUnconstrained {
return false
}
@ -934,37 +994,37 @@ private final class ItemComponent: Component {
private var component: ItemComponent?
private weak var state: EmptyComponentState?
private var setImageListener: Int?
private var setSelectedImageListener: Int?
private var setBadgeListener: Int?
override init(frame: CGRect) {
self.contextContainerView = ContextExtractedContentContainingView()
super.init(frame: frame)
self.addSubview(self.contextContainerView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
if let component = self.component {
if let component = self.component, case let .tabBarItem(tabBarItem) = component.item.content {
if let setImageListener = self.setImageListener {
component.item.item.removeSetImageListener(setImageListener)
tabBarItem.removeSetImageListener(setImageListener)
}
if let setSelectedImageListener = self.setSelectedImageListener {
component.item.item.removeSetSelectedImageListener(setSelectedImageListener)
tabBarItem.removeSetSelectedImageListener(setSelectedImageListener)
}
if let setBadgeListener = self.setBadgeListener {
component.item.item.removeSetBadgeListener(setBadgeListener)
tabBarItem.removeSetBadgeListener(setBadgeListener)
}
}
}
func playSelectionAnimation() {
if let animationIconView = self.animationIcon?.view as? LottieComponent.View {
animationIconView.playOnce()
@ -975,125 +1035,241 @@ private final class ItemComponent: Component {
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.25)
let previousComponent = self.component
if previousComponent?.item.item !== component.item.item {
if let setImageListener = self.setImageListener {
self.component?.item.item.removeSetImageListener(setImageListener)
}
if let setSelectedImageListener = self.setSelectedImageListener {
self.component?.item.item.removeSetSelectedImageListener(setSelectedImageListener)
}
if let setBadgeListener = self.setBadgeListener {
self.component?.item.item.removeSetBadgeListener(setBadgeListener)
}
self.setImageListener = component.item.item.addSetImageListener { [weak self] _ in
guard let self else {
return
let previousTabBarItem: UITabBarItem?
if let previousComponent, case let .tabBarItem(tabBarItem) = previousComponent.item.content {
previousTabBarItem = tabBarItem
} else {
previousTabBarItem = nil
}
let currentTabBarItem: UITabBarItem?
if case let .tabBarItem(tabBarItem) = component.item.content {
currentTabBarItem = tabBarItem
} else {
currentTabBarItem = nil
}
if previousTabBarItem !== currentTabBarItem {
if let previousTabBarItem {
if let setImageListener = self.setImageListener {
previousTabBarItem.removeSetImageListener(setImageListener)
}
self.state?.updated(transition: .immediate, isLocal: true)
}
self.setSelectedImageListener = component.item.item.addSetSelectedImageListener { [weak self] _ in
guard let self else {
return
if let setSelectedImageListener = self.setSelectedImageListener {
previousTabBarItem.removeSetSelectedImageListener(setSelectedImageListener)
}
self.state?.updated(transition: .immediate, isLocal: true)
}
self.setBadgeListener = UITabBarItem_addSetBadgeListener(component.item.item) { [weak self] _ in
guard let self else {
return
if let setBadgeListener = self.setBadgeListener {
previousTabBarItem.removeSetBadgeListener(setBadgeListener)
}
self.setImageListener = nil
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.state = state
if let animationName = component.item.item.animationName {
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: 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)
}
let iconTintColor = (component.isSelected && component.tintSelectedItem && !component.isCompact) ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor
let title: String
let badgeValue: String?
switch component.item.content {
case let .tabBarItem(tabBarItem):
title = tabBarItem.title ?? " "
badgeValue = tabBarItem.badgeValue
if let animationName = tabBarItem.animationName {
if let imageIcon = self.imageIcon {
self.imageIcon = nil
imageIcon.view?.removeFromSuperview()
}
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 ? component.item.item.selectedImage : component.item.item.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)
}
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: animationName
),
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: tabBarItem.animationOffset.x, dy: tabBarItem.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)
}
} 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(
transition: .immediate,
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: {},
containerSize: CGSize(width: availableSize.width, height: 100.0)
@ -1106,8 +1282,8 @@ private final class ItemComponent: Component {
titleView.frame = titleFrame
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>
var badgeTransition = transition
if let current = self.badge {
@ -1142,11 +1318,11 @@ private final class ItemComponent: Component {
self.badge = nil
badge.view?.removeFromSuperview()
}
transition.setFrame(view: self.contextContainerView, 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)
if component.isUnconstrained {
return CGSize(width: titleSize.width + 10.0 * 2.0, height: availableSize.height)
} else {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -61,6 +61,8 @@ import LegacyChatHeaderPanelComponent
import ChatSearchNavigationContentNode
import GroupCallHeaderPanelComponent
import PresentationDataUtils
import TextProcessingScreen
import Pasteboard
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
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?.interfaceInteraction?.presentController(controller, nil)
})
self.textInputPanelNode?.isAIEnabled = true
self.textInputPanelNode?.textInputAccessoryPanel = textInputAccessoryPanel
self.textInputPanelNode?.textInputContextPanel = textInputContextPanel
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 {
return
}

View file

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

View file

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